From c1284fc3b6fa1f4a944d1a8386f1638c4cf28e69 Mon Sep 17 00:00:00 2001 From: CleverMouse Date: Fri, 17 May 2013 23:53:28 +0000 Subject: [PATCH] USB support git-svn-id: svn://kolibrios.org@3520 a494cfbc-eb01-0410-851d-a64ba20cac60 --- data/eng/Makefile | 172 +-- data/it/Makefile | 162 +-- data/rus/Makefile | 172 +-- data/sp/Makefile | 172 +-- kernel/trunk/bus/usb/ehci.inc | 1914 ++++++++++++++++++++++++++++ kernel/trunk/bus/usb/hccommon.inc | 472 +++++++ kernel/trunk/bus/usb/hub.inc | 1237 ++++++++++++++++++ kernel/trunk/bus/usb/init.inc | 250 ++++ kernel/trunk/bus/usb/memory.inc | 215 ++++ kernel/trunk/bus/usb/ohci.inc | 1601 +++++++++++++++++++++++ kernel/trunk/bus/usb/pipe.inc | 813 ++++++++++++ kernel/trunk/bus/usb/protocol.inc | 926 ++++++++++++++ kernel/trunk/bus/usb/scheduler.inc | 508 ++++++++ kernel/trunk/bus/usb/uhci.inc | 1817 ++++++++++++++++++++++++++ kernel/trunk/const.inc | 11 + kernel/trunk/core/dll.inc | 44 +- kernel/trunk/core/taskman.inc | 2 +- kernel/trunk/detect/biosdisk.inc | 38 +- kernel/trunk/docs/usbapi.txt | 183 +++ kernel/trunk/drivers/fdo.inc | 439 +++++++ kernel/trunk/drivers/imports.inc | 10 +- kernel/trunk/drivers/usbhid.asm | 696 ++++++++++ kernel/trunk/drivers/usbstor.asm | 1609 +++++++++++++++++++++++ kernel/trunk/kernel.asm | 2 + kernel/trunk/kernel32.inc | 3 + 25 files changed, 13119 insertions(+), 349 deletions(-) create mode 100644 kernel/trunk/bus/usb/ehci.inc create mode 100644 kernel/trunk/bus/usb/hccommon.inc create mode 100644 kernel/trunk/bus/usb/hub.inc create mode 100644 kernel/trunk/bus/usb/init.inc create mode 100644 kernel/trunk/bus/usb/memory.inc create mode 100644 kernel/trunk/bus/usb/ohci.inc create mode 100644 kernel/trunk/bus/usb/pipe.inc create mode 100644 kernel/trunk/bus/usb/protocol.inc create mode 100644 kernel/trunk/bus/usb/scheduler.inc create mode 100644 kernel/trunk/bus/usb/uhci.inc create mode 100644 kernel/trunk/docs/usbapi.txt create mode 100644 kernel/trunk/drivers/fdo.inc create mode 100644 kernel/trunk/drivers/usbhid.asm create mode 100644 kernel/trunk/drivers/usbstor.asm diff --git a/data/eng/Makefile b/data/eng/Makefile index ae83058435..316dbc59f7 100644 --- a/data/eng/Makefile +++ b/data/eng/Makefile @@ -129,6 +129,8 @@ FASM_PROGRAMS:=\ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \ + drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \ + drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \ @@ -301,7 +303,7 @@ OTHER_FILES:=autorun.dat:AUTORUN.DAT \ # Generate skins list understandable by gnu make Makefile.skins: $(REPOSITORY)/skins/authors.txt $(REPOSITORY)/data/generate_makefile_skins.sh - cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ + cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ include Makefile.skins # Extra targets for the distribution kit and LiveCD image in the syntax of mkisofs @@ -423,38 +425,38 @@ $(BUILD_DIR)/kolibri.img: $(BUILD_DIR)/.dir \ $(BUILD_DIR)/boot_fat12.bin \ $(targets) # SYSXTREE - str=`date -u +"[auto-build %d %b %Y %R, r$(REV)]"`; \ - echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null - dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 - mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: - dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 - mmd -i $(BUILD_DIR)/kolibri.img ::3D - mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO - mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS - mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers - mmd -i $(BUILD_DIR)/kolibri.img ::FONTS - mmd -i $(BUILD_DIR)/kolibri.img ::GAMES - mmd -i $(BUILD_DIR)/kolibri.img ::LIB - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF - mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK - $(mcopy_all_items) + str=`date -u +"[auto-build %d %b %Y %R, r$(REV)]"`; \ + echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null + dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 + mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: + dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 + mmd -i $(BUILD_DIR)/kolibri.img ::3D + mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO + mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS + mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers + mmd -i $(BUILD_DIR)/kolibri.img ::FONTS + mmd -i $(BUILD_DIR)/kolibri.img ::GAMES + mmd -i $(BUILD_DIR)/kolibri.img ::LIB + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF + mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK + $(mcopy_all_items) # The second goal: LiveCD image. $(BUILD_DIR)/kolibri.iso: $(BUILD_DIR)/kolibri.img $(mkisofs_extra_targets) - mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ - -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ - -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 + mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ + -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ + -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 # The third goal: distribution list. $(BUILD_DIR)/distr.lst: $(BUILD_DIR)/kolibri.img $(distribution_extra_targets) - rm -rf distribution_kit - $(call respace,$(make_distribution_dirs)) - ln -sr $(BUILD_DIR)/kolibri.img distribution_kit/kolibri.img - $(call respace,$(make_distribution_links)) - touch $(BUILD_DIR)/distr.lst + rm -rf distribution_kit + $(call respace,$(make_distribution_dirs)) + ln -sr $(BUILD_DIR)/kolibri.img distribution_kit/kolibri.img + $(call respace,$(make_distribution_links)) + touch $(BUILD_DIR)/distr.lst # Special targets to modify behaviour of make. .DELETE_ON_ERROR: @@ -462,16 +464,16 @@ $(BUILD_DIR)/distr.lst: $(BUILD_DIR)/kolibri.img $(distribution_extra_targets) # The floppy bootsector. $(BUILD_DIR)/boot_fat12.bin: $(KERNEL)/bootloader/boot_fat12.asm $(KERNEL)/bootloader/floppy1440.inc - fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin + fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin $(BUILD_DIR)/.dir 3d/.dir demos/.dir develop/.dir develop/info/.dir drivers/.dir fonts/.dir \ games/.dir lib/.dir media/.dir network/.dir allskins/.dir distr_data/.dir .deps/.dir: - mkdir -p $(dir $@) - touch $@ + mkdir -p $(dir $@) + touch $@ develop/info/.dir: develop/.dir File\ Managers/.dir: - mkdir -p "File Managers" - touch "File Managers/.dir" + mkdir -p "File Managers" + touch "File Managers/.dir" # extra dependency for mtldr_install.exe distr_data/mtldr_install.exe: mtldr_for_installer @@ -488,7 +490,7 @@ include Makefile.copy # Special rules for copying sysfuncs.txt - it isn't directly included in the image. docpack: $(DOCDIR)SYSFUNCS.TXT $(DOCDIR)SYSFUNCS.TXT: $(KERNEL)/docs/sysfuncs.txt - cp $(KERNEL)/docs/sysfuncs.txt $(DOCDIR)SYSFUNCS.TXT + cp $(KERNEL)/docs/sysfuncs.txt $(DOCDIR)SYSFUNCS.TXT # Similar for C--. include Makefile.cmm @@ -501,21 +503,21 @@ include Makefile.msvc # Rules for table table: .obj.table/table.exe - $(msvc_final) + $(msvc_final) TABLE_OBJECTS:=.obj.table/calc.obj .obj.table/func.obj .obj.table/hello.obj \ .obj.table/KosFile.obj .obj.table/kosSyst.obj .obj.table/math2.obj \ .obj.table/mcsmemm.obj .obj.table/parser.obj TABLE_H_FILES:=$(wildcard $(PROGS)/other/table/*.h) .obj.table/table.exe: $(TABLE_OBJECTS) - $(msvc_link) + $(msvc_link) $(TABLE_OBJECTS): .obj.table/%.obj: $(PROGS)/other/table/%.cpp $(TABLE_H_FILES) Makefile.msvc | .obj.table - $(msvc_compile) + $(msvc_compile) .obj.table: - mkdir -p .obj.table + mkdir -p .obj.table # Rules for graph graph: .obj.graph/graph.exe - $(msvc_final) + $(msvc_final) GRAPH_CPP_OBJECTS:=.obj.graph/func.obj .obj.graph/hello.obj .obj.graph/kolibri.obj \ .obj.graph/KosFile.obj .obj.graph/kosSyst.obj .obj.graph/math2.obj \ .obj.graph/mcsmemm.obj .obj.graph/parser.obj @@ -523,34 +525,34 @@ GRAPH_C_OBJECTS:=.obj.graph/string.obj GRAPH_H_FILES:=$(wildcard $(PROGS)/other/graph/*.h) GRAPH_FASM_OBJECTS:=.obj.graph/memcpy.obj .obj.graph/memset.obj .obj.graph/graph.exe: $(GRAPH_CPP_OBJECTS) $(GRAPH_C_OBJECTS) $(GRAPH_FASM_OBJECTS) - $(msvc_link) + $(msvc_link) $(GRAPH_CPP_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.cpp $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_C_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.c $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_FASM_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.asm Makefile.msvc | .obj.graph - fasm $< $@ + fasm $< $@ .obj.graph: - mkdir -p .obj.graph + mkdir -p .obj.graph # Rules for kosilka games/kosilka: .obj.kosilka/kosilka.exe - $(msvc_final) + $(msvc_final) KOSILKA_OBJECTS:=.obj.kosilka/kosilka.obj .obj.kosilka/KosFile.obj .obj.kosilka/kosSyst.obj .obj.kosilka/mcsmemm.obj KOSILKA_H_FILES:=$(PROGS)/games/kosilka/*.h .obj.kosilka/kosilka.exe: $(KOSILKA_OBJECTS) - $(msvc_link) + $(msvc_link) $(KOSILKA_OBJECTS): .obj.kosilka/%.obj: $(PROGS)/games/kosilka/%.cpp $(KOSILKA_H_FILES) Makefile.msvc | .obj.kosilka - $(msvc_compile) + $(msvc_compile) .obj.kosilka: - mkdir -p .obj.kosilka + mkdir -p .obj.kosilka include Makefile.gcc # Rules for shell shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o \ $(PROGS)/system/shell/kolibri.ld - $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) + $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) .obj.shell/shell.o: $(PROGS)/system/shell/shell.c \ $(PROGS)/system/shell/all.h \ $(PROGS)/system/shell/system/*.h \ @@ -558,77 +560,77 @@ shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/std $(PROGS)/system/shell/modules/*.c \ $(PROGS)/system/shell/locale/rus/globals.h \ Makefile.gcc | .obj.shell - $(gcc_compile) + $(gcc_compile) .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o: .obj.shell/%.o: \ $(PROGS)/system/shell/system/%.c $(PROGS)/system/shell/system/*.h \ Makefile.gcc | .obj.shell - $(gcc_compile) - win32-gcc -c -Os -o $@ $< + $(gcc_compile) + win32-gcc -c -Os -o $@ $< .obj.shell/start.o: $(PROGS)/system/shell/start.asm | .obj.shell - fasm $< $@ + fasm $< $@ .obj.shell: - mkdir -p .obj.shell + mkdir -p .obj.shell # Rules for e80 E80DIR=$(PROGS)/emulator/e80/trunk e80: .obj.e80/start.o .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o .obj.e80/z80.o .obj.e80/e80.o - $(call gcc_link,$(E80DIR)/kolibri.ld) + $(call gcc_link,$(E80DIR)/kolibri.ld) .obj.e80/e80.o: $(E80DIR)/e80.c $(E80DIR)/48.h \ $(E80DIR)/system/*.h $(E80DIR)/system/msgbox.c \ $(E80DIR)/z80/z80.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o: .obj.e80/%.o: \ $(E80DIR)/system/%.c $(E80DIR)/system/*.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/z80.o: $(E80DIR)/z80/z80.c $(E80DIR)/z80/* - $(gcc_compile) + $(gcc_compile) .obj.e80/start.o: $(E80DIR)/asm_code.asm | .obj.e80 - fasm $< $@ + fasm $< $@ .obj.e80: - mkdir -p .obj.e80 + mkdir -p .obj.e80 # Rules for sdk/sound, used by media/ac97snd SOUNDDIR=$(PROGS)/develop/sdk/trunk/sound/src SOUND_OBJECTS:=$(patsubst $(SOUNDDIR)/%.asm,.sdk/%.obj,$(wildcard $(SOUNDDIR)/*.asm)) SOUND_INC_FILES:=$(wildcard $(SOUNDDIR)/*.inc) .sdk/sound.lib: $(SOUND_OBJECTS) - win32-link /lib /out:$@ $^ + win32-link /lib /out:$@ $^ $(SOUND_OBJECTS): .sdk/%.obj: $(SOUNDDIR)/%.asm $(SOUND_INC_FILES) | .sdk - fasm $< $@ + fasm $< $@ .sdk: - mkdir -p .sdk + mkdir -p .sdk # Rules for media/ac97snd AC97DIR=$(PROGS)/media/ac97snd media/ac97snd: .obj.ac97snd/ac97snd.exe - $(msvc_final) + $(msvc_final) .obj.ac97snd/ac97snd.exe: .obj.ac97snd/ac97wav.obj .obj.ac97snd/crt.obj .obj.ac97snd/k_lib.obj \ - .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj - $(msvc_link) + .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj + $(msvc_link) .obj.ac97snd/ac97wav.obj: $(AC97DIR)/ac97snd/ac97wav.c \ - $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ - $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ + $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd + $(msvc_compile) .obj.ac97snd/crt.obj: $(AC97DIR)/ac97snd/crt.c $(AC97DIR)/ac97snd/crt.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/k_lib.obj: $(AC97DIR)/ac97snd/k_lib.asm $(AC97DIR)/ac97snd/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd/ufmod.obj: $(AC97DIR)/ufmod-config.asm | .obj.ac97snd - fasm $< $@ -s .deps/ac97snd-ufmod.fas - prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ - perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ - -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po + fasm $< $@ -s .deps/ac97snd-ufmod.fas + prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ + perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ + -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po -include .deps/ac97snd-ufmod.Po AC97SND_MPG_C_FILES:=$(wildcard $(AC97DIR)/mpg/*.c) AC97SND_MPG_H_FILES:=$(wildcard $(AC97DIR)/mpg/*.h) AC97SND_MPG_C_OBJECTS:=$(patsubst $(AC97DIR)/mpg/%.c,.obj.ac97snd/%.o,$(AC97SND_MPG_C_FILES)) .obj.ac97snd/mpg.lib: $(AC97SND_MPG_C_OBJECTS) .obj.ac97snd/pow.obj - win32-link /lib /ltcg /out:$@ $^ + win32-link /lib /ltcg /out:$@ $^ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_FILES) Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/pow.obj: $(AC97DIR)/mpg/pow.asm $(AC97DIR)/mpg/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd: - mkdir -p .obj.ac97snd + mkdir -p .obj.ac97snd # Rules for atikms.dll # Use Makefile from $(REPOSITORY)/drivers/ddk and $(REPOSITORY)/drivers/video/drm/radeon @@ -637,16 +639,16 @@ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_F # Note that we are going to write in the directory shared # between all Makefiles, so we need locked operations. drivers/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll drivers/.dir - kpack --nologo $< $@ + kpack --nologo $< $@ $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/Makefile.lto - flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto + flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto $(REPOSITORY)/drivers/ddk/libddk.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a $(REPOSITORY)/drivers/ddk/libcore.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a # dependencies $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: \ $(REPOSITORY)/drivers/video/drm/radeon/atikms.lds \ diff --git a/data/it/Makefile b/data/it/Makefile index 185afdc14d..abad34afc8 100644 --- a/data/it/Makefile +++ b/data/it/Makefile @@ -129,6 +129,8 @@ FASM_PROGRAMS:=\ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \ + drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \ + drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \ @@ -301,7 +303,7 @@ OTHER_FILES:=autorun.dat:AUTORUN.DAT \ # Generate skins list understandable by gnu make Makefile.skins: $(REPOSITORY)/skins/authors.txt $(REPOSITORY)/data/generate_makefile_skins.sh - cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ + cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ include Makefile.skins # Extra targets for LiveCD image in the syntax of mkisofs @@ -403,30 +405,30 @@ $(BUILD_DIR)/kolibri.img: $(BUILD_DIR)/.dir \ Makefile \ $(BUILD_DIR)/boot_fat12.bin \ $(targets) - str=`date -u +"[auto-build %d %b %Y %R, r$(REV)]"`; \ - echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null - dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 - mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: - dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 - mmd -i $(BUILD_DIR)/kolibri.img ::3D - mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO - mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS - mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers - mmd -i $(BUILD_DIR)/kolibri.img ::FONTS - mmd -i $(BUILD_DIR)/kolibri.img ::GAMES - mmd -i $(BUILD_DIR)/kolibri.img ::LIB - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF - mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK - $(mcopy_all_items) + str=`date -u +"[auto-build %d %b %Y %R, r$(REV)]"`; \ + echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null + dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 + mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: + dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 + mmd -i $(BUILD_DIR)/kolibri.img ::3D + mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO + mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS + mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers + mmd -i $(BUILD_DIR)/kolibri.img ::FONTS + mmd -i $(BUILD_DIR)/kolibri.img ::GAMES + mmd -i $(BUILD_DIR)/kolibri.img ::LIB + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF + mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK + $(mcopy_all_items) # The second goal: LiveCD image. $(BUILD_DIR)/kolibri.iso: $(BUILD_DIR)/kolibri.img $(mkisofs_extra_targets) - mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ - -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ - -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 + mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ + -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ + -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 # Special targets to modify behaviour of make. .DELETE_ON_ERROR: @@ -434,16 +436,16 @@ $(BUILD_DIR)/kolibri.iso: $(BUILD_DIR)/kolibri.img $(mkisofs_extra_targets) # The floppy bootsector. $(BUILD_DIR)/boot_fat12.bin: $(KERNEL)/bootloader/boot_fat12.asm $(KERNEL)/bootloader/floppy1440.inc - fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin + fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin $(BUILD_DIR)/.dir 3d/.dir demos/.dir develop/.dir develop/info/.dir drivers/.dir fonts/.dir \ games/.dir lib/.dir media/.dir network/.dir allskins/.dir distr_data/.dir .deps/.dir: - mkdir -p $(dir $@) - touch $@ + mkdir -p $(dir $@) + touch $@ develop/info/.dir: develop/.dir File\ Managers/.dir: - mkdir -p "File Managers" - touch "File Managers/.dir" + mkdir -p "File Managers" + touch "File Managers/.dir" # extra dependency for mtldr_install.exe distr_data/mtldr_install.exe: mtldr_for_installer @@ -460,7 +462,7 @@ include Makefile.copy # Special rules for copying sysfuncs.txt - it isn't directly included in the image. docpack: $(DOCDIR)SYSFUNCS.TXT $(DOCDIR)SYSFUNCS.TXT: $(KERNEL)/docs/sysfuncs.txt - cp $(KERNEL)/docs/sysfuncs.txt $(DOCDIR)SYSFUNCS.TXT + cp $(KERNEL)/docs/sysfuncs.txt $(DOCDIR)SYSFUNCS.TXT # Similar for C--. include Makefile.cmm @@ -473,21 +475,21 @@ include Makefile.msvc # Rules for table table: .obj.table/table.exe - $(msvc_final) + $(msvc_final) TABLE_OBJECTS:=.obj.table/calc.obj .obj.table/func.obj .obj.table/hello.obj \ .obj.table/KosFile.obj .obj.table/kosSyst.obj .obj.table/math2.obj \ .obj.table/mcsmemm.obj .obj.table/parser.obj TABLE_H_FILES:=$(wildcard $(PROGS)/other/table/*.h) .obj.table/table.exe: $(TABLE_OBJECTS) - $(msvc_link) + $(msvc_link) $(TABLE_OBJECTS): .obj.table/%.obj: $(PROGS)/other/table/%.cpp $(TABLE_H_FILES) Makefile.msvc | .obj.table - $(msvc_compile) + $(msvc_compile) .obj.table: - mkdir -p .obj.table + mkdir -p .obj.table # Rules for graph graph: .obj.graph/graph.exe - $(msvc_final) + $(msvc_final) GRAPH_CPP_OBJECTS:=.obj.graph/func.obj .obj.graph/hello.obj .obj.graph/kolibri.obj \ .obj.graph/KosFile.obj .obj.graph/kosSyst.obj .obj.graph/math2.obj \ .obj.graph/mcsmemm.obj .obj.graph/parser.obj @@ -495,34 +497,34 @@ GRAPH_C_OBJECTS:=.obj.graph/string.obj GRAPH_H_FILES:=$(wildcard $(PROGS)/other/graph/*.h) GRAPH_FASM_OBJECTS:=.obj.graph/memcpy.obj .obj.graph/memset.obj .obj.graph/graph.exe: $(GRAPH_CPP_OBJECTS) $(GRAPH_C_OBJECTS) $(GRAPH_FASM_OBJECTS) - $(msvc_link) + $(msvc_link) $(GRAPH_CPP_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.cpp $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_C_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.c $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_FASM_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.asm Makefile.msvc | .obj.graph - fasm $< $@ + fasm $< $@ .obj.graph: - mkdir -p .obj.graph + mkdir -p .obj.graph # Rules for kosilka games/kosilka: .obj.kosilka/kosilka.exe - $(msvc_final) + $(msvc_final) KOSILKA_OBJECTS:=.obj.kosilka/kosilka.obj .obj.kosilka/KosFile.obj .obj.kosilka/kosSyst.obj .obj.kosilka/mcsmemm.obj KOSILKA_H_FILES:=$(PROGS)/games/kosilka/*.h .obj.kosilka/kosilka.exe: $(KOSILKA_OBJECTS) - $(msvc_link) + $(msvc_link) $(KOSILKA_OBJECTS): .obj.kosilka/%.obj: $(PROGS)/games/kosilka/%.cpp $(KOSILKA_H_FILES) Makefile.msvc | .obj.kosilka - $(msvc_compile) + $(msvc_compile) .obj.kosilka: - mkdir -p .obj.kosilka + mkdir -p .obj.kosilka include Makefile.gcc # Rules for shell shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o \ $(PROGS)/system/shell/kolibri.ld - $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) + $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) .obj.shell/shell.o: $(PROGS)/system/shell/shell.c \ $(PROGS)/system/shell/all.h \ $(PROGS)/system/shell/system/*.h \ @@ -530,77 +532,77 @@ shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/std $(PROGS)/system/shell/modules/*.c \ $(PROGS)/system/shell/locale/rus/globals.h \ Makefile.gcc | .obj.shell - $(gcc_compile) + $(gcc_compile) .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o: .obj.shell/%.o: \ $(PROGS)/system/shell/system/%.c $(PROGS)/system/shell/system/*.h \ Makefile.gcc | .obj.shell - $(gcc_compile) - win32-gcc -c -Os -o $@ $< + $(gcc_compile) + win32-gcc -c -Os -o $@ $< .obj.shell/start.o: $(PROGS)/system/shell/start.asm | .obj.shell - fasm $< $@ + fasm $< $@ .obj.shell: - mkdir -p .obj.shell + mkdir -p .obj.shell # Rules for e80 E80DIR=$(PROGS)/emulator/e80/trunk e80: .obj.e80/start.o .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o .obj.e80/z80.o .obj.e80/e80.o - $(call gcc_link,$(E80DIR)/kolibri.ld) + $(call gcc_link,$(E80DIR)/kolibri.ld) .obj.e80/e80.o: $(E80DIR)/e80.c $(E80DIR)/48.h \ $(E80DIR)/system/*.h $(E80DIR)/system/msgbox.c \ $(E80DIR)/z80/z80.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o: .obj.e80/%.o: \ $(E80DIR)/system/%.c $(E80DIR)/system/*.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/z80.o: $(E80DIR)/z80/z80.c $(E80DIR)/z80/* - $(gcc_compile) + $(gcc_compile) .obj.e80/start.o: $(E80DIR)/asm_code.asm | .obj.e80 - fasm $< $@ + fasm $< $@ .obj.e80: - mkdir -p .obj.e80 + mkdir -p .obj.e80 # Rules for sdk/sound, used by media/ac97snd SOUNDDIR=$(PROGS)/develop/sdk/trunk/sound/src SOUND_OBJECTS:=$(patsubst $(SOUNDDIR)/%.asm,.sdk/%.obj,$(wildcard $(SOUNDDIR)/*.asm)) SOUND_INC_FILES:=$(wildcard $(SOUNDDIR)/*.inc) .sdk/sound.lib: $(SOUND_OBJECTS) - win32-link /lib /out:$@ $^ + win32-link /lib /out:$@ $^ $(SOUND_OBJECTS): .sdk/%.obj: $(SOUNDDIR)/%.asm $(SOUND_INC_FILES) | .sdk - fasm $< $@ + fasm $< $@ .sdk: - mkdir -p .sdk + mkdir -p .sdk # Rules for media/ac97snd AC97DIR=$(PROGS)/media/ac97snd media/ac97snd: .obj.ac97snd/ac97snd.exe - $(msvc_final) + $(msvc_final) .obj.ac97snd/ac97snd.exe: .obj.ac97snd/ac97wav.obj .obj.ac97snd/crt.obj .obj.ac97snd/k_lib.obj \ - .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj - $(msvc_link) + .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj + $(msvc_link) .obj.ac97snd/ac97wav.obj: $(AC97DIR)/ac97snd/ac97wav.c \ - $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ - $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ + $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd + $(msvc_compile) .obj.ac97snd/crt.obj: $(AC97DIR)/ac97snd/crt.c $(AC97DIR)/ac97snd/crt.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/k_lib.obj: $(AC97DIR)/ac97snd/k_lib.asm $(AC97DIR)/ac97snd/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd/ufmod.obj: $(AC97DIR)/ufmod-config.asm | .obj.ac97snd - fasm $< $@ -s .deps/ac97snd-ufmod.fas - prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ - perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ - -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po + fasm $< $@ -s .deps/ac97snd-ufmod.fas + prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ + perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ + -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po -include .deps/ac97snd-ufmod.Po AC97SND_MPG_C_FILES:=$(wildcard $(AC97DIR)/mpg/*.c) AC97SND_MPG_H_FILES:=$(wildcard $(AC97DIR)/mpg/*.h) AC97SND_MPG_C_OBJECTS:=$(patsubst $(AC97DIR)/mpg/%.c,.obj.ac97snd/%.o,$(AC97SND_MPG_C_FILES)) .obj.ac97snd/mpg.lib: $(AC97SND_MPG_C_OBJECTS) .obj.ac97snd/pow.obj - win32-link /lib /ltcg /out:$@ $^ + win32-link /lib /ltcg /out:$@ $^ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_FILES) Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/pow.obj: $(AC97DIR)/mpg/pow.asm $(AC97DIR)/mpg/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd: - mkdir -p .obj.ac97snd + mkdir -p .obj.ac97snd # Rules for atikms.dll # Use Makefile from $(REPOSITORY)/drivers/ddk and $(REPOSITORY)/drivers/video/drm/radeon @@ -609,16 +611,16 @@ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_F # Note that we are going to write in the directory shared # between all Makefiles, so we need locked operations. drivers/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll drivers/.dir - kpack --nologo $< $@ + kpack --nologo $< $@ $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/Makefile.lto - flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto + flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto $(REPOSITORY)/drivers/ddk/libddk.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a $(REPOSITORY)/drivers/ddk/libcore.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a # dependencies $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: \ $(REPOSITORY)/drivers/video/drm/radeon/atikms.lds \ diff --git a/data/rus/Makefile b/data/rus/Makefile index e0b47d2043..adb5b3d038 100644 --- a/data/rus/Makefile +++ b/data/rus/Makefile @@ -130,6 +130,8 @@ FASM_PROGRAMS:=\ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \ + drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \ + drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \ @@ -298,7 +300,7 @@ OTHER_FILES:=autorun.dat:AUTORUN.DAT \ # Generate skins list understandable by gnu make Makefile.skins: $(REPOSITORY)/skins/authors.txt $(REPOSITORY)/data/generate_makefile_skins.sh - cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ + cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ include Makefile.skins # Extra targets for the distribution kit and LiveCD image in the syntax of mkisofs @@ -445,38 +447,38 @@ $(BUILD_DIR)/kolibri.img: $(BUILD_DIR)/.dir \ Makefile \ $(BUILD_DIR)/boot_fat12.bin \ $(targets) - str=`LANG=ru_RU.utf8 date -u +"[автосборка %d %b %Y %R, r$(REV)]"|iconv -f utf8 -t cp866`; \ - echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null - dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 - mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: - dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 - mmd -i $(BUILD_DIR)/kolibri.img ::3D - mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO - mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS - mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers - mmd -i $(BUILD_DIR)/kolibri.img ::FONTS - mmd -i $(BUILD_DIR)/kolibri.img ::GAMES - mmd -i $(BUILD_DIR)/kolibri.img ::LIB - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF - mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK - $(mcopy_all_items) + str=`LANG=ru_RU.utf8 date -u +"[автосборка %d %b %Y %R, r$(REV)]"|iconv -f utf8 -t cp866`; \ + echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null + dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 + mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: + dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 + mmd -i $(BUILD_DIR)/kolibri.img ::3D + mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO + mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS + mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers + mmd -i $(BUILD_DIR)/kolibri.img ::FONTS + mmd -i $(BUILD_DIR)/kolibri.img ::GAMES + mmd -i $(BUILD_DIR)/kolibri.img ::LIB + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF + mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK + $(mcopy_all_items) # The second goal: LiveCD image. $(BUILD_DIR)/kolibri.iso: $(BUILD_DIR)/kolibri.img $(mkisofs_extra_targets) - mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ - -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ - -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 + mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ + -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ + -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 # The third goal: distribution list. $(BUILD_DIR)/distr.lst: $(BUILD_DIR)/kolibri.img $(distribution_extra_targets) - rm -rf distribution_kit - $(call respace,$(make_distribution_dirs)) - ln -sr $(BUILD_DIR)/kolibri.img distribution_kit/kolibri.img - $(call respace,$(make_distribution_links)) - touch $(BUILD_DIR)/distr.lst + rm -rf distribution_kit + $(call respace,$(make_distribution_dirs)) + ln -sr $(BUILD_DIR)/kolibri.img distribution_kit/kolibri.img + $(call respace,$(make_distribution_links)) + touch $(BUILD_DIR)/distr.lst # Special targets to modify behaviour of make. .DELETE_ON_ERROR: @@ -484,16 +486,16 @@ $(BUILD_DIR)/distr.lst: $(BUILD_DIR)/kolibri.img $(distribution_extra_targets) # The floppy bootsector. $(BUILD_DIR)/boot_fat12.bin: $(KERNEL)/bootloader/boot_fat12.asm $(KERNEL)/bootloader/floppy1440.inc - fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin + fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin $(BUILD_DIR)/.dir 3d/.dir demos/.dir develop/.dir develop/info/.dir drivers/.dir fonts/.dir \ games/.dir lib/.dir media/.dir network/.dir allskins/.dir distr_data/.dir .deps/.dir: - mkdir -p $(dir $@) - touch $@ + mkdir -p $(dir $@) + touch $@ develop/info/.dir: develop/.dir File\ Managers/.dir: - mkdir -p "File Managers" - touch "File Managers/.dir" + mkdir -p "File Managers" + touch "File Managers/.dir" # extra dependency for mtldr_install.exe distr_data/mtldr_install.exe: mtldr_for_installer @@ -510,7 +512,7 @@ include Makefile.copy # Special rules for copying sysfuncr.txt - it isn't directly included in the image. docpack: $(DOCDIR)SYSFUNCR.TXT $(DOCDIR)SYSFUNCR.TXT: $(KERNEL)/docs/sysfuncr.txt - cp $(KERNEL)/docs/sysfuncr.txt $(DOCDIR)SYSFUNCR.TXT + cp $(KERNEL)/docs/sysfuncr.txt $(DOCDIR)SYSFUNCR.TXT # Similar for C--. include Makefile.cmm @@ -518,7 +520,7 @@ include Makefile.cmm # Recode some text files from native encoding aka cp866 to cp1251 define recode_meta_rule $(1): $(2) - iconv -f cp866 -t cp1251 "$$<" > "$$@" + iconv -f cp866 -t cp1251 "$$<" > "$$@" endef $(foreach f, $(RECODE_TEXT_FILES), $(eval $(call recode_meta_rule,$(fbinary),$(fimage)))) @@ -530,21 +532,21 @@ include Makefile.msvc # Rules for table table: .obj.table/table.exe - $(msvc_final) + $(msvc_final) TABLE_OBJECTS:=.obj.table/calc.obj .obj.table/func.obj .obj.table/hello.obj \ .obj.table/KosFile.obj .obj.table/kosSyst.obj .obj.table/math2.obj \ .obj.table/mcsmemm.obj .obj.table/parser.obj TABLE_H_FILES:=$(wildcard $(PROGS)/other/table/*.h) .obj.table/table.exe: $(TABLE_OBJECTS) - $(msvc_link) + $(msvc_link) $(TABLE_OBJECTS): .obj.table/%.obj: $(PROGS)/other/table/%.cpp $(TABLE_H_FILES) Makefile.msvc | .obj.table - $(msvc_compile) + $(msvc_compile) .obj.table: - mkdir -p .obj.table + mkdir -p .obj.table # Rules for graph graph: .obj.graph/graph.exe - $(msvc_final) + $(msvc_final) GRAPH_CPP_OBJECTS:=.obj.graph/func.obj .obj.graph/hello.obj .obj.graph/kolibri.obj \ .obj.graph/KosFile.obj .obj.graph/kosSyst.obj .obj.graph/math2.obj \ .obj.graph/mcsmemm.obj .obj.graph/parser.obj @@ -552,34 +554,34 @@ GRAPH_C_OBJECTS:=.obj.graph/string.obj GRAPH_H_FILES:=$(wildcard $(PROGS)/other/graph/*.h) GRAPH_FASM_OBJECTS:=.obj.graph/memcpy.obj .obj.graph/memset.obj .obj.graph/graph.exe: $(GRAPH_CPP_OBJECTS) $(GRAPH_C_OBJECTS) $(GRAPH_FASM_OBJECTS) - $(msvc_link) + $(msvc_link) $(GRAPH_CPP_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.cpp $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_C_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.c $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_FASM_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.asm Makefile.msvc | .obj.graph - fasm $< $@ + fasm $< $@ .obj.graph: - mkdir -p .obj.graph + mkdir -p .obj.graph # Rules for kosilka games/kosilka: .obj.kosilka/kosilka.exe - $(msvc_final) + $(msvc_final) KOSILKA_OBJECTS:=.obj.kosilka/kosilka.obj .obj.kosilka/KosFile.obj .obj.kosilka/kosSyst.obj .obj.kosilka/mcsmemm.obj KOSILKA_H_FILES:=$(PROGS)/games/kosilka/*.h .obj.kosilka/kosilka.exe: $(KOSILKA_OBJECTS) - $(msvc_link) + $(msvc_link) $(KOSILKA_OBJECTS): .obj.kosilka/%.obj: $(PROGS)/games/kosilka/%.cpp $(KOSILKA_H_FILES) Makefile.msvc | .obj.kosilka - $(msvc_compile) + $(msvc_compile) .obj.kosilka: - mkdir -p .obj.kosilka + mkdir -p .obj.kosilka include Makefile.gcc # Rules for shell shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o \ $(PROGS)/system/shell/kolibri.ld - $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) + $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) .obj.shell/shell.o: $(PROGS)/system/shell/shell.c \ $(PROGS)/system/shell/all.h \ $(PROGS)/system/shell/system/*.h \ @@ -587,76 +589,76 @@ shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/std $(PROGS)/system/shell/modules/*.c \ $(PROGS)/system/shell/locale/rus/globals.h \ Makefile.gcc | .obj.shell - $(gcc_compile) + $(gcc_compile) .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o: .obj.shell/%.o: \ $(PROGS)/system/shell/system/%.c $(PROGS)/system/shell/system/*.h \ Makefile.gcc | .obj.shell - $(gcc_compile) + $(gcc_compile) .obj.shell/start.o: $(PROGS)/system/shell/start.asm | .obj.shell - fasm $< $@ + fasm $< $@ .obj.shell: - mkdir -p .obj.shell + mkdir -p .obj.shell # Rules for e80 E80DIR=$(PROGS)/emulator/e80/trunk e80: .obj.e80/start.o .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o .obj.e80/z80.o .obj.e80/e80.o - $(call gcc_link,$(E80DIR)/kolibri.ld) + $(call gcc_link,$(E80DIR)/kolibri.ld) .obj.e80/e80.o: $(E80DIR)/e80.c $(E80DIR)/48.h \ $(E80DIR)/system/*.h $(E80DIR)/system/msgbox.c \ $(E80DIR)/z80/z80.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o: .obj.e80/%.o: \ $(E80DIR)/system/%.c $(E80DIR)/system/*.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/z80.o: $(E80DIR)/z80/z80.c $(E80DIR)/z80/* - $(gcc_compile) + $(gcc_compile) .obj.e80/start.o: $(E80DIR)/asm_code.asm | .obj.e80 - fasm $< $@ + fasm $< $@ .obj.e80: - mkdir -p .obj.e80 + mkdir -p .obj.e80 # Rules for sdk/sound, used by media/ac97snd SOUNDDIR=$(PROGS)/develop/sdk/trunk/sound/src SOUND_OBJECTS:=$(patsubst $(SOUNDDIR)/%.asm,.sdk/%.obj,$(wildcard $(SOUNDDIR)/*.asm)) SOUND_INC_FILES:=$(wildcard $(SOUNDDIR)/*.inc) .sdk/sound.lib: $(SOUND_OBJECTS) - win32-link /lib /out:$@ $^ + win32-link /lib /out:$@ $^ $(SOUND_OBJECTS): .sdk/%.obj: $(SOUNDDIR)/%.asm $(SOUND_INC_FILES) | .sdk - fasm $< $@ + fasm $< $@ .sdk: - mkdir -p .sdk + mkdir -p .sdk # Rules for media/ac97snd AC97DIR=$(PROGS)/media/ac97snd media/ac97snd: .obj.ac97snd/ac97snd.exe - $(msvc_final) + $(msvc_final) .obj.ac97snd/ac97snd.exe: .obj.ac97snd/ac97wav.obj .obj.ac97snd/crt.obj .obj.ac97snd/k_lib.obj \ - .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj - $(msvc_link) + .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj + $(msvc_link) .obj.ac97snd/ac97wav.obj: $(AC97DIR)/ac97snd/ac97wav.c \ - $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ - $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ + $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd + $(msvc_compile) .obj.ac97snd/crt.obj: $(AC97DIR)/ac97snd/crt.c $(AC97DIR)/ac97snd/crt.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/k_lib.obj: $(AC97DIR)/ac97snd/k_lib.asm $(AC97DIR)/ac97snd/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd/ufmod.obj: $(AC97DIR)/ufmod-config.asm | .obj.ac97snd - fasm $< $@ -s .deps/ac97snd-ufmod.fas - prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ - perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ - -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po + fasm $< $@ -s .deps/ac97snd-ufmod.fas + prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ + perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ + -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po -include .deps/ac97snd-ufmod.Po AC97SND_MPG_C_FILES:=$(wildcard $(AC97DIR)/mpg/*.c) AC97SND_MPG_H_FILES:=$(wildcard $(AC97DIR)/mpg/*.h) AC97SND_MPG_C_OBJECTS:=$(patsubst $(AC97DIR)/mpg/%.c,.obj.ac97snd/%.o,$(AC97SND_MPG_C_FILES)) .obj.ac97snd/mpg.lib: $(AC97SND_MPG_C_OBJECTS) .obj.ac97snd/pow.obj - win32-link /lib /ltcg /out:$@ $^ + win32-link /lib /ltcg /out:$@ $^ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_FILES) Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/pow.obj: $(AC97DIR)/mpg/pow.asm $(AC97DIR)/mpg/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd: - mkdir -p .obj.ac97snd + mkdir -p .obj.ac97snd # Rules for atikms.dll # Use Makefile from $(REPOSITORY)/drivers/ddk and $(REPOSITORY)/drivers/video/drm/radeon @@ -665,16 +667,16 @@ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_F # Note that we are going to write in the directory shared # between all Makefiles, so we need locked operations. drivers/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll drivers/.dir - kpack --nologo $< $@ + kpack --nologo $< $@ $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/Makefile.lto - flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto + flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto $(REPOSITORY)/drivers/ddk/libddk.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a $(REPOSITORY)/drivers/ddk/libcore.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a # dependencies $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: \ $(REPOSITORY)/drivers/video/drm/radeon/atikms.lds \ diff --git a/data/sp/Makefile b/data/sp/Makefile index ae83058435..316dbc59f7 100644 --- a/data/sp/Makefile +++ b/data/sp/Makefile @@ -129,6 +129,8 @@ FASM_PROGRAMS:=\ drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \ drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \ drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \ + drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \ + drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \ drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \ drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \ File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \ @@ -301,7 +303,7 @@ OTHER_FILES:=autorun.dat:AUTORUN.DAT \ # Generate skins list understandable by gnu make Makefile.skins: $(REPOSITORY)/skins/authors.txt $(REPOSITORY)/data/generate_makefile_skins.sh - cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ + cut -f1 $< | $(SHELL) $(REPOSITORY)/data/generate_makefile_skins.sh > $@ include Makefile.skins # Extra targets for the distribution kit and LiveCD image in the syntax of mkisofs @@ -423,38 +425,38 @@ $(BUILD_DIR)/kolibri.img: $(BUILD_DIR)/.dir \ $(BUILD_DIR)/boot_fat12.bin \ $(targets) # SYSXTREE - str=`date -u +"[auto-build %d %b %Y %R, r$(REV)]"`; \ - echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null - dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 - mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: - dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 - mmd -i $(BUILD_DIR)/kolibri.img ::3D - mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP - mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO - mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS - mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers - mmd -i $(BUILD_DIR)/kolibri.img ::FONTS - mmd -i $(BUILD_DIR)/kolibri.img ::GAMES - mmd -i $(BUILD_DIR)/kolibri.img ::LIB - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA - mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF - mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK - $(mcopy_all_items) + str=`date -u +"[auto-build %d %b %Y %R, r$(REV)]"`; \ + echo -n $$str|dd of=kernel.mnt bs=1 seek=`expr 279 - length "$$str"` conv=notrunc 2>/dev/null + dd if=/dev/zero of=$(BUILD_DIR)/kolibri.img count=2880 bs=512 2>&1 + mformat -f 1440 -i $(BUILD_DIR)/kolibri.img :: + dd if=$(BUILD_DIR)/boot_fat12.bin of=$(BUILD_DIR)/kolibri.img count=1 bs=512 conv=notrunc 2>&1 + mmd -i $(BUILD_DIR)/kolibri.img ::3D + mmd -i $(BUILD_DIR)/kolibri.img ::DEMOS + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP + mmd -i $(BUILD_DIR)/kolibri.img ::DEVELOP/INFO + mmd -i $(BUILD_DIR)/kolibri.img ::DRIVERS + mmd -i $(BUILD_DIR)/kolibri.img ::File\ Managers + mmd -i $(BUILD_DIR)/kolibri.img ::FONTS + mmd -i $(BUILD_DIR)/kolibri.img ::GAMES + mmd -i $(BUILD_DIR)/kolibri.img ::LIB + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA + mmd -i $(BUILD_DIR)/kolibri.img ::MEDIA/ImgF + mmd -i $(BUILD_DIR)/kolibri.img ::NETWORK + $(mcopy_all_items) # The second goal: LiveCD image. $(BUILD_DIR)/kolibri.iso: $(BUILD_DIR)/kolibri.img $(mkisofs_extra_targets) - mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ - -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ - -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 + mkisofs -U -J -pad -b kolibri.img -c boot.catalog -hide-joliet boot.catalog -graft-points \ + -A "KolibriOS AutoBuilder" -p "CleverMouse" -publisher "KolibriOS Team" -V "KolibriOS r$(REV)" -sysid "KOLIBRI" \ + -iso-level 3 -o $(BUILD_DIR)/kolibri.iso $(BUILD_DIR)/kolibri.img $(call respace,$(MKISOFS_EXTRA)) 2>&1 # The third goal: distribution list. $(BUILD_DIR)/distr.lst: $(BUILD_DIR)/kolibri.img $(distribution_extra_targets) - rm -rf distribution_kit - $(call respace,$(make_distribution_dirs)) - ln -sr $(BUILD_DIR)/kolibri.img distribution_kit/kolibri.img - $(call respace,$(make_distribution_links)) - touch $(BUILD_DIR)/distr.lst + rm -rf distribution_kit + $(call respace,$(make_distribution_dirs)) + ln -sr $(BUILD_DIR)/kolibri.img distribution_kit/kolibri.img + $(call respace,$(make_distribution_links)) + touch $(BUILD_DIR)/distr.lst # Special targets to modify behaviour of make. .DELETE_ON_ERROR: @@ -462,16 +464,16 @@ $(BUILD_DIR)/distr.lst: $(BUILD_DIR)/kolibri.img $(distribution_extra_targets) # The floppy bootsector. $(BUILD_DIR)/boot_fat12.bin: $(KERNEL)/bootloader/boot_fat12.asm $(KERNEL)/bootloader/floppy1440.inc - fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin + fasm $(KERNEL)/bootloader/boot_fat12.asm $(BUILD_DIR)/boot_fat12.bin $(BUILD_DIR)/.dir 3d/.dir demos/.dir develop/.dir develop/info/.dir drivers/.dir fonts/.dir \ games/.dir lib/.dir media/.dir network/.dir allskins/.dir distr_data/.dir .deps/.dir: - mkdir -p $(dir $@) - touch $@ + mkdir -p $(dir $@) + touch $@ develop/info/.dir: develop/.dir File\ Managers/.dir: - mkdir -p "File Managers" - touch "File Managers/.dir" + mkdir -p "File Managers" + touch "File Managers/.dir" # extra dependency for mtldr_install.exe distr_data/mtldr_install.exe: mtldr_for_installer @@ -488,7 +490,7 @@ include Makefile.copy # Special rules for copying sysfuncs.txt - it isn't directly included in the image. docpack: $(DOCDIR)SYSFUNCS.TXT $(DOCDIR)SYSFUNCS.TXT: $(KERNEL)/docs/sysfuncs.txt - cp $(KERNEL)/docs/sysfuncs.txt $(DOCDIR)SYSFUNCS.TXT + cp $(KERNEL)/docs/sysfuncs.txt $(DOCDIR)SYSFUNCS.TXT # Similar for C--. include Makefile.cmm @@ -501,21 +503,21 @@ include Makefile.msvc # Rules for table table: .obj.table/table.exe - $(msvc_final) + $(msvc_final) TABLE_OBJECTS:=.obj.table/calc.obj .obj.table/func.obj .obj.table/hello.obj \ .obj.table/KosFile.obj .obj.table/kosSyst.obj .obj.table/math2.obj \ .obj.table/mcsmemm.obj .obj.table/parser.obj TABLE_H_FILES:=$(wildcard $(PROGS)/other/table/*.h) .obj.table/table.exe: $(TABLE_OBJECTS) - $(msvc_link) + $(msvc_link) $(TABLE_OBJECTS): .obj.table/%.obj: $(PROGS)/other/table/%.cpp $(TABLE_H_FILES) Makefile.msvc | .obj.table - $(msvc_compile) + $(msvc_compile) .obj.table: - mkdir -p .obj.table + mkdir -p .obj.table # Rules for graph graph: .obj.graph/graph.exe - $(msvc_final) + $(msvc_final) GRAPH_CPP_OBJECTS:=.obj.graph/func.obj .obj.graph/hello.obj .obj.graph/kolibri.obj \ .obj.graph/KosFile.obj .obj.graph/kosSyst.obj .obj.graph/math2.obj \ .obj.graph/mcsmemm.obj .obj.graph/parser.obj @@ -523,34 +525,34 @@ GRAPH_C_OBJECTS:=.obj.graph/string.obj GRAPH_H_FILES:=$(wildcard $(PROGS)/other/graph/*.h) GRAPH_FASM_OBJECTS:=.obj.graph/memcpy.obj .obj.graph/memset.obj .obj.graph/graph.exe: $(GRAPH_CPP_OBJECTS) $(GRAPH_C_OBJECTS) $(GRAPH_FASM_OBJECTS) - $(msvc_link) + $(msvc_link) $(GRAPH_CPP_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.cpp $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_C_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.c $(GRAPH_H_FILES) Makefile.msvc | .obj.graph - $(msvc_compile) + $(msvc_compile) $(GRAPH_FASM_OBJECTS): .obj.graph/%.obj: $(PROGS)/other/graph/%.asm Makefile.msvc | .obj.graph - fasm $< $@ + fasm $< $@ .obj.graph: - mkdir -p .obj.graph + mkdir -p .obj.graph # Rules for kosilka games/kosilka: .obj.kosilka/kosilka.exe - $(msvc_final) + $(msvc_final) KOSILKA_OBJECTS:=.obj.kosilka/kosilka.obj .obj.kosilka/KosFile.obj .obj.kosilka/kosSyst.obj .obj.kosilka/mcsmemm.obj KOSILKA_H_FILES:=$(PROGS)/games/kosilka/*.h .obj.kosilka/kosilka.exe: $(KOSILKA_OBJECTS) - $(msvc_link) + $(msvc_link) $(KOSILKA_OBJECTS): .obj.kosilka/%.obj: $(PROGS)/games/kosilka/%.cpp $(KOSILKA_H_FILES) Makefile.msvc | .obj.kosilka - $(msvc_compile) + $(msvc_compile) .obj.kosilka: - mkdir -p .obj.kosilka + mkdir -p .obj.kosilka include Makefile.gcc # Rules for shell shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o \ $(PROGS)/system/shell/kolibri.ld - $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) + $(call gcc_link,$(PROGS)/system/shell/kolibri.ld) .obj.shell/shell.o: $(PROGS)/system/shell/shell.c \ $(PROGS)/system/shell/all.h \ $(PROGS)/system/shell/system/*.h \ @@ -558,77 +560,77 @@ shell: .obj.shell/start.o .obj.shell/shell.o .obj.shell/kolibri.o .obj.shell/std $(PROGS)/system/shell/modules/*.c \ $(PROGS)/system/shell/locale/rus/globals.h \ Makefile.gcc | .obj.shell - $(gcc_compile) + $(gcc_compile) .obj.shell/kolibri.o .obj.shell/stdlib.o .obj.shell/string.o .obj.shell/ctype.o: .obj.shell/%.o: \ $(PROGS)/system/shell/system/%.c $(PROGS)/system/shell/system/*.h \ Makefile.gcc | .obj.shell - $(gcc_compile) - win32-gcc -c -Os -o $@ $< + $(gcc_compile) + win32-gcc -c -Os -o $@ $< .obj.shell/start.o: $(PROGS)/system/shell/start.asm | .obj.shell - fasm $< $@ + fasm $< $@ .obj.shell: - mkdir -p .obj.shell + mkdir -p .obj.shell # Rules for e80 E80DIR=$(PROGS)/emulator/e80/trunk e80: .obj.e80/start.o .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o .obj.e80/z80.o .obj.e80/e80.o - $(call gcc_link,$(E80DIR)/kolibri.ld) + $(call gcc_link,$(E80DIR)/kolibri.ld) .obj.e80/e80.o: $(E80DIR)/e80.c $(E80DIR)/48.h \ $(E80DIR)/system/*.h $(E80DIR)/system/msgbox.c \ $(E80DIR)/z80/z80.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/kolibri.o .obj.e80/stdlib.o .obj.e80/string.o: .obj.e80/%.o: \ $(E80DIR)/system/%.c $(E80DIR)/system/*.h Makefile.gcc | .obj.e80 - $(gcc_compile) + $(gcc_compile) .obj.e80/z80.o: $(E80DIR)/z80/z80.c $(E80DIR)/z80/* - $(gcc_compile) + $(gcc_compile) .obj.e80/start.o: $(E80DIR)/asm_code.asm | .obj.e80 - fasm $< $@ + fasm $< $@ .obj.e80: - mkdir -p .obj.e80 + mkdir -p .obj.e80 # Rules for sdk/sound, used by media/ac97snd SOUNDDIR=$(PROGS)/develop/sdk/trunk/sound/src SOUND_OBJECTS:=$(patsubst $(SOUNDDIR)/%.asm,.sdk/%.obj,$(wildcard $(SOUNDDIR)/*.asm)) SOUND_INC_FILES:=$(wildcard $(SOUNDDIR)/*.inc) .sdk/sound.lib: $(SOUND_OBJECTS) - win32-link /lib /out:$@ $^ + win32-link /lib /out:$@ $^ $(SOUND_OBJECTS): .sdk/%.obj: $(SOUNDDIR)/%.asm $(SOUND_INC_FILES) | .sdk - fasm $< $@ + fasm $< $@ .sdk: - mkdir -p .sdk + mkdir -p .sdk # Rules for media/ac97snd AC97DIR=$(PROGS)/media/ac97snd media/ac97snd: .obj.ac97snd/ac97snd.exe - $(msvc_final) + $(msvc_final) .obj.ac97snd/ac97snd.exe: .obj.ac97snd/ac97wav.obj .obj.ac97snd/crt.obj .obj.ac97snd/k_lib.obj \ - .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj - $(msvc_link) + .obj.ac97snd/mpg.lib .sdk/sound.lib .obj.ac97snd/ufmod.obj + $(msvc_link) .obj.ac97snd/ac97wav.obj: $(AC97DIR)/ac97snd/ac97wav.c \ - $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ - $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(AC97DIR)/kolibri.h $(AC97DIR)/ac97snd/ac97wav.h $(AC97DIR)/mpg/mpg123.h \ + $(AC97DIR)/sound.h $(AC97DIR)/ufmod-codec.h Makefile.msvc | .obj.ac97snd + $(msvc_compile) .obj.ac97snd/crt.obj: $(AC97DIR)/ac97snd/crt.c $(AC97DIR)/ac97snd/crt.h Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/k_lib.obj: $(AC97DIR)/ac97snd/k_lib.asm $(AC97DIR)/ac97snd/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd/ufmod.obj: $(AC97DIR)/ufmod-config.asm | .obj.ac97snd - fasm $< $@ -s .deps/ac97snd-ufmod.fas - prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ - perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ - -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po + fasm $< $@ -s .deps/ac97snd-ufmod.fas + prepsrc .deps/ac97snd-ufmod.fas /dev/stdout | \ + perl -n -e 's|\\|/|g;s| |\\ |g;push @a,$$1 if/^;include\\ \x27(.*?)\x27/;' \ + -e 'END{$$a=join " \\\n ",@a;print "$@: $$a\n$$a:\n"}' > .deps/ac97snd-ufmod.Po -include .deps/ac97snd-ufmod.Po AC97SND_MPG_C_FILES:=$(wildcard $(AC97DIR)/mpg/*.c) AC97SND_MPG_H_FILES:=$(wildcard $(AC97DIR)/mpg/*.h) AC97SND_MPG_C_OBJECTS:=$(patsubst $(AC97DIR)/mpg/%.c,.obj.ac97snd/%.o,$(AC97SND_MPG_C_FILES)) .obj.ac97snd/mpg.lib: $(AC97SND_MPG_C_OBJECTS) .obj.ac97snd/pow.obj - win32-link /lib /ltcg /out:$@ $^ + win32-link /lib /ltcg /out:$@ $^ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_FILES) Makefile.msvc | .obj.ac97snd - $(msvc_compile) + $(msvc_compile) .obj.ac97snd/pow.obj: $(AC97DIR)/mpg/pow.asm $(AC97DIR)/mpg/proc32.inc | .obj.ac97snd - fasm $< $@ + fasm $< $@ .obj.ac97snd: - mkdir -p .obj.ac97snd + mkdir -p .obj.ac97snd # Rules for atikms.dll # Use Makefile from $(REPOSITORY)/drivers/ddk and $(REPOSITORY)/drivers/video/drm/radeon @@ -637,16 +639,16 @@ $(AC97SND_MPG_C_OBJECTS): .obj.ac97snd/%.o: $(AC97DIR)/mpg/%.c $(AC97SND_MPG_H_F # Note that we are going to write in the directory shared # between all Makefiles, so we need locked operations. drivers/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll drivers/.dir - kpack --nologo $< $@ + kpack --nologo $< $@ $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: $(REPOSITORY)/drivers/video/drm/radeon/Makefile.lto - flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto + flock $(REPOSITORY)/drivers/video/drm/radeon/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/video/drm/radeon -f Makefile.lto $(REPOSITORY)/drivers/ddk/libddk.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libddk.a $(REPOSITORY)/drivers/ddk/libcore.a: $(REPOSITORY)/drivers/ddk/Makefile - flock $(REPOSITORY)/drivers/ddk/.lock \ - $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a + flock $(REPOSITORY)/drivers/ddk/.lock \ + $(MAKE) CC=win32-gcc45 AS=win32-as LD=win32-ld AR=win32-ar FASM=fasm -C $(REPOSITORY)/drivers/ddk libcore.a # dependencies $(REPOSITORY)/drivers/video/drm/radeon/atikms.dll: \ $(REPOSITORY)/drivers/video/drm/radeon/atikms.lds \ diff --git a/kernel/trunk/bus/usb/ehci.inc b/kernel/trunk/bus/usb/ehci.inc new file mode 100644 index 0000000000..2576a36382 --- /dev/null +++ b/kernel/trunk/bus/usb/ehci.inc @@ -0,0 +1,1914 @@ +; Code for EHCI controllers. +; Note: it should be moved to an external driver, +; it was convenient to have this code compiled into the kernel during initial +; development, but there are no reasons to keep it here. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; EHCI register declarations. +; Part 1. Capability registers. +; Base is MMIO from the PCI space. +EhciCapLengthReg = 0 +EhciVersionReg = 2 +EhciStructParamsReg = 4 +EhciCapParamsReg = 8 +EhciPortRouteReg = 0Ch +; Part 2. Operational registers. +; Base is (base for part 1) + (value of EhciCapLengthReg). +EhciCommandReg = 0 +EhciStatusReg = 4 +EhciInterruptReg = 8 +EhciFrameIndexReg = 0Ch +EhciCtrlDataSegReg = 10h +EhciPeriodicListReg = 14h +EhciAsyncListReg = 18h +EhciConfigFlagReg = 40h +EhciPortsReg = 44h + +; Possible values of ehci_pipe.NextQH.Type bitfield. +EHCI_TYPE_ITD = 0 ; isochronous transfer descriptor +EHCI_TYPE_QH = 1 ; queue head +EHCI_TYPE_SITD = 2 ; split-transaction isochronous TD +EHCI_TYPE_FSTN = 3 ; frame span traversal node + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= + +; Hardware part of EHCI general transfer descriptor. +struct ehci_hardware_td +NextTD dd ? +; Bit 0 is Terminate bit, 1 = there is no next TD. +; Bits 1-4 must be zero. +; With masked 5 lower bits, this is the physical address of the next TD, if any. +AlternateNextTD dd ? +; Similar to NextTD, used if the transfer terminates with a short packet. +Token dd ? +; 1. Lower byte is Status field: +; bit 0 = ping state for USB2 endpoints, ERR handshake signal for USB1 endpoints +; bit 1 = split transaction state, meaningless for USB2 endpoints +; bit 2 = missed micro-frame +; bit 3 = transaction error +; bit 4 = babble detected +; bit 5 = data buffer error +; bit 6 = halted +; bit 7 = active +; 2. Next two bits (bits 8-9) are PID code, 0 = OUT, 1 = IN, 2 = SETUP. +; 3. Next two bits (bits 10-11) is ErrorCounter. Initialized as 3, decremented +; on each error; if it goes to zero, transaction is stopped. +; 4. Next 3 bits (bits 12-14) are CurrentPage field. +; 5. Next bit (bit 15) is InterruptOnComplete bit. +; 6. Next 15 bits (bits 16-30) are TransferLength field, +; number of bytes to transfer. +; 7. Upper bit (bit 31) is DataToggle bit. +BufferPointers rd 5 +; The buffer to be transferred can be spanned on up to 5 physical pages. +; The first item of this array is the physical address of the first byte in +; the buffer, other items are physical addresses of next pages. Lower 12 bits +; in other items must be set to zero; ehci_pipe.Overlay reuses some of them. +BufferPointersHigh rd 5 +; Upper dwords of BufferPointers for controllers with 64-bit memory access. +; Always zero. +ends + +; EHCI general transfer descriptor. +; * The structure describes transfers to be performed on Control, Bulk or +; Interrupt endpoints. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 52 bytes and corresponds to +; the Queue Element Transfer Descriptor from EHCI specification. +; * The hardware requires 32-bytes alignment of the hardware part, so +; the entire descriptor must be 32-bytes aligned. Since the allocator +; (usb_allocate_common) allocates memory sequentially from page start +; (aligned on 0x1000 bytes), size of the structure must be divisible by 32. +; * The hardware also requires that the hardware part must not cross page +; boundary; the allocator satisfies this automatically. +struct ehci_gtd ehci_hardware_td +Flags dd ? +; Copy of flags from the call to usb_*_transfer_async. +SoftwarePart rd sizeof.usb_gtd/4 +; Software part, common for all controllers. + rd 3 ; padding +ends + +if sizeof.ehci_gtd mod 32 +.err ehci_gtd must be 32-bytes aligned +end if + +; EHCI-specific part of a pipe descriptor. +; * This structure corresponds to the Queue Head from the EHCI specification. +; * The hardware requires 32-bytes alignment of the hardware part. +; Since the allocator (usb_allocate_common) allocates memory sequentially +; from page start (aligned on 0x1000 bytes), size of the structure must be +; divisible by 32. +; * The hardware requires also that the hardware part must not cross page +; boundary; the allocator satisfies this automatically. +struct ehci_pipe +NextQH dd ? +; 1. First bit (bit 0) is Terminate bit, 1 = there is no next QH. +; 2. Next two bits (bits 1-2) are Type field of the next QH, +; one of EHCI_TYPE_* constants. +; 3. Next two bits (bits 3-4) are reserved, must be zero. +; 4. With masked 5 lower bits, this is the physical address of the next object +; to be processed, usually next QH. +Token dd ? +; 1. Lower 7 bits are DeviceAddress field. This is the address of the +; target device on the USB bus. +; 2. Next bit (bit 7) is Inactivate-on-next-transaction bit. Can be nonzero +; only for interrupt/isochronous USB1 endpoints. +; 3. Next 4 bits (bits 8-11) are Endpoint field. This is the target endpoint +; number. +; 4. Next 2 bits (bits 12-13) are EndpointSpeed field, one of EHCI_SPEED_*. +; 5. Next bit (bit 14) is DataToggleControl bit, +; 0 = use DataToggle bit from QH, 1 = from TD. +; 6. Next bit (bit 15) is Head-of-reclamation-list. The head of Control list +; has 1 here, all other QHs have zero. +; 7. Next 11 bits (bits 16-26) are MaximumPacketLength field for the target +; endpoint. +; 8. Next bit (bit 27) is ControlEndpoint bit, must be 1 for USB1 control +; endpoints and 0 for all others. +; 9. Upper 4 bits (bits 28-31) are NakCountReload field. +; Zero for USB1 endpoints, zero for periodic endpoints. +; For control/bulk USB2 endpoints, the code sets it to 4, +; which is rather arbitrary. +Flags dd ? +; 1. Lower byte is S-mask, each bit corresponds to one microframe per frame; +; bit is set <=> enable transactions in this microframe. +; 2. Next byte is C-mask, each bit corresponds to one microframe per frame; +; bit is set <=> enable complete-split transactions in this microframe. +; Meaningful only for USB1 endpoints. +; 3. Next 14 bits give address of the target device as hub:port, bits 16-22 +; are the USB address of the hub, bits 23-29 are the port number. +; Meaningful only for USB1 endpoints. +; 4. Upper 2 bits define number of consequetive transactions per micro-frame +; which host is allowed to permit for this endpoint. +; For control/bulk endpoints, it must be 1. +; For periodic endpoints, the value is taken from the endpoint descriptor. +HeadTD dd ? +; The physical address of the first TD for this pipe. +; Lower 5 bits must be zero. +Overlay ehci_hardware_td ? +; Working area for the current TD, if there is any. +; When TD is retired, it is written to that TD and Overlay is loaded +; from the new TD, if any. +BaseList dd ? +; Pointer to head of the corresponding pipe list. +SoftwarePart rd sizeof.usb_pipe/4 +; Software part, common for all controllers. + rd 2 ; padding +ends + +if sizeof.ehci_pipe mod 32 +.err ehci_pipe must be 32-bytes aligned +end if + +; This structure describes the static head of every list of pipes. +; The hardware requires 32-bytes alignment of this structure. +; All instances of this structure are located sequentially in ehci_controller, +; ehci_controller is page-aligned, so it is sufficient to make this structure +; 32-bytes aligned and verify that the first instance is 32-bytes aligned +; inside ehci_controller. +; The hardware also requires that 44h bytes (size of 64-bit Queue Head +; Descriptor) starting at the beginning of this structure must not cross page +; boundary. If not, most hardware still behaves correctly (in fact, the last +; dword can have any value and this structure is never written), but on some +; hardware some things just break in mysterious ways. +struct ehci_static_ep +; Hardware fields are the same as in ehci_pipe. +; Only NextQH and Overlay.Token are actually used. +; NB: some emulators ignore Token.Halted bit (probably assuming that it is set +; only when device fails and emulation never fails) and always follow +; [Alternate]NextTD when they see that OverlayToken.Active bit is zero; +; so it is important to also set [Alternate]NextTD to 1. +NextQH dd ? +Token dd ? +Flags dd ? +HeadTD dd ? +NextTD dd ? +AlternateNextTD dd ? +OverlayToken dd ? +NextList dd ? +SoftwarePart rd sizeof.usb_static_ep/4 +Bandwidths rw 8 + dd ? +ends + +if sizeof.ehci_static_ep mod 32 +.err ehci_static_ep must be 32-bytes aligned +end if + +if ehci_static_ep.OverlayToken <> ehci_pipe.Overlay.Token +.err ehci_static_ep.OverlayToken misplaced +end if + +; EHCI-specific part of controller data. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 4096 bytes and corresponds to +; the Periodic Frame List from the EHCI specification. +; * The hardware requires page-alignment of the hardware part, so +; the entire descriptor must be page-aligned. +; This structure is allocated with kernel_alloc (see usb_init_controller), +; this gives page-aligned data. +; * The controller is described by both ehci_controller and usb_controller +; structures, for each controller there is one ehci_controller and one +; usb_controller structure. These structures are located sequentially +; in the memory: beginning from some page start, there is ehci_controller +; structure - this enforces hardware alignment requirements - and then +; usb_controller structure. +; * The code keeps pointer to usb_controller structure. The ehci_controller +; structure is addressed as [ptr + ehci_controller.field - sizeof.ehci_controller]. +struct ehci_controller +; ------------------------------ hardware fields ------------------------------ +FrameList rd 1024 +; Entry n corresponds to the head of the frame list to be executed in +; the frames n,n+1024,n+2048,n+3096,... +; The first bit of each entry is Terminate bit, 1 = the frame is empty. +; Bits 1-2 are Type field, one of EHCI_TYPE_* constants. +; Bits 3-4 must be zero. +; With masked 5 lower bits, the entry is a physical address of the first QH/TD +; to be executed. +; ------------------------------ software fields ------------------------------ +; Every list has the static head, which is an always halted QH. +; The following fields are static heads, one per list: +; 32+16+8+4+2+1 = 63 for Periodic lists, 1 for Control list and 1 for Bulk list. +IntEDs ehci_static_ep + rb 62 * sizeof.ehci_static_ep +; Beware. +; Two following strings ensure that 44h bytes at any static head +; do not cross page boundary. Without that, the code "works on my machine"... +; but fails on some hardware in seemingly unrelated ways. +; One hardware TD (without any software fields) fit in the rest of the page. +ehci_controller.ControlDelta = 2000h - (ehci_controller.IntEDs + 63 * sizeof.ehci_static_ep) +StopQueueTD ehci_hardware_td +; Used as AlternateNextTD for transfers when short packet is considered +; as an error; short packet must stop the queue in this case, not advance +; to the next transfer. + rb ehci_controller.ControlDelta - sizeof.ehci_hardware_td +; Padding for page-alignment. +ControlED ehci_static_ep +BulkED ehci_static_ep +MMIOBase1 dd ? +; Virtual address of memory-mapped area with part 1 of EHCI registers EhciXxxReg. +MMIOBase2 dd ? +; Pointer inside memory-mapped area MMIOBase1; points to part 2 of EHCI registers. +StructuralParams dd ? +; Copy of EhciStructParamsReg value. +CapabilityParams dd ? +; Copy of EhciCapParamsReg value. +DeferredActions dd ? +; Bitmask of events from EhciStatusReg which were observed by the IRQ handler +; and needs to be processed in the IRQ thread. +ends + +if ehci_controller.IntEDs mod 32 +.err Static endpoint descriptors must be 32-bytes aligned inside ehci_controller +end if + +; Description of #HCI-specific data and functions for +; controller-independent code. +; Implements the structure usb_hardware_func from hccommon.inc for EHCI. +iglobal +align 4 +ehci_hardware_func: + dd 'EHCI' + dd sizeof.ehci_controller + dd ehci_init + dd ehci_process_deferred + dd ehci_set_device_address + dd ehci_get_device_address + dd ehci_port_disable + dd ehci_new_port.reset + dd ehci_set_endpoint_packet_size + dd ehci_alloc_pipe + dd ehci_free_pipe + dd ehci_init_pipe + dd ehci_unlink_pipe + dd ehci_alloc_td + dd ehci_free_td + dd ehci_alloc_transfer + dd ehci_insert_transfer + dd ehci_new_device +endg + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +; Controller-specific initialization function. +; Called from usb_init_controller. Initializes the hardware and +; EHCI-specific parts of software structures. +; eax = pointer to ehci_controller to be initialized +; [ebp-4] = pcidevice +proc ehci_init +; inherit some variables from the parent (usb_init_controller) +.devfn equ ebp - 4 +.bus equ ebp - 3 +; 1. Store pointer to ehci_controller for further use. + push eax + mov edi, eax + mov esi, eax +; 2. Initialize ehci_controller.FrameList. +; Note that FrameList is located in the beginning of ehci_controller, +; so esi and edi now point to ehci_controller.FrameList. +; First 32 entries of FrameList contain physical addresses +; of first 32 Periodic static heads, further entries duplicate these. +; See the description of structures for full info. +; 2a. Get physical address of first static head. +; Note that 1) it is located in the beginning of a page +; and 2) first 32 static heads fit in the same page, +; so one call to get_phys_addr without correction of lower 12 bits +; is sufficient. +if (ehci_controller.IntEDs / 0x1000) <> ((ehci_controller.IntEDs + 32 * sizeof.ehci_static_ep) / 0x1000) +.err assertion failed +end if +if (ehci_controller.IntEDs mod 0x1000) <> 0 +.err assertion failed +end if + add eax, ehci_controller.IntEDs + call get_phys_addr +; 2b. Fill first 32 entries. + inc eax + inc eax ; set Type to EHCI_TYPE_QH + push 32 + pop ecx + mov edx, ecx +@@: + stosd + add eax, sizeof.ehci_static_ep + loop @b +; 2c. Fill the rest entries. + mov ecx, 1024 - 32 + rep movsd +; 3. Initialize static heads ehci_controller.*ED. +; Use the loop over groups: first group consists of first 32 Periodic +; descriptors, next group consists of next 16 Periodic descriptors, +; ..., last group consists of the last Periodic descriptor. +; 3a. Prepare for the loop. +; make esi point to the second group, other registers are already set. + add esi, 32*4 + 32*sizeof.ehci_static_ep +; 3b. Loop over groups. On every iteration: +; edx = size of group, edi = pointer to the current group, +; esi = pointer to the next group. +.init_static_eds: +; 3c. Get the size of next group. + shr edx, 1 +; 3d. Exit the loop if there is no next group. + jz .init_static_eds_done +; 3e. Initialize the first half of the current group. +; Advance edi to the second half. + push esi + call ehci_init_static_ep_group + pop esi +; 3f. Initialize the second half of the current group +; with the same values. +; Advance edi to the next group, esi/eax to the next of the next group. + call ehci_init_static_ep_group + jmp .init_static_eds +.init_static_eds_done: +; 3g. Initialize the last static head. + xor esi, esi + call ehci_init_static_endpoint +; While we are here, initialize StopQueueTD. +if (ehci_controller.StopQueueTD <> ehci_controller.IntEDs + 63 * sizeof.ehci_static_ep) +.err assertion failed +end if + inc [edi+ehci_hardware_td.NextTD] ; 0 -> 1 + inc [edi+ehci_hardware_td.AlternateNextTD] ; 0 -> 1 +; leave other fields as zero, including Active bit +; 3i. Initialize the head of Control list. + add edi, ehci_controller.ControlDelta + lea esi, [edi+sizeof.ehci_static_ep] + call ehci_init_static_endpoint + or byte [edi-sizeof.ehci_static_ep+ehci_static_ep.Token+1], 80h +; 3j. Initialize the head of Bulk list. + sub esi, sizeof.ehci_static_ep + call ehci_init_static_endpoint +; 4. Create a virtual memory area to talk with the controller. +; 4a. Enable memory & bus master access. + mov ch, [.bus] + mov cl, 1 + mov eax, ecx + mov bh, [.devfn] + mov bl, 4 + call pci_read_reg + or al, 6 + xchg eax, ecx + call pci_write_reg +; 4b. Read memory base address. + mov ah, [.bus] + mov al, 2 + mov bl, 10h + call pci_read_reg +; DEBUGF 1,'K : phys MMIO %x\n',eax + and al, not 0Fh +; 4c. Create mapping for physical memory. 200h bytes are always sufficient. + stdcall map_io_mem, eax, 200h, PG_SW+PG_NOCACHE + test eax, eax + jz .fail +; DEBUGF 1,'K : MMIO %x\n',eax +if ehci_controller.MMIOBase1 <> ehci_controller.BulkED + sizeof.ehci_static_ep +.err assertion failed +end if + stosd ; fill ehci_controller.MMIOBase1 + movzx ecx, byte [eax+EhciCapLengthReg] + mov edx, [eax+EhciCapParamsReg] + mov ebx, [eax+EhciStructParamsReg] + add eax, ecx +if ehci_controller.MMIOBase2 <> ehci_controller.MMIOBase1 + 4 +.err assertion failed +end if + stosd ; fill ehci_controller.MMIOBase2 +if ehci_controller.StructuralParams <> ehci_controller.MMIOBase2 + 4 +.err assertion failed +end if +if ehci_controller.CapabilityParams <> ehci_controller.StructuralParams + 4 +.err assertion failed +end if + mov [edi], ebx ; fill ehci_controller.StructuralParams + mov [edi+4], edx ; fill ehci_controller.CapabilityParams + DEBUGF 1,'K : HCSPARAMS=%x, HCCPARAMS=%x\n',ebx,edx + and ebx, 15 + mov [edi+usb_controller.NumPorts+sizeof.ehci_controller-ehci_controller.StructuralParams], ebx + mov edi, eax +; now edi = MMIOBase2 +; 6. Transfer the controller to a known state. +; 6b. Stop the controller if it is running. + push 10 + pop ecx + test dword [edi+EhciStatusReg], 1 shl 12 + jnz .stopped + and dword [edi+EhciCommandReg], not 1 +@@: + push 1 + pop esi + call delay_ms + test dword [edi+EhciStatusReg], 1 shl 12 + jnz .stopped + loop @b + dbgstr 'Failed to stop EHCI controller' + jmp .fail_unmap +.stopped: +; 6c. Reset the controller. Wait up to 50 ms checking status every 1 ms. + or dword [edi+EhciCommandReg], 2 + push 50 + pop ecx +@@: + push 1 + pop esi + call delay_ms + test dword [edi+EhciCommandReg], 2 + jz .reset_ok + loop @b + dbgstr 'Failed to reset EHCI controller' + jmp .fail_unmap +.reset_ok: +; 7. Configure the controller. + pop esi ; restore the pointer saved at step 1 + add esi, sizeof.ehci_controller +; 7a. If the controller is 64-bit, say to it that all structures are located +; in first 4G. + test byte [esi+ehci_controller.CapabilityParams-sizeof.ehci_controller], 1 + jz @f + mov dword [edi+EhciCtrlDataSegReg], 0 +@@: +; 7b. Hook interrupt and enable appropriate interrupt sources. + mov ah, [.bus] + mov al, 0 + mov bh, [.devfn] + mov bl, 3Ch + call pci_read_reg +; al = IRQ + DEBUGF 1,'K : attaching to IRQ %x\n',al + movzx eax, al + stdcall attach_int_handler, eax, ehci_irq, esi +; mov dword [edi+EhciStatusReg], 111111b ; clear status +; disable Frame List Rollover interrupt, enable all other sources + mov dword [edi+EhciInterruptReg], 110111b +; 7c. Inform the controller of the address of periodic lists head. + lea eax, [esi-sizeof.ehci_controller] + call get_phys_addr + mov dword [edi+EhciPeriodicListReg], eax +; 7d. Inform the controller of the address of asynchronous lists head. + lea eax, [esi+ehci_controller.ControlED-sizeof.ehci_controller] + call get_phys_addr + mov dword [edi+EhciAsyncListReg], eax +; 7e. Configure operational details and run the controller. + mov dword [edi+EhciCommandReg], \ + (1 shl 16) + \ ; interrupt threshold = 1 microframe = 0.125ms + (0 shl 11) + \ ; disable Async Park Mode + (0 shl 8) + \ ; zero Async Park Mode Count + (1 shl 5) + \ ; Async Schedule Enable + (1 shl 4) + \ ; Periodic Schedule Enable + (0 shl 2) + \ ; 1024 elements in FrameList + 1 ; Run +; 7f. Route all ports to this controller, not companion controllers. + mov dword [edi+EhciConfigFlagReg], 1 + DEBUGF 1,'K : EHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] +; 8. Apply port power, if needed, and disable all ports. + xor ecx, ecx +@@: + mov dword [edi+EhciPortsReg+ecx*4], 1000h ; Port Power enabled, all other bits disabled + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb @b + test byte [esi+ehci_controller.StructuralParams-sizeof.ehci_controller], 10h + jz @f + push esi + push 20 + pop esi + call delay_ms + pop esi +@@: + DEBUGF 1,'K : EHCI %x: command = %x, status = %x\n',esi,[edi+EhciCommandReg],[edi+EhciStatusReg] +; 9. Return pointer to usb_controller. + xchg eax, esi + ret +; On error, pop the pointer saved at step 1 and return zero. +; Note that the main code branch restores the stack at step 7 and never fails +; after step 7. +.fail_unmap: + pop eax + push eax + stdcall free_kernel_space, [eax+ehci_controller.MMIOBase1] +.fail: + pop ecx + xor eax, eax + ret +endp + +; Helper procedure for step 3 of ehci_init, see comments there. +; Initializes the static head of one list. +; esi = pointer to the "next" list, edi = pointer to head to initialize. +; Advances edi to the next head, keeps esi. +proc ehci_init_static_endpoint + xor eax, eax + inc eax ; set Terminate bit + mov [edi+ehci_static_ep.NextTD], eax + mov [edi+ehci_static_ep.AlternateNextTD], eax + test esi, esi + jz @f + mov eax, esi + call get_phys_addr + inc eax + inc eax ; set Type to EHCI_TYPE_QH +@@: + mov [edi+ehci_static_ep.NextQH], eax + mov [edi+ehci_static_ep.NextList], esi + mov byte [edi+ehci_static_ep.OverlayToken], 1 shl 6 ; halted + add edi, ehci_static_ep.SoftwarePart + call usb_init_static_endpoint + add edi, sizeof.ehci_static_ep - ehci_static_ep.SoftwarePart + ret +endp + +; Helper procedure for step 3 of ehci_init, see comments there. +; Initializes one half of group of static heads. +; edx = size of the next group = half of size of the group, +; edi = pointer to the group, esi = pointer to the next group. +; Advances esi, edi to next group, keeps edx. +proc ehci_init_static_ep_group + push edx +@@: + call ehci_init_static_endpoint + add esi, sizeof.ehci_static_ep + dec edx + jnz @b + pop edx + ret +endp + +; Controller-specific pre-initialization function: take ownership from BIOS. +; Some BIOSes, although not all of them, use USB controllers themselves +; to support USB flash drives. In this case, +; we must notify the BIOS that we don't need that emulation and know how to +; deal with USB devices. +proc ehci_kickoff_bios +; 1. Get the physical address of MMIO registers. + mov ah, [esi+PCIDEV.bus] + mov bh, [esi+PCIDEV.devfn] + mov al, 2 + mov bl, 10h + call pci_read_reg + and al, not 0Fh +; 2. Create mapping for physical memory. 200h bytes are always sufficient. + stdcall map_io_mem, eax, 200h, PG_SW+PG_NOCACHE + test eax, eax + jz .nothing + push eax ; push argument for step 8 +; 3. Some BIOSes enable controller interrupts as a result of giving +; controller away. At this point the system knows nothing about how to serve +; EHCI interrupts, so such an interrupt will send the system into an infinite +; loop handling the same IRQ again and again. Thus, we need to block EHCI +; interrupts. We can't do this at the controller level until step 5, +; because the controller is currently owned by BIOS, so we block all hardware +; interrupts on this processor until step 5. + pushf + cli +; 4. Take the ownership over the controller. +; 4a. Locate take-ownership capability in the PCI configuration space. +; Limit the loop with 100h iterations; since the entire configuration space is +; 100h bytes long, hitting this number of iterations means that something is +; corrupted. +; Use a value from MMIO as a starting point. + mov edx, [eax+EhciCapParamsReg] + DEBUGF 1,'K : edx=%x\n',edx + movzx edi, byte [eax+EhciCapLengthReg] + add edi, eax + push 0 + mov bl, dh ; get Extended Capabilities Pointer + test bl, bl + jz .has_ownership2 + cmp bl, 40h + jb .no_capability +.look_bios_handoff: + test bl, 3 + jnz .no_capability +; In each iteration, read the current dword, + mov ah, [esi+PCIDEV.bus] + mov al, 2 + mov bh, [esi+PCIDEV.devfn] + call pci_read_reg +; check, whether the capability ID is take-ownership ID = 1, + cmp al, 1 + jz .found_bios_handoff +; if not, advance to next-capability link and continue loop. + dec byte [esp] + jz .no_capability + mov bl, ah + cmp bl, 40h + jae .look_bios_handoff +.no_capability: + dbgstr 'warning: cannot locate take-ownership capability' + jmp .has_ownership2 +.found_bios_handoff: +; 4b. Check whether BIOS has ownership. +; Some BIOSes release ownership before loading OS, but forget to unwatch for +; change-ownership requests; they cannot handle ownership request, so +; such a request sends the system into infinite loop of handling the same SMI +; over and over. Avoid this. + inc ebx + inc ebx + test eax, 0x10000 + jz .has_ownership +; 4c. Request ownership. + inc ebx + mov cl, 1 + mov ah, [esi+PCIDEV.bus] + mov al, 0 + call pci_write_reg +; 4d. Some BIOSes set ownership flag, but forget to watch for change-ownership +; requests; if so, there is no sense in waiting. + inc ebx + mov ah, [esi+PCIDEV.bus] + mov al, 2 + call pci_read_reg + dec ebx + dec ebx + test ah, 20h + jz .force_ownership +; 4e. Wait for result no more than 1 s, checking for status every 1 ms. +; If successful, go to 5. + mov dword [esp], 1000 +@@: + mov ah, [esi+PCIDEV.bus] + mov al, 0 + call pci_read_reg + test al, 1 + jz .has_ownership + push esi + push 1 + pop esi + call delay_ms + pop esi + dec dword [esp] + jnz @b + dbgstr 'warning: taking EHCI ownership from BIOS timeout' +.force_ownership: +; 4f. BIOS has not responded within the timeout. +; Let's just clear BIOS ownership flag and hope that everything will be ok. + mov ah, [esi+PCIDEV.bus] + mov al, 0 + mov cl, 0 + call pci_write_reg +.has_ownership: +; 5. Just in case clear all SMI event sources except change-ownership. + dbgstr 'has_ownership' + inc ebx + inc ebx + mov ah, [esi+PCIDEV.bus] + mov al, 2 + mov ecx, eax + call pci_read_reg + and ax, 2000h + xchg eax, ecx + call pci_write_reg +.has_ownership2: + pop ecx +; 6. Disable all controller interrupts until the system will be ready to +; process them. + mov dword [edi+EhciInterruptReg], 0 +; 7. Now we can unblock interrupts in the processor. + popf +; 8. Release memory mapping created in step 2 and return. + call free_kernel_space +.nothing: + ret +endp + +; IRQ handler for EHCI controllers. +ehci_irq.noint: + spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock] +; Not our interrupt: restore registers and return zero. + xor eax, eax + pop edi esi ebx + ret + +proc ehci_irq + push ebx esi edi ; save registers to be cdecl +virtual at esp + rd 3 ; saved registers + dd ? ; return address +.controller dd ? +end virtual +; 1. ebx will hold whether some deferred processing is needed, +; that cannot be done from the interrupt handler. Initialize to zero. + xor ebx, ebx +; 2. Get the mask of events which should be processed. + mov esi, [.controller] + mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] + spin_lock_irqsave [esi+usb_controller.WaitSpinlock] + mov eax, [edi+EhciStatusReg] + mov ecx, eax +; DEBUGF 1,'K : [%d] EHCI status %x\n',[timer_ticks],eax +; 3. Check whether that interrupt has been generated by our controller. +; (One IRQ can be shared by several devices.) + and eax, [edi+EhciInterruptReg] + jz .noint +; 4. Clear the events we know of. +; Note that this should be done before processing of events: +; new events could arise while we are processing those, this way we won't lose +; them (the controller would generate another interrupt after completion +; of this one). + DEBUGF 1,'K : EHCI %x interrupt: status = %x, enable = %x\n',esi,ecx,[edi+EhciInterruptReg] +; DEBUGF 1,'K : EHCI interrupt: status = %x\n',eax + mov [edi+EhciStatusReg], eax +; 5. Sanity check. + test al, 10h + jz @f + DEBUGF 1,'K : something terrible happened with EHCI %x (%x)\n',esi,al +@@: +; We can't do too much from an interrupt handler. Inform the processing thread +; that it should perform appropriate actions. + or [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], eax + spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock] + inc ebx + call usb_wakeup_if_needed +; 6. Interrupt processed; return non-zero. + mov al, 1 + pop edi esi ebx ; restore used registers to be cdecl + ret +endp + +; This procedure is called from usb_set_address_callback +; and stores USB device address in the ehci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe, cl = address +proc ehci_set_device_address + mov byte [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart], cl + call usb_subscribe_control + ret +endp + +; This procedure returns USB device address from the ehci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe +; out: eax = endpoint address +proc ehci_get_device_address + mov eax, [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart] + and eax, 7Fh + ret +endp + +; This procedure is called from usb_set_address_callback +; if the device does not accept SET_ADDRESS command and needs +; to be disabled at the port level. +; in: esi -> usb_controller, ecx = port (zero-based) +proc ehci_port_disable + mov eax, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] + and dword [eax+EhciPortsReg+ecx*4], not (4 or 2Ah) + ret +endp + +; This procedure is called from usb_get_descr8_callback when +; the packet size for zero endpoint becomes known and +; stores the packet size in ehci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size +proc ehci_set_endpoint_packet_size + mov eax, [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart] + and eax, not (0x7FF shl 16) + shl ecx, 16 + or eax, ecx + mov [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart], eax +; Wait until hardware cache is evicted. + call usb_subscribe_control + ret +endp + +uglobal +align 4 +; Data for memory allocator, see memory.inc. +ehci_ep_first_page dd ? +ehci_ep_mutex MUTEX +ehci_gtd_first_page dd ? +ehci_gtd_mutex MUTEX +endg + +; This procedure allocates memory for pipe. +; Both hardware+software parts must be allocated, returns pointer to usb_pipe +; (software part). +proc ehci_alloc_pipe + push ebx + mov ebx, ehci_ep_mutex + stdcall usb_allocate_common, sizeof.ehci_pipe + test eax, eax + jz @f + add eax, ehci_pipe.SoftwarePart +@@: + pop ebx + ret +endp + +; This procedure frees memory for pipe allocated by ehci_alloc_pipe. +; void stdcall with one argument = pointer to usb_pipe. +proc ehci_free_pipe +virtual at esp + dd ? ; return address +.ptr dd ? +end virtual + sub [.ptr], ehci_pipe.SoftwarePart + jmp usb_free_common +endp + +; This procedure is called from API usb_open_pipe and processes +; the controller-specific part of this API. See docs. +; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, +; esi -> usb_controller, eax -> usb_gtd for the first TD, +; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type +proc ehci_init_pipe +virtual at ebp+8 +.config_pipe dd ? +.endpoint dd ? +.maxpacket dd ? +.type dd ? +.interval dd ? +end virtual +; 1. Zero all fields in the hardware part. + push eax ecx + sub edi, ehci_pipe.SoftwarePart + xor eax, eax + push ehci_pipe.SoftwarePart/4 + pop ecx + rep stosd + pop ecx eax +; 2. Setup PID in the first TD and make sure that the it is not active. + xor edx, edx + test byte [.endpoint], 80h + setnz dh + mov [eax+ehci_gtd.Token-ehci_gtd.SoftwarePart], edx + mov [eax+ehci_gtd.NextTD-ehci_gtd.SoftwarePart], 1 + mov [eax+ehci_gtd.AlternateNextTD-ehci_gtd.SoftwarePart], 1 +; 3. Store physical address of the first TD. + sub eax, ehci_gtd.SoftwarePart + call get_phys_addr + mov [edi+ehci_pipe.Overlay.NextTD-ehci_pipe.SoftwarePart], eax +; 4. Fill ehci_pipe.Flags except for S- and C-masks. +; Copy location from the config pipe. + mov eax, [ecx+ehci_pipe.Flags-ehci_pipe.SoftwarePart] + and eax, 3FFF0000h +; Use 1 requests per microframe for control/bulk endpoints, +; use value from the endpoint descriptor for periodic endpoints + push 1 + pop edx + test [.type], 1 + jz @f + mov edx, [.maxpacket] + shr edx, 11 + inc edx +@@: + shl edx, 30 + or eax, edx + mov [edi+ehci_pipe.Flags-ehci_pipe.SoftwarePart], eax +; 5. Fill ehci_pipe.Token. + mov eax, [ecx+ehci_pipe.Token-ehci_pipe.SoftwarePart] +; copy following fields from the config pipe: +; DeviceAddress, EndpointSpeed, ControlEndpoint if new type is control + mov ecx, eax + and eax, 307Fh + and ecx, 8000000h + or ecx, 4000h + mov edx, [.endpoint] + and edx, 15 + shl edx, 8 + or eax, edx + mov edx, [.maxpacket] + shl edx, 16 + or eax, edx +; for control endpoints, use DataToggle from TD, otherwise use DataToggle from QH + cmp [.type], CONTROL_PIPE + jnz @f + or eax, ecx +@@: +; for control/bulk USB2 endpoints, set NakCountReload to 4 + test eax, USB_SPEED_HS shl 12 + jz .nonak + cmp [.type], CONTROL_PIPE + jz @f + cmp [.type], BULK_PIPE + jnz .nonak +@@: + or eax, 40000000h +.nonak: + mov [edi+ehci_pipe.Token-ehci_pipe.SoftwarePart], eax +; 5. Select the corresponding list and insert to the list. +; 5a. Use Control list for control pipes, Bulk list for bulk pipes. + lea edx, [esi+ehci_controller.ControlED.SoftwarePart-sizeof.ehci_controller] + cmp [.type], BULK_PIPE + jb .insert ; control pipe + lea edx, [esi+ehci_controller.BulkED.SoftwarePart-sizeof.ehci_controller] + jz .insert ; bulk pipe +.interrupt_pipe: +; 5b. For interrupt pipes, let the scheduler select the appropriate list +; and the appropriate microframe(s) (which goes to S-mask and C-mask) +; based on the current bandwidth distribution and the requested bandwidth. +; There are two schedulers, one for high-speed devices, +; another for split transactions. +; This could fail if the requested bandwidth is not available; +; if so, return an error. + test word [edi+ehci_pipe.Flags-ehci_pipe.SoftwarePart+2], 3FFFh + jnz .interrupt_fs + call ehci_select_hs_interrupt_list + jmp .interrupt_common +.interrupt_fs: + call ehci_select_fs_interrupt_list +.interrupt_common: + test edx, edx + jz .return0 + mov word [edi+ehci_pipe.Flags-ehci_pipe.SoftwarePart], ax +.insert: + mov [edi+ehci_pipe.BaseList-ehci_pipe.SoftwarePart], edx +; Insert to the head of the corresponding list. +; Note: inserting to the head guarantees that the list traverse in +; ehci_process_updated_schedule, once started, will not interact with new pipes. +; However, we still need to ensure that links in the new pipe (edi.NextVirt) +; are initialized before links to the new pipe (edx.NextVirt). +; 5c. Insert in the list of virtual addresses. + mov ecx, [edx+usb_pipe.NextVirt] + mov [edi+usb_pipe.NextVirt], ecx + mov [edi+usb_pipe.PrevVirt], edx + mov [ecx+usb_pipe.PrevVirt], edi + mov [edx+usb_pipe.NextVirt], edi +; 5d. Insert in the hardware list: copy previous NextQH to the new pipe, +; store the physical address of the new pipe to previous NextQH. + mov ecx, [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart] + mov [edi+ehci_pipe.NextQH-ehci_pipe.SoftwarePart], ecx + lea eax, [edi-ehci_pipe.SoftwarePart] + call get_phys_addr + inc eax + inc eax + mov [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], eax +; 6. Return with nonzero eax. + ret +.return0: + xor eax, eax + ret +endp + +; This function is called from ehci_process_deferred when +; a new device was connected at least USB_CONNECT_DELAY ticks +; and therefore is ready to be configured. +; ecx = port, esi -> ehci_controller, edi -> EHCI MMIO +proc ehci_new_port +; 1. If the device operates at low-speed, just release it to a companion. + mov eax, [edi+EhciPortsReg+ecx*4] + DEBUGF 1,'K : [%d] EHCI %x port %d state is %x\n',[timer_ticks],esi,ecx,eax + mov edx, eax + and ah, 0Ch + cmp ah, 4 + jz .low_speed +; 2. Devices operating at full-speed and high-speed must now have ah == 8. +; Some broken hardware asserts both D+ and D- even after initial decoupling; +; if so, stop initialization here, no sense in further actions. + cmp ah, 0Ch + jz .se1 +; 3. If another port is resetting right now, mark this port as 'reset pending' +; and return. + bts [esi+usb_controller.PendingPorts], ecx + cmp [esi+usb_controller.ResettingPort], -1 + jnz .nothing + btr [esi+usb_controller.PendingPorts], ecx +; Otherwise, fall through to ohci_new_port.reset. + +; This function is called from ehci_new_port and usb_test_pending_port. +; It starts reset signalling for the port. Note that in USB first stages +; of configuration can not be done for several ports in parallel. +.reset: + push edi + mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] + mov eax, [edi+EhciPortsReg+ecx*4] +; 1. Store information about resetting hub (roothub) and port. + and [esi+usb_controller.ResettingHub], 0 + mov [esi+usb_controller.ResettingPort], cl +; 2. Initiate reset signalling. + or ah, 1 + and al, not (4 or 2Ah) + mov [edi+EhciPortsReg+ecx*4], eax +; 3. Store the current time and set status to 1 = reset signalling active. + mov eax, [timer_ticks] + mov [esi+usb_controller.ResetTime], eax + mov [esi+usb_controller.ResettingStatus], 1 +; dbgstr 'high-speed or full-speed device, resetting' + DEBUGF 1,'K : [%d] EHCI %x: port %d has HS or FS device, resetting\n',[timer_ticks],esi,ecx + pop edi +.nothing: + ret +.low_speed: +; dbgstr 'low-speed device, releasing' + DEBUGF 1,'K : [%d] EHCI %x: port %d has LS device, releasing\n',[timer_ticks],esi,ecx + or dh, 20h + and dl, not 2Ah + mov [edi+EhciPortsReg+ecx*4], edx + ret +.se1: + dbgstr 'SE1 after connect debounce. Broken hardware?' + ret +endp + +; This procedure is called from several places in main USB code +; and allocates required packets for the given transfer. +; ebx = pipe, other parameters are passed through the stack: +; buffer,size = data to transfer +; flags = same as in usb_open_pipe: bit 0 = allow short transfer, other bits reserved +; td = pointer to the current end-of-queue descriptor +; direction = +; 0000b for normal transfers, +; 1000b for control SETUP transfer, +; 1101b for control OUT transfer, +; 1110b for control IN transfer +; returns eax = pointer to the new end-of-queue descriptor +; (not included in the queue itself) or 0 on error +proc ehci_alloc_transfer stdcall uses edi, \ + buffer:dword, size:dword, flags:dword, td:dword, direction:dword +locals +origTD dd ? +packetSize dd ? ; must be last variable, see usb_init_transfer +endl +; 1. Save original value of td: +; it will be useful for rollback if something would fail. + mov eax, [td] + mov [origTD], eax +; One transfer descriptor can describe up to 5 pages. +; In the worst case (when the buffer is something*1000h+0FFFh) +; this corresponds to 4001h bytes. If the requested size is +; greater, we should split the transfer into several descriptors. +; Boundaries to split must be multiples of endpoint transfer size +; to avoid short packets except in the end of the transfer, +; 4000h is always a good value. +; 2. While the remaining data cannot fit in one descriptor, +; allocate full descriptors (of maximal possible size). + mov edi, 4000h + mov [packetSize], edi +.fullpackets: + cmp [size], edi + jbe .lastpacket + call ehci_alloc_packet + test eax, eax + jz .fail + mov [td], eax + add [buffer], edi + sub [size], edi + jmp .fullpackets +; 3. The remaining data can fit in one packet; +; allocate the last descriptor with size = size of remaining data. +.lastpacket: + mov eax, [size] + mov [packetSize], eax + call ehci_alloc_packet + test eax, eax + jz .fail +; 9. Update flags in the last packet. + mov edx, [flags] + mov [ecx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], edx +; 10. Fill AlternateNextTD field in all allocated TDs. +; If the caller says that short transfer is ok, the queue must advance to +; the next descriptor, which is in eax. +; Otherwise, the queue should stop, so make AlternateNextTD point to +; always-inactive descriptor StopQueueTD. + push eax + test dl, 1 + jz .disable_short + sub eax, ehci_gtd.SoftwarePart + jmp @f +.disable_short: + mov eax, [ebx+usb_pipe.Controller] + add eax, ehci_controller.StopQueueTD - sizeof.ehci_controller +@@: + call get_phys_addr + mov edx, [origTD] +@@: + cmp edx, [esp] + jz @f + mov [edx+ehci_gtd.AlternateNextTD-ehci_gtd.SoftwarePart], eax + mov edx, [edx+usb_gtd.NextVirt] + jmp @b +@@: + pop eax + ret +.fail: + mov edi, ehci_hardware_func + mov eax, [td] + stdcall usb_undo_tds, [origTD] + xor eax, eax + ret +endp + +; Helper procedure for ehci_alloc_transfer. +; Allocates and initializes one transfer descriptor. +; ebx = pipe, other parameters are passed through the stack; +; fills the current last descriptor and +; returns eax = next descriptor (not filled). +proc ehci_alloc_packet +; inherit some variables from the parent ehci_alloc_transfer +virtual at ebp-8 +.origTD dd ? +.packetSize dd ? + rd 2 +.buffer dd ? +.transferSize dd ? +.Flags dd ? +.td dd ? +.direction dd ? +end virtual +; 1. Allocate the next TD. + call ehci_alloc_td + test eax, eax + jz .nothing +; 2. Initialize controller-independent parts of both TDs. + push eax + call usb_init_transfer + pop eax +; 3. Copy PID to the new descriptor. + mov edx, [ecx+ehci_gtd.Token-ehci_gtd.SoftwarePart] + mov [eax+ehci_gtd.Token-ehci_gtd.SoftwarePart], edx + mov [eax+ehci_gtd.NextTD-ehci_gtd.SoftwarePart], 1 + mov [eax+ehci_gtd.AlternateNextTD-ehci_gtd.SoftwarePart], 1 +; 4. Save the returned value (next descriptor). + push eax +; 5. Store the physical address of the next descriptor. + sub eax, ehci_gtd.SoftwarePart + call get_phys_addr + mov [ecx+ehci_gtd.NextTD-ehci_gtd.SoftwarePart], eax +; 6. For zero-length transfers, store zero in all fields for buffer addresses. +; Otherwise, fill them with real values. + xor eax, eax + mov [ecx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], eax +repeat 10 + mov [ecx+ehci_gtd.BufferPointers-ehci_gtd.SoftwarePart+(%-1)*4], eax +end repeat + cmp [.packetSize], eax + jz @f + mov eax, [.buffer] + call get_phys_addr + mov [ecx+ehci_gtd.BufferPointers-ehci_gtd.SoftwarePart], eax + and eax, 0xFFF + mov edx, [.packetSize] + add edx, eax + sub edx, 0x1000 + jbe @f + mov eax, [.buffer] + add eax, 0x1000 + call get_pg_addr + mov [ecx+ehci_gtd.BufferPointers+4-ehci_gtd.SoftwarePart], eax + sub edx, 0x1000 + jbe @f + mov eax, [.buffer] + add eax, 0x2000 + call get_pg_addr + mov [ecx+ehci_gtd.BufferPointers+8-ehci_gtd.SoftwarePart], eax + sub edx, 0x1000 + jbe @f + mov eax, [.buffer] + add eax, 0x3000 + call get_pg_addr + mov [ecx+ehci_gtd.BufferPointers+12-ehci_gtd.SoftwarePart], eax + sub edx, 0x1000 + jbe @f + mov eax, [.buffer] + add eax, 0x4000 + call get_pg_addr + mov [ecx+ehci_gtd.BufferPointers+16-ehci_gtd.SoftwarePart], eax +@@: +; 7. Fill Token field: +; set Status = 0 (inactive, ehci_insert_transfer would mark everything active); +; keep current PID if [.direction] is zero, use two lower bits of [.direction] +; otherwise shifted as (0|1|2) -> (2|0|1); +; set error counter to 3; +; set current page to 0; +; do not interrupt on complete (ehci_insert_transfer sets this bit where needed); +; set DataToggle to bit 2 of [.direction]. + mov eax, [ecx+ehci_gtd.Token-ehci_gtd.SoftwarePart] + and eax, 300h ; keep PID code + mov edx, [.direction] + test edx, edx + jz .haspid + and edx, 3 + dec edx + jns @f + add edx, 3 +@@: + mov ah, dl + mov edx, [.direction] + and edx, not 3 + shl edx, 29 + or eax, edx +.haspid: + or eax, 0C00h + mov edx, [.packetSize] + shl edx, 16 + or eax, edx + mov [ecx+ehci_gtd.Token-ehci_gtd.SoftwarePart], eax +; 4. Restore the returned value saved in step 2. + pop eax +.nothing: + ret +endp + +; This procedure is called from several places in main USB code +; and activates the transfer which was previously allocated by +; ehci_alloc_transfer. +; ecx -> last descriptor for the transfer, ebx -> usb_pipe +proc ehci_insert_transfer + or byte [ecx+ehci_gtd.Token+1-ehci_gtd.SoftwarePart], 80h ; set IOC bit + mov eax, [esp+4] +.activate: + or byte [eax+ehci_gtd.Token-ehci_gtd.SoftwarePart], 80h ; set Active bit + cmp eax, ecx + mov eax, [eax+usb_gtd.NextVirt] + jnz .activate + ret +endp + +; This function is called from ehci_process_deferred when +; reset signalling for a new device needs to be finished. +proc ehci_port_reset_done + movzx ecx, [esi+usb_controller.ResettingPort] + and dword [edi+EhciPortsReg+ecx*4], not 12Ah + mov eax, [timer_ticks] + mov [esi+usb_controller.ResetTime], eax + mov [esi+usb_controller.ResettingStatus], 2 + DEBUGF 1,'K : [%d] EHCI %x: reset port %d done\n',[timer_ticks],esi,ecx + ret +endp + +; This function is called from ehci_process_deferred when +; a new device has been reset, recovered after reset and needs to be configured. +proc ehci_port_init +; 1. Get the status and set it to zero. +; If reset has been failed (device disconnected during reset), +; continue to next device (if there is one). + xor eax, eax + xchg al, [esi+usb_controller.ResettingStatus] + test al, al + js usb_test_pending_port +; 2. Get the port status. High-speed devices should be now enabled, +; full-speed devices are left disabled; +; if the port is disabled, release it to a companion and continue to +; next device (if there is one). + movzx ecx, [esi+usb_controller.ResettingPort] + mov eax, [edi+EhciPortsReg+ecx*4] + DEBUGF 1,'K : [%d] EHCI %x status of port %d is %x\n',[timer_ticks],esi,ecx,eax + test al, 4 + jnz @f +; DEBUGF 1,'K : USB port disabled after reset, status = %x\n',eax + dbgstr 'releasing to companion' + or ah, 20h + mov [edi+EhciPortsReg+ecx*4], eax + jmp usb_test_pending_port +@@: +; 3. Call the worker procedure to notify the protocol layer +; about new EHCI device. It is high-speed. + push USB_SPEED_HS + pop eax + call ehci_new_device + test eax, eax + jnz .nothing +; 4. If something at the protocol layer has failed +; (no memory, no bus address), disable the port and stop the initialization. +.disable_exit: + and dword [edi+EhciPortsReg+ecx*4], not (4 or 2Ah) + jmp usb_test_pending_port +.nothing: + ret +endp + +; This procedure is called from ehci_port_init and from hub support code +; when a new device is connected and has been reset. +; It calls usb_new_device at the protocol layer with correct parameters. +; in: esi -> usb_controller, eax = speed. +proc ehci_new_device + push ebx ecx ; save used registers (ecx is important for ehci_port_init) +; 1. Store the speed for the protocol layer. + mov [esi+usb_controller.ResettingSpeed], al +; 2. Shift speed bits to the proper place in ehci_pipe.Token. + shl eax, 12 +; 3. For high-speed devices, go to step 5 with edx = 0. + xor edx, edx + cmp ah, USB_SPEED_HS shl (12-8) + jz .common +; 4. For low-speed and full-speed devices, fill address:port +; of the last high-speed hub (the closest to the device hub) +; for split transactions, and set ControlEndpoint bit in eax; +; ehci_init_pipe assumes that the parent pipe is a control pipe. + movzx ecx, [esi+usb_controller.ResettingPort] + mov edx, [esi+usb_controller.ResettingHub] + push eax +.find_hs_hub: + mov eax, [edx+usb_hub.ConfigPipe] + mov eax, [eax+usb_pipe.DeviceData] + cmp [eax+usb_device_data.Speed], USB_SPEED_HS + jz .found_hs_hub + movzx ecx, [eax+usb_device_data.Port] + mov edx, [eax+usb_device_data.Hub] + jmp .find_hs_hub +.found_hs_hub: + mov edx, [edx+usb_hub.ConfigPipe] + inc ecx + mov edx, [edx+ehci_pipe.Token-ehci_pipe.SoftwarePart] + shl ecx, 23 + and edx, 7Fh + shl edx, 16 + or edx, ecx ; ehci_pipe.Flags + pop eax + or eax, 1 shl 27 ; ehci_pipe.Token +.common: +; 5. Create pseudo-pipe in the stack. +; See ehci_init_pipe: only .Controller, .Token, .Flags fields are used. + push esi ; ehci_pipe.SoftwarePart.Controller + mov ecx, esp + sub esp, ehci_pipe.SoftwarePart - ehci_pipe.Flags - 4 + push edx ; ehci_pipe.Flags + push eax ; ehci_pipe.Token +; 6. Notify the protocol layer. + call usb_new_device +; 7. Cleanup the stack after step 5 and return. + add esp, ehci_pipe.SoftwarePart - ehci_pipe.Flags + 8 + pop ecx ebx ; restore used registers + ret +endp + +; This procedure is called in the USB thread from usb_thread_proc, +; processes regular actions and those actions which can't be safely done +; from interrupt handler. +; Returns maximal time delta before the next call. +proc ehci_process_deferred + push ebx edi ; save used registers to be stdcall + mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] +; 1. Get the mask of events to process. + xor eax, eax + xchg eax, [esi+ehci_controller.DeferredActions-sizeof.ehci_controller] + push eax +; 2. Initialize the return value. + push -1 +; Handle roothub events. +; 3a. Test whether there are such events. + test al, 4 + jz .skip_roothub +; Status of some port has changed. Loop over all ports. +; 3b. Prepare for the loop: start from port 0. + xor ecx, ecx +.portloop: +; 3c. Get the port status and changes of it. +; If there are no changes, just continue to the next port. + mov eax, [edi+EhciPortsReg+ecx*4] + test al, 2Ah + jz .nextport +; 3d. Clear change bits and read the status again. +; (It is possible, although quite unlikely, that some event occurs between +; the first read and the clearing, invalidating the old status. If an event +; occurs after the clearing, we will not miss it, looking in the next scan. + mov [edi+EhciPortsReg+ecx*4], eax + mov ebx, eax + mov eax, [edi+EhciPortsReg+ecx*4] + DEBUGF 1,'K : [%d] EHCI %x: status of port %d changed to %x\n',[timer_ticks],esi,ecx,ebx +; 3e. Handle overcurrent. +; Note: that needs work. + test bl, 20h ; overcurrent change + jz .noovercurrent + test al, 10h ; overcurrent active + jz .noovercurrent + DEBUGF 1,'K : overcurrent at port %d\n',ecx +.noovercurrent: +; 3f. Handle changing of connection status. + test bl, 2 + jz .nocsc +; There was a connect or disconnect event at this port. +; 3g. Disconnect the old device on this port, if any. +; If the port was resetting, indicate fail; later stages will process it. + cmp [esi+usb_controller.ResettingHub], 0 + jnz @f + cmp cl, [esi+usb_controller.ResettingPort] + jnz @f + mov [esi+usb_controller.ResettingStatus], -1 +@@: + bts [esi+usb_controller.NewDisconnected], ecx +; 3h. Change connected status. For the connection event, also store +; the connection time; any further processing is permitted only after +; USB_CONNECT_DELAY ticks. + test al, 1 + jz .disconnect + mov eax, [timer_ticks] + mov [esi+usb_controller.ConnectedTime+ecx*4], eax + bts [esi+usb_controller.NewConnected], ecx + jmp .nextport +.disconnect: + btr [esi+usb_controller.NewConnected], ecx + jmp .nextport +.nocsc: +; 3i. Handle port disabling. +; Note: that needs work. + test al, 8 + jz @f + test al, 4 + jz @f + DEBUGF 1,'K : port %d disabled\n',ecx +@@: +; 3j. Continue the loop for the next port. +.nextport: + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .portloop +.skip_roothub: +; 4. Process disconnect events. This should be done after step 3 +; (which includes the first stage of disconnect processing). + call usb_disconnect_stage2 +; 5. Check for previously connected devices. +; If there is a connected device which was connected less than +; USB_CONNECT_DELAY ticks ago, plan to wake up when the delay will be over. +; Otherwise, call ehci_new_port. +; This should be done after step 3. + xor ecx, ecx + cmp [esi+usb_controller.NewConnected], ecx + jz .skip_newconnected +.portloop2: + bt [esi+usb_controller.NewConnected], ecx + jnc .noconnect + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ConnectedTime+ecx*4] + sub eax, USB_CONNECT_DELAY + jge .connected + neg eax + cmp [esp], eax + jb .nextport2 + mov [esp], eax + jmp .nextport2 +.connected: + btr [esi+usb_controller.NewConnected], ecx + call ehci_new_port + jmp .portloop2 +.noconnect: +.nextport2: + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .portloop2 +.skip_newconnected: +; 6. Process wait lists. +; 6a. Periodic endpoints. +; If a request is pending >8 microframes, satisfy it. +; If a request is pending <=8 microframes, schedule next wakeup in 0.01s. + mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] + cmp eax, [esi+usb_controller.ReadyPipeHeadPeriodic] + jz .noperiodic + mov edx, [edi+EhciFrameIndexReg] + sub edx, [esi+usb_controller.StartWaitFrame] + and edx, 0x3FFF + cmp edx, 8 + jbe @f + mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax + jmp .noperiodic +@@: + pop eax + push 1 ; wakeup in 0.01 sec for next test +.noperiodic: +; 6b. Asynchronous endpoints. +; Satisfy a request when InterruptOnAsyncAdvance fired. + test byte [esp+4], 20h + jz @f + dbgstr 'async advance int' + mov eax, [esi+usb_controller.WaitPipeRequestAsync] + mov [esi+usb_controller.ReadyPipeHeadAsync], eax +@@: +; Some hardware in some (rarely) conditions set the status bit, +; but just does not generate the corresponding interrupt. +; Force checking the status here. + mov eax, [esi+usb_controller.WaitPipeRequestAsync] + cmp [esi+usb_controller.ReadyPipeHeadAsync], eax + jz .noasync + spin_lock_irq [esi+usb_controller.WaitSpinlock] + mov edx, [edi+EhciStatusReg] + test dl, 20h + jz @f + mov dword [edi+EhciStatusReg], 20h + and dword [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], not 20h + dbgstr 'warning: async advance int missed' + mov [esi+usb_controller.ReadyPipeHeadAsync], eax + jmp .async_unlock +@@: + cmp dword [esp], 100 + jb .async_unlock + mov dword [esp], 100 +.async_unlock: + spin_unlock_irq [esi+usb_controller.WaitSpinlock] +.noasync: +; 7. Finalize transfers processed by hardware. +; It is better to perform this step after step 4 (disconnect events), +; although not strictly obligatory. This way, an active transfer aborted +; due to disconnect would be handled with more specific USB_STATUS_CLOSED, +; not USB_STATUS_NORESPONSE. + test byte [esp+4], 3 + jz @f + call ehci_process_updated_schedule +@@: +; 8. Test whether reset signalling has been started and should be stopped now. +; This must be done after step 7, because completion of some transfer could +; result in resetting a new port. +.test_reset: +; 8a. Test whether reset signalling is active. + cmp [esi+usb_controller.ResettingStatus], 1 + jnz .no_reset_in_progress +; 8b. Yep. Test whether it should be stopped. + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ResetTime] + sub eax, USB_RESET_TIME + jge .reset_done +; 8c. Not yet, but initiate wakeup in -eax ticks and exit this step. + neg eax + cmp [esp], eax + jb .skip_reset + mov [esp], eax + jmp .skip_reset +.reset_done: +; 8d. Yep, call the worker function and proceed to 8e. + call ehci_port_reset_done +.no_reset_in_progress: +; 8e. Test whether reset process is done, either successful or failed. + cmp [esi+usb_controller.ResettingStatus], 0 + jz .skip_reset +; 8f. Yep. Test whether it should be stopped. + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ResetTime] + sub eax, USB_RESET_RECOVERY_TIME + jge .reset_recovery_done +; 8g. Not yet, but initiate wakeup in -eax ticks and exit this step. + neg eax + cmp [esp], eax + jb .skip_reset + mov [esp], eax + jmp .skip_reset +.reset_recovery_done: +; 8h. Yep, call the worker function. This could initiate another reset, +; so return to the beginning of this step. + call ehci_port_init + jmp .test_reset +.skip_reset: +; 9. Process wait-done notifications, test for new wait requests. +; Note: that must be done after steps 4 and 7 which could create new requests. +; 9a. Call the worker function. + call usb_process_wait_lists +; 9b. If it reports that an asynchronous endpoint should be removed, +; doorbell InterruptOnAsyncAdvance and schedule wakeup in 1s +; (sometimes it just does not fire). + test al, 1 shl CONTROL_PIPE + jz @f + mov edx, [esi+usb_controller.WaitPipeListAsync] + mov [esi+usb_controller.WaitPipeRequestAsync], edx + or dword [edi+EhciCommandReg], 1 shl 6 + dbgstr 'async advance doorbell' + cmp dword [esp], 100 + jb @f + mov dword [esp], 100 +@@: +; 9c. If it reports that a periodic endpoint should be removed, +; save the current frame and schedule wakeup in 0.01 sec. + test al, 1 shl INTERRUPT_PIPE + jz @f + mov eax, [esi+usb_controller.WaitPipeListPeriodic] + mov [esi+usb_controller.WaitPipeRequestPeriodic], eax + mov edx, [edi+EhciFrameIndexReg] + mov [esi+usb_controller.StartWaitFrame], edx + mov dword [esp], 1 ; wakeup in 0.01 sec for next test +@@: +; 10. Pop the return value, restore the stack after step 1 and return. + pop eax + pop ecx + pop edi ebx ; restore used registers to be stdcall + ret +endp + +; This procedure is called in the USB thread from ehci_process_deferred +; when EHCI IRQ handler has signalled that new IOC-packet was processed. +; It scans all lists for completed packets and calls ehci_process_finalized_td +; for those packets. +proc ehci_process_updated_schedule +; Important note: we cannot hold the list lock during callbacks, +; because callbacks sometimes open and/or close pipes and thus acquire/release +; the corresponding lock itself. +; Fortunately, pipes can be finally freed only by another step of +; ehci_process_deferred, so all pipes existing at the start of this function +; will be valid while this function is running. Some pipes can be removed +; from the corresponding list, some pipes can be inserted; insert/remove +; functions guarantee that traversing one list yields all pipes that were in +; that list at the beginning of the traversing (possibly with some new pipes, +; possibly without some new pipes, that doesn't matter). + push edi +; 1. Process all Periodic lists. + lea edi, [esi+ehci_controller.IntEDs-sizeof.ehci_controller+ehci_static_ep.SoftwarePart] + lea ebx, [esi+ehci_controller.IntEDs+63*sizeof.ehci_static_ep-sizeof.ehci_controller+ehci_static_ep.SoftwarePart] +@@: + call ehci_process_updated_list + cmp edi, ebx + jnz @b +; 2. Process the Control list. + add edi, ehci_controller.ControlDelta + call ehci_process_updated_list +; 3. Process the Bulk list. + call ehci_process_updated_list +; 4. Return. + pop edi + ret +endp + +; This procedure is called from ehci_process_updated_schedule, see comments there. +; It processes one list, esi -> usb_controller, edi -> usb_static_ep, +; and advances edi to next head. +proc ehci_process_updated_list + push ebx +; 1. Perform the external loop over all pipes. + mov ebx, [edi+usb_static_ep.NextVirt] +.loop: + cmp ebx, edi + jz .done +; store pointer to the next pipe in the stack + push [ebx+usb_static_ep.NextVirt] +; 2. For every pipe, perform the internal loop over all descriptors. +; All descriptors are organized in the queue; we process items from the start +; of the queue until a) the last descriptor (not the part of the queue itself) +; or b) an active (not yet processed by the hardware) descriptor is reached. + lea ecx, [ebx+usb_pipe.Lock] + call mutex_lock + mov ebx, [ebx+usb_pipe.LastTD] + push ebx + mov ebx, [ebx+usb_gtd.NextVirt] +.tdloop: +; 3. For every descriptor, test active flag and check for end-of-queue; +; if either of conditions holds, exit from the internal loop. + cmp ebx, [esp] + jz .tddone + cmp byte [ebx+ehci_gtd.Token-ehci_gtd.SoftwarePart], 0 + js .tddone +; Release the queue lock while processing one descriptor: +; callback function could (and often would) schedule another transfer. + push ecx + call mutex_unlock + call ehci_process_updated_td + pop ecx + call mutex_lock + jmp .tdloop +.tddone: + call mutex_unlock + pop ebx +; End of internal loop, restore pointer to the next pipe +; and continue the external loop. + pop ebx + jmp .loop +.done: + pop ebx + add edi, sizeof.ehci_static_ep + ret +endp + +; This procedure is called from ehci_process_updated_list, which is itself +; called from ehci_process_updated_schedule, see comments there. +; It processes one completed descriptor. +; in: ebx -> usb_gtd, out: ebx -> next usb_gtd. +proc ehci_process_updated_td +; mov eax, [ebx+usb_gtd.Pipe] +; cmp [eax+usb_pipe.Type], INTERRUPT_PIPE +; jnz @f +; DEBUGF 1,'K : finalized TD for pipe %x:\n',eax +; lea eax, [ebx-ehci_gtd.SoftwarePart] +; DEBUGF 1,'K : %x %x %x %x\n',[eax],[eax+4],[eax+8],[eax+12] +; DEBUGF 1,'K : %x %x %x %x\n',[eax+16],[eax+20],[eax+24],[eax+28] +;@@: +; 1. Remove this descriptor from the list of descriptors for this pipe. + call usb_unlink_td +; 2. Calculate actual number of bytes transferred. + mov eax, [ebx+ehci_gtd.Token-ehci_gtd.SoftwarePart] + lea edx, [eax+eax] + shr edx, 17 + sub edx, [ebx+usb_gtd.Length] + neg edx +; 3. Check whether we need some special processing beyond notifying the driver. +; Transfer errors require special processing. +; Short packets require special processing if +; a) this is not the last descriptor for transfer stage +; (in this case we need to process subsequent descriptors for the stage too) +; or b) the caller considers short transfers to be an error. +; ehci_alloc_transfer sets bit 0 of ehci_gtd.Flags to 0 if short packet +; in this descriptor requires special processing and to 1 otherwise. +; If special processing is not needed, advance to 4 with ecx = 0. +; Otherwise, go to 6. + xor ecx, ecx + test al, 40h + jnz .error + test byte [ebx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], 1 + jnz .notify + cmp edx, [ebx+usb_gtd.Length] + jnz .special +.notify: +; 4. Either the descriptor in ebx was processed without errors, +; or all necessary error actions were taken and ebx points to the last +; related descriptor. +; 4a. Test whether it is the last descriptor in the transfer +; <=> it has an associated callback. + mov eax, [ebx+usb_gtd.Callback] + test eax, eax + jz .nocallback +; 4b. It has an associated callback; call it with corresponding parameters. + stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \ + [ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] + jmp .callback +.nocallback: +; 4c. It is an intermediate descriptor. Add its length to the length +; in the following descriptor. + mov eax, [ebx+usb_gtd.NextVirt] + add [eax+usb_gtd.Length], edx +.callback: +; 5. Free the current descriptor and return the next one. + push [ebx+usb_gtd.NextVirt] + stdcall ehci_free_td, ebx + pop ebx + ret +.error: + push ebx + sub ebx, ehci_gtd.SoftwarePart + DEBUGF 1,'K : TD failed:\n' + DEBUGF 1,'K : %x %x %x %x\n',[ebx],[ebx+4],[ebx+8],[ebx+12] + DEBUGF 1,'K : %x %x %x %x\n',[ebx+16],[ebx+20],[ebx+24],[ebx+28] + pop ebx + DEBUGF 1,'K : pipe now:\n' + mov ecx, [ebx+usb_gtd.Pipe] + sub ecx, ehci_pipe.SoftwarePart + DEBUGF 1,'K : %x %x %x %x\n',[ecx],[ecx+4],[ecx+8],[ecx+12] + DEBUGF 1,'K : %x %x %x %x\n',[ecx+16],[ecx+20],[ecx+24],[ecx+28] + DEBUGF 1,'K : %x %x %x %x\n',[ecx+32],[ecx+36],[ecx+40],[ecx+44] +.special: +; 6. Special processing is needed. +; 6a. Save the status and length. + push edx + push eax +; 6b. Traverse the list of descriptors looking for the final descriptor +; for this transfer. Free and unlink non-final descriptors. +; Final descriptor will be freed in step 5. +.look_final: + call usb_is_final_packet + jnc .found_final + push [ebx+usb_gtd.NextVirt] + stdcall ehci_free_td, ebx + pop ebx + call usb_unlink_td + jmp .look_final +.found_final: +; 6c. Restore the status saved in 6a and transform it to the error code. +; Notes: +; * any USB transaction error results in Halted bit; if it is not set, +; but we are here, it must be due to short packet; +; * babble is considered a fatal USB transaction error, +; other errors just lead to retrying the transaction; +; if babble is detected, return the corresponding error; +; * if several non-fatal errors have occured during transaction retries, +; all corresponding bits are set. In this case, return some error code, +; the order is quite arbitrary. + pop eax ; status + push USB_STATUS_UNDERRUN + pop ecx + test al, 40h ; not Halted? + jz .know_error + mov cl, USB_STATUS_OVERRUN + test al, 10h ; Babble detected? + jnz .know_error + mov cl, USB_STATUS_BUFOVERRUN + test al, 20h ; Data Buffer error? + jnz .know_error + mov cl, USB_STATUS_NORESPONSE + test al, 8 ; Transaction Error? + jnz .know_error + mov cl, USB_STATUS_STALL +.know_error: +; 6d. If error code is USB_STATUS_UNDERRUN and the last TD allows short packets, +; it is not an error; in this case, go to 4 with ecx = 0. + cmp ecx, USB_STATUS_UNDERRUN + jnz @f + test byte [ebx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], 1 + jz @f + xor ecx, ecx + pop edx ; length + jmp .notify +@@: +; 6e. Abort the entire transfer. +; There are two cases: either there is only one transfer stage +; (everything except control transfers), then ebx points to the last TD and +; all previous TD were unlinked and dismissed (if possible), +; or there are several stages (a control transfer) and ebx points to the last +; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, +; because Setup stage can not produce short packets); for Data stage, we need +; to unlink and free (if possible) one more TD and advance ebx to the next one. + cmp [ebx+usb_gtd.Callback], 0 + jnz .normal + push ecx + push [ebx+usb_gtd.NextVirt] + stdcall ehci_free_td, ebx + pop ebx + call usb_unlink_td + pop ecx +.normal: +; 6f. For bulk/interrupt transfers we have no choice but halt the queue, +; the driver should intercede (through some API which is not written yet). +; Control pipes normally recover at the next SETUP transaction (first stage +; of any control transfer), so we hope on the best and just advance the queue +; to the next transfer. (According to the standard, "A control pipe may also +; support functional stall as well, but this is not recommended."). + mov edx, [ebx+usb_gtd.Pipe] + mov eax, [ebx+ehci_gtd.NextTD-ehci_gtd.SoftwarePart] + or al, 1 + mov [edx+ehci_pipe.Overlay.NextTD-ehci_pipe.SoftwarePart], eax + mov [edx+ehci_pipe.Overlay.AlternateNextTD-ehci_pipe.SoftwarePart], eax + cmp [edx+usb_pipe.Type], CONTROL_PIPE + jz .control +; Bulk/interrupt transfer; halt the queue. + mov [edx+ehci_pipe.Overlay.Token-ehci_pipe.SoftwarePart], 40h + pop edx + jmp .notify +; Control transfer. +.control: + and [edx+ehci_pipe.Overlay.Token-ehci_pipe.SoftwarePart], 0 + dec [edx+ehci_pipe.Overlay.NextTD-ehci_pipe.SoftwarePart] + pop edx + jmp .notify +endp + +; This procedure unlinks the pipe from the corresponding pipe list. +; esi -> usb_controller, ebx -> usb_pipe +proc ehci_unlink_pipe + cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE + jnz @f + test word [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart+2], 3FFFh + jnz .interrupt_fs + call ehci_hs_interrupt_list_unlink + jmp .interrupt_common +.interrupt_fs: + call ehci_fs_interrupt_list_unlink +.interrupt_common: +@@: + mov edx, [ebx+usb_pipe.NextVirt] + mov eax, [ebx+usb_pipe.PrevVirt] + mov [edx+usb_pipe.PrevVirt], eax + mov [eax+usb_pipe.NextVirt], edx + mov edx, esi + sub edx, eax + cmp edx, sizeof.ehci_controller + mov edx, [ebx+ehci_pipe.NextQH-ehci_pipe.SoftwarePart] + jb .prev_is_static + mov [eax+ehci_pipe.NextQH-ehci_pipe.SoftwarePart], edx + ret +.prev_is_static: + mov [eax+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], edx + ret +endp + +proc ehci_alloc_td + push ebx + mov ebx, ehci_gtd_mutex + stdcall usb_allocate_common, sizeof.ehci_gtd + test eax, eax + jz @f + add eax, ehci_gtd.SoftwarePart +@@: + pop ebx + ret +endp + +; This procedure is called from several places from main USB code and +; frees all additional data associated with the transfer descriptor. +; EHCI has no additional data, so just free ehci_gtd structure. +proc ehci_free_td + sub dword [esp+4], ehci_gtd.SoftwarePart + jmp usb_free_common +endp diff --git a/kernel/trunk/bus/usb/hccommon.inc b/kernel/trunk/bus/usb/hccommon.inc new file mode 100644 index 0000000000..05dc309770 --- /dev/null +++ b/kernel/trunk/bus/usb/hccommon.inc @@ -0,0 +1,472 @@ +; USB Host Controller support code: hardware-independent part, +; common for all controller types. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; USB device must have at least 100ms of stable power before initializing can +; proceed; one timer tick is 10ms, so enforce delay in 10 ticks +USB_CONNECT_DELAY = 10 +; USB requires at least 10 ms for reset signalling. Normally, this is one timer +; tick. However, it is possible that we start reset signalling in the end of +; interval between timer ticks and then we test time in the start of the next +; interval; in this case, the delta between [timer_ticks] is 1, but the real +; time passed is significantly less than 10 ms. To avoid this, we add an extra +; tick; this guarantees that at least 10 ms have passed. +USB_RESET_TIME = 2 +; USB requires at least 10 ms of reset recovery, a delay between reset +; signalling and any commands to device. Add an extra tick for the same reasons +; as with the previous constant. +USB_RESET_RECOVERY_TIME = 2 + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= +; Controller descriptor. +; This structure represents the common (controller-independent) part +; of a controller for the USB code. The corresponding controller-dependent +; part *hci_controller is located immediately before usb_controller. +struct usb_controller +; Two following fields organize all controllers in the global linked list. +Next dd ? +Prev dd ? +HardwareFunc dd ? +; Pointer to usb_hardware_func structure with controller-specific functions. +NumPorts dd ? +; Number of ports in the root hub. +SetAddressBuffer rb 8 +; Buffer for USB control command SET_ADDRESS. +ExistingAddresses rd 128/32 +; Bitmask for 128 bits; bit i is cleared <=> address i is free for allocating +; for new devices. Bit 0 is always set. +; +; The hardware is allowed to cache some data from hardware structures. +; Regular operations are designed considering this, +; but sometimes it is required to wait for synchronization of hardware cache +; with modified structures in memory. +; The code keeps two queues of pipes waiting for synchronization, +; one for asynchronous (bulk/control) pipes, one for periodic pipes, hardware +; cache is invalidated under different conditions for those types. +; Both queues are organized in the same way, as single-linked lists. +; There are three special positions: the head of list (new pipes are added +; here), the first pipe to be synchronized at the current iteration, +; the tail of list (all pipes starting from here are synchronized). +WaitPipeListAsync dd ? +WaitPipeListPeriodic dd ? +; List heads. +WaitPipeRequestAsync dd ? +WaitPipeRequestPeriodic dd ? +; Pending request to hardware to refresh cache for items from WaitPipeList*. +; (Pointers to some items in WaitPipeList* or NULLs). +ReadyPipeHeadAsync dd ? +ReadyPipeHeadPeriodic dd ? +; Items of RemovingList* which were released by hardware and are ready +; for further processing. +; (Pointers to some items in WaitPipeList* or NULLs). +NewConnected dd ? +; bit mask of recently connected ports of the root hub, +; bit set = a device was recently connected to the corresponding port; +; after USB_CONNECT_DELAY ticks of stable status these ports are moved to +; PendingPorts +NewDisconnected dd ? +; bit mask of disconnected ports of the root hub, +; bit set = a device in the corresponding port was disconnected, +; disconnect processing is required. +PendingPorts dd ? +; bit mask of ports which are ready to be initialized +ControlLock MUTEX ? +; mutex which guards all operations with control queue +BulkLock MUTEX ? +; mutex which guards all operations with bulk queue +PeriodicLock MUTEX ? +; mutex which guards all operations with periodic queues +WaitSpinlock: +; spinlock guarding WaitPipeRequest/ReadyPipeHead (but not WaitPipeList) +StartWaitFrame dd ? +; USB frame number when WaitPipeRequest* was registered. +ResettingHub dd ? +; Pointer to usb_hub responsible for the currently resetting port, if any. +; NULL for the root hub. +ResettingPort db ? +; Port that is currently resetting, 0-based. +ResettingSpeed db ? +; Speed of currently resetting device. +ResettingStatus db ? +; Status of port reset. 0 = no port is resetting, -1 = reset failed, +; 1 = reset in progress, 2 = reset recovery in progress. + rb 1 ; alignment +ResetTime dd ? +; Time when reset signalling or reset recovery has been started. +ConnectedTime rd 16 +; Time, in timer ticks, when the port i has signalled the connect event. +; Valid only if bit i in NewConnected is set. +DevicesByPort rd 16 +; Pointer to usb_pipe for zero endpoint (which serves as device handle) +; for each port. +ends + +; Interface-specific data. Several interfaces of one device can operate +; independently, each is controlled by some driver and is identified by +; some driver-specific data passed as is to the driver. +struct usb_interface_data +DriverData dd ? +; Passed as is to the driver. +DriverFunc dd ? +; Pointer to USBSRV structure for the driver. +ends + +; Device-specific data. +struct usb_device_data +PipeListLock MUTEX +; Lock guarding OpenedPipeList. Must be the first item of the structure, +; the code passes pointer to usb_device_data as is to mutex_lock/unlock. +OpenedPipeList rd 2 +; List of all opened pipes for the device. +; Used when the device is disconnected, so all pipes should be closed. +ClosedPipeList rd 2 +; List of all closed, but still valid pipes for the device. +; A pipe closed with USBClosePipe is just deallocated, +; but a pipe closed due to disconnect must remain valid until driver-provided +; disconnect handler returns; this list links all such pipes to deallocate them +; after disconnect processing. +NumPipes dd ? +; Number of not-yet-closed pipes. +Hub dd ? +; NULL if connected to the root hub, pointer to usb_hub otherwise. +Port db ? +; Port on the hub, zero-based. +DeviceDescrSize db ? +; Size of device descriptor. +NumInterfaces db ? +; Number of interfaces. +Speed db ? +; Device speed, one of USB_SPEED_*. +ConfigDataSize dd ? +; Total size of data associated with the configuration descriptor +; (including the configuration descriptor itself); +Interfaces dd ? +; Offset from the beginning of this structure to Interfaces field. +; Variable-length fields: +; DeviceDescriptor: +; device descriptor starts here +; ConfigDescriptor = DeviceDescriptor + DeviceDescrSize +; configuration descriptor with all associated data +; Interfaces = ALIGN_UP(ConfigDescriptor + ConfigDataSize, 4) +; array of NumInterfaces elements of type usb_interface_data +ends + +usb_device_data.DeviceDescriptor = sizeof.usb_device_data + +; Description of controller-specific data and functions. +struct usb_hardware_func +ID dd ? ; '*HCI' +DataSize dd ? ; sizeof(*hci_controller) +Init dd ? +; Initialize controller-specific part of controller data. +; in: eax -> *hci_controller to initialize, [ebp-4] = (bus shl 8) + devfn +; out: eax = 0 <=> failed, otherwise eax -> usb_controller +ProcessDeferred dd ? +; Called regularly from the main loop of USB thread +; (either due to timeout from a previous call, or due to explicit wakeup). +; in: esi -> usb_controller +; out: eax = maximum timeout for next call (-1 = infinity) +SetDeviceAddress dd ? +; in: esi -> usb_controller, ebx -> usb_pipe, cl = address +GetDeviceAddress dd ? +; in: esi -> usb_controller, ebx -> usb_pipe +; out: eax = address +PortDisable dd ? +; Disable the given port in the root hub. +; in: esi -> usb_controller, ecx = port (zero-based) +InitiateReset dd ? +; Start reset signalling on the given port. +; in: esi -> usb_controller, ecx = port (zero-based) +SetEndpointPacketSize dd ? +; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size +AllocPipe dd ? +; out: eax = pointer to allocated usb_pipe +FreePipe dd ? +; void stdcall with one argument = pointer to previously allocated usb_pipe +InitPipe dd ? +; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, +; esi -> usb_controller, eax -> usb_gtd for the first TD, +; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type +UnlinkPipe dd ? +; esi -> usb_controller, ebx -> usb_pipe +AllocTD dd ? +; out: eax = pointer to allocated usb_gtd +FreeTD dd ? +; void stdcall with one argument = pointer to previously allocated usb_gtd +AllocTransfer dd ? +; Allocate and initialize one stage of a transfer. +; ebx -> usb_pipe, other parameters are passed through the stack: +; buffer,size = data to transfer +; flags = same as in usb_open_pipe: +; bit 0 = allow short transfer, other bits reserved +; td = pointer to the current end-of-queue descriptor +; direction = +; 0000b for normal transfers, +; 1000b for control SETUP transfer, +; 1101b for control OUT transfer, +; 1110b for control IN transfer +; returns eax = pointer to the new end-of-queue descriptor +; (not included in the queue itself) or 0 on error +InsertTransfer dd ? +; Activate previously initialized transfer (maybe with multiple stages). +; esi -> usb_controller, ebx -> usb_pipe, +; [esp+4] -> first usb_gtd for the transfer, +; ecx -> last descriptor for the transfer +NewDevice dd ? +; Initiate configuration of a new device (create pseudo-pipe describing that +; device and call usb_new_device). +; esi -> usb_controller, eax = speed (one of USB_SPEED_* constants). +ends + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +; Initializes one controller, called by usb_init for every controller. +; edi -> usb_hardware_func, eax -> PCIDEV structure for the device. +proc usb_init_controller + push ebp + mov ebp, esp +; 1. Store in the stack PCI coordinates and save pointer to PCIDEV: +; make [ebp-4] = (bus shl 8) + devfn, used by controller-specific Init funcs. + push dword [eax+PCIDEV.devfn] + push eax +; 2. Allocate *hci_controller + usb_controller. + mov ebx, [edi+usb_hardware_func.DataSize] + add ebx, sizeof.usb_controller + stdcall kernel_alloc, ebx + test eax, eax + jz .nothing +; 3. Zero-initialize both structures. + push edi eax + mov ecx, ebx + shr ecx, 2 + xchg edi, eax + xor eax, eax + rep stosd +; 4. Initialize usb_controller structure, +; except data known only to controller-specific code (like NumPorts) +; and link fields +; (this structure will be inserted to the overall list at step 6). + dec eax + mov [edi+usb_controller.ExistingAddresses+4-sizeof.usb_controller], eax + mov [edi+usb_controller.ExistingAddresses+8-sizeof.usb_controller], eax + mov [edi+usb_controller.ExistingAddresses+12-sizeof.usb_controller], eax + mov [edi+usb_controller.ResettingPort-sizeof.usb_controller], al ; no resetting port + dec eax ; don't allocate zero address + mov [edi+usb_controller.ExistingAddresses-sizeof.usb_controller], eax + lea ecx, [edi+usb_controller.PeriodicLock-sizeof.usb_controller] + call mutex_init + add ecx, usb_controller.ControlLock - usb_controller.PeriodicLock + call mutex_init + add ecx, usb_controller.BulkLock - usb_controller.ControlLock + call mutex_init + pop eax edi + mov [eax+ebx-sizeof.usb_controller+usb_controller.HardwareFunc], edi + push eax +; 5. Call controller-specific initialization. +; If failed, free memory allocated in step 2 and return. + call [edi+usb_hardware_func.Init] + test eax, eax + jz .fail + pop ecx +; 6. Insert the controller to the global list. + xchg eax, ebx + mov ecx, usb_controllers_list_mutex + call mutex_lock + mov edx, usb_controllers_list + mov eax, [edx+usb_controller.Prev] + mov [ebx+usb_controller.Next], edx + mov [ebx+usb_controller.Prev], eax + mov [edx+usb_controller.Prev], ebx + mov [eax+usb_controller.Next], ebx + call mutex_unlock +; 7. Wakeup USB thread to call ProcessDeferred. + call usb_wakeup +.nothing: +; 8. Restore pointer to PCIDEV saved in step 1 and return. + pop eax + leave + ret +.fail: + call kernel_free + jmp .nothing +endp + +; Helper function, calculates physical address including offset in page. +proc get_phys_addr + push ecx + mov ecx, eax + and ecx, 0xFFF + call get_pg_addr + add eax, ecx + pop ecx + ret +endp + +; Put the given control pipe in the wait list; +; called when the pipe structure is changed and a possible hardware cache +; needs to be synchronized. When it will be known that the cache is updated, +; usb_subscription_done procedure will be called. +proc usb_subscribe_control + cmp [ebx+usb_pipe.NextWait], -1 + jnz @f + mov eax, [esi+usb_controller.WaitPipeListAsync] + mov [ebx+usb_pipe.NextWait], eax + mov [esi+usb_controller.WaitPipeListAsync], ebx +@@: + ret +endp + +; Called after synchronization of hardware cache with software changes. +; Continues process of device enumeration based on when it was delayed +; due to call to usb_subscribe_control. +proc usb_subscription_done + mov eax, [ebx+usb_pipe.DeviceData] + cmp [eax+usb_device_data.DeviceDescrSize], 0 + jz usb_after_set_address + jmp usb_after_set_endpoint_size +endp + +; This function is called when a new device has either passed +; or failed first stages of configuration, so the next device +; can enter configuration process. +proc usb_test_pending_port + mov [esi+usb_controller.ResettingPort], -1 + cmp [esi+usb_controller.PendingPorts], 0 + jz .nothing + bsf ecx, [esi+usb_controller.PendingPorts] + btr [esi+usb_controller.PendingPorts], ecx + mov eax, [esi+usb_controller.HardwareFunc] + jmp [eax+usb_hardware_func.InitiateReset] +.nothing: + ret +endp + +; This procedure is regularly called from controller-specific ProcessDeferred, +; it checks whether there are disconnected events and if so, process them. +proc usb_disconnect_stage2 + bsf ecx, [esi+usb_controller.NewDisconnected] + jz .nothing + lock btr [esi+usb_controller.NewDisconnected], ecx + btr [esi+usb_controller.PendingPorts], ecx + xor ebx, ebx + xchg ebx, [esi+usb_controller.DevicesByPort+ecx*4] + test ebx, ebx + jz usb_disconnect_stage2 + call usb_device_disconnected + jmp usb_disconnect_stage2 +.nothing: + ret +endp + +; Initial stage of disconnect processing: called when device is disconnected. +proc usb_device_disconnected +; Loop over all pipes, close everything, wait until hardware reacts. +; The final handling is done in usb_pipe_closed. + push ebx + mov ecx, [ebx+usb_pipe.DeviceData] + call mutex_lock + lea eax, [ecx+usb_device_data.OpenedPipeList-usb_pipe.NextSibling] + push eax + mov ebx, [eax+usb_pipe.NextSibling] +.pipe_loop: + call usb_close_pipe_nolock + mov ebx, [ebx+usb_pipe.NextSibling] + cmp ebx, [esp] + jnz .pipe_loop + pop eax + pop ebx + mov ecx, [ebx+usb_pipe.DeviceData] + call mutex_unlock + ret +endp + +; Called from controller-specific ProcessDeferred, +; processes wait-pipe-done notifications, +; returns whether there are more items in wait queues. +; in: esi -> usb_controller +; out: eax = bitmask of pipe types with non-empty wait queue +proc usb_process_wait_lists + xor edx, edx + push edx + call usb_process_one_wait_list + jnc @f + or byte [esp], 1 shl CONTROL_PIPE +@@: + push 4 + pop edx + call usb_process_one_wait_list + jnc @f + or byte [esp], 1 shl INTERRUPT_PIPE +@@: + xor edx, edx + call usb_process_one_wait_list + jnc @f + or byte [esp], 1 shl CONTROL_PIPE +@@: + pop eax + ret +endp + +; Helper procedure for usb_process_wait_lists; +; does the same for one wait queue. +; in: esi -> usb_controller, +; edx=0 for *Async, edx=4 for *Periodic list +; out: CF = issue new request +proc usb_process_one_wait_list +; 1. Check whether there is a pending request. If so, do nothing. + mov ebx, [esi+usb_controller.WaitPipeRequestAsync+edx] + cmp ebx, [esi+usb_controller.ReadyPipeHeadAsync+edx] + clc + jnz .nothing +; 2. Check whether there are new data. If so, issue a new request. + cmp ebx, [esi+usb_controller.WaitPipeListAsync+edx] + stc + jnz .nothing + test ebx, ebx + jz .nothing +; 3. Clear all lists. + xor ecx, ecx + mov [esi+usb_controller.WaitPipeListAsync+edx], ecx + mov [esi+usb_controller.WaitPipeRequestAsync+edx], ecx + mov [esi+usb_controller.ReadyPipeHeadAsync+edx], ecx +; 4. Loop over all pipes from the wait list. +.pipe_loop: +; For every pipe: +; 5. Save edx and next pipe in the list. + push edx + push [ebx+usb_pipe.NextWait] +; 6. If USB_FLAG_EXTRA_WAIT is set, reinsert the pipe to the list and continue. + test [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT + jz .process + mov eax, [esi+usb_controller.WaitPipeListAsync+edx] + mov [ebx+usb_pipe.NextWait], eax + mov [esi+usb_controller.WaitPipeListAsync+edx], ebx + jmp .continue +.process: +; 7. Call the handler depending on USB_FLAG_CLOSED. + or [ebx+usb_pipe.NextWait], -1 + test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + jz .nodisconnect + call usb_pipe_closed + jmp .continue +.nodisconnect: + call usb_subscription_done +.continue: +; 8. Restore edx and next pipe saved in step 5 and continue the loop. + pop ebx + pop edx + test ebx, ebx + jnz .pipe_loop +.check_new_work: +; 9. Set CF depending on whether WaitPipeList* is nonzero. + cmp [esi+usb_controller.WaitPipeListAsync+edx], 1 + cmc +.nothing: + ret +endp diff --git a/kernel/trunk/bus/usb/hub.inc b/kernel/trunk/bus/usb/hub.inc new file mode 100644 index 0000000000..403d59a4e6 --- /dev/null +++ b/kernel/trunk/bus/usb/hub.inc @@ -0,0 +1,1237 @@ +; Support for USB (non-root) hubs: +; powering up/resetting/disabling ports, +; watching for adding/removing devices. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; Hub constants +; USB hub descriptor type +USB_HUB_DESCRIPTOR = 29h + +; Features for CLEAR_FEATURE commands to the hub. +C_HUB_LOCAL_POWER = 0 +C_HUB_OVER_CURRENT = 1 + +; Bits in result of GET_STATUS command for a port. +; Also suitable for CLEAR_FEATURE/SET_FEATURE commands, where applicable, +; except TEST/INDICATOR. +PORT_CONNECTION = 0 +PORT_ENABLE = 1 +PORT_SUSPEND = 2 +PORT_OVER_CURRENT = 3 +PORT_RESET = 4 +PORT_POWER = 8 +PORT_LOW_SPEED = 9 +PORT_HIGH_SPEED = 10 +PORT_TEST_BIT = 11 +PORT_INDICATOR_BIT = 12 +C_PORT_CONNECTION = 16 +C_PORT_ENABLE = 17 +C_PORT_SUSPEND = 18 +C_PORT_OVER_CURRENT = 19 +C_PORT_RESET = 20 +PORT_TEST_FEATURE = 21 +PORT_INDICATOR_FEATURE = 22 + +; Internal constants +; Bits in usb_hub.Actions +HUB_WAIT_POWERED = 1 +; ports were powered, wait until power is stable +HUB_WAIT_CONNECT = 2 +; some device was connected, wait initial debounce interval +HUB_RESET_IN_PROGRESS = 4 +; reset in progress, so buffer for config requests is owned +; by reset process; this includes all stages from initial disconnect test +; to end of setting address (fail on any stage should lead to disabling port, +; which requires a config request) +HUB_RESET_WAITING = 8 +; the port is ready for reset, but another device somewhere on the bus +; is resetting. Implies HUB_RESET_IN_PROGRESS +HUB_RESET_SIGNAL = 10h +; reset signalling is active for some port in the hub +; Implies HUB_RESET_IN_PROGRESS +HUB_RESET_RECOVERY = 20h +; reset recovery is active for some port in the hub +; Implies HUB_RESET_IN_PROGRESS + +; Well, I think that those 5 flags WAIT_CONNECT and RESET_* require additional +; comments. So that is the overview of what happens with a new device assuming +; no errors. +; * device is connected; +; * hub notifies us about connect event; after some processing +; usb_hub_port_change finally processes that event, setting the flag +; HUB_WAIT_CONNECT and storing time when the device was connected; +; * 100 ms delay; +; * usb_hub_process_deferred clears HUB_WAIT_CONNECT, +; sets HUB_RESET_IN_PROGRESS, stores the port index in ConfigBuffer and asks +; the hub whether there was a disconnect event for that port during those +; 100 ms (on the hardware level notifications are obtained using polling +; with some intervals, so it is possible that the corresponding notification +; has not arrived yet); +; * usb_hub_connect_port_status checks that there was no disconnect event +; and sets HUB_RESET_WAITING flag (HUB_RESET_IN_PROGRESS is still set, +; ConfigBuffer still contains the port index); +; * usb_hub_process_deferred checks whether there is another device currently +; resetting. If so, it waits until reset is done +; (with HUB_RESET_WAITING and HUB_RESET_IN_PROGRESS bits set); +; * usb_hub_process_deferred clears HUB_RESET_WAITING, sets HUB_RESET_SIGNAL +; and initiates reset signalling on the port; +; * usb_hub_process_deferred checks the status every tick; +; when reset signalling is stopped by the hub, usb_hub_resetting_port_status +; callback clears HUB_RESET_SIGNAL and sets HUB_RESET_RECOVERY; +; * 10 ms (at least) delay; +; * usb_hub_process_deferred clears HUB_RESET_RECOVERY and notifies other code +; that the new device is ready to be configured; +; * when it is possible to reset another device, the protocol layer +; clears HUB_RESET_IN_PROGRESS bit. + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= +; This structure contains all used data for one hub. +struct usb_hub +; All configured hubs are organized in the global usb_hub_list. +; Two following fields give next/prev items in that list. +; While the hub is unconfigured, they point to usb_hub itself. +Next dd ? +Prev dd ? +Controller dd ? +; Pointer to usb_controller for the bus. +; +; Handles of two pipes: configuration control pipe for zero endpoint opened by +; the common code and status interrupt pipe opened by us. +ConfigPipe dd ? +StatusPipe dd ? +NumPorts dd ? +; Number of downstream ports; from 1 to 255. +Actions dd ? +; Bitfield with HUB_* constants. +PoweredOnTime dd ? +; Time (in ticks) when all downstream ports were powered up. +ResetTime dd ? +; Time (in ticks) when the current port was reset; +; when a port is resetting, contains the last tick of status check; +; when reset recovery for a port is active, contains the time when +; reset was completed. +; +; There are two possible reasons for configuration requests: +; synchronous, when certain time is passed after something, +; and asynchronous, when the hub is notifying about some change and +; config request needs to be issued in order to query details. +; Use two different buffers to avoid unnecessary dependencies. +ConfigBuffer rb 8 +; Buffer for configuration requests for synchronous events. +ChangeConfigBuffer rb 8 +; Buffer for configuration requests for status changes. +AccStatusChange db ? +; Accumulated status change. See 11.12.3 of USB2 spec or comments in code. +HubCharacteristics dw ? +; Copy of usb_hub_descr.wHubCharacteristics. +PowerOnInterval db ? +; Copy of usb_hub_descr.bPwrOn2PwrGood. +; +; Two following fields are written at once by GET_STATUS request +; and must remain in this order. +StatusData dw ? +; Bitfield with 1 shl PORT_* indicating status of the current port. +StatusChange dw ? +; Bitfield with 1 shl PORT_* indicating change in status of the current port. +; Two following fields are written at once by GET_STATUS request +; and must remain in this order. +; The meaning is the same as of StatusData/StatusChange; two following fields +; are used by the synchronous requests to avoid unnecessary interactions with +; the asynchronous handler. +ResetStatusData dw ? +ResetStatusChange dw ? +StatusChangePtr dd ? +; Pointer to StatusChangeBuf. +ConnectedDevicesPtr dd ? +; Pointer to ConnectedDevices. +ConnectedTimePtr dd ? +; Pointer to ConnectedTime. +; +; Variable-length parts: +; DeviceRemovable rb (NumPorts+8)/8 +; Bit i+1 = device at port i (zero-based) is non-removable. +; StatusChangeBuf rb (NumPorts+8)/8 +; Buffer for status interrupt pipe. Bit 0 = hub status change, +; other bits = status change of the corresponding ports. +; ConnectedDevices rd NumPorts +; Pointers to config pipes for connected devices or zero if no device connected. +; ConnectedTime rd NumPorts +; For initial debounce interval: +; time (in ticks) when a device was connected at that port. +; Normally: -1 +ends + +; Hub descriptor. +struct usb_hub_descr usb_descr +bNbrPorts db ? +; Number of downstream ports. +wHubCharacteristics dw ? +; Bit 0: 0 = all ports are powered at once, 1 = individual port power switching +; Bit 1: reserved, must be zero +; Bit 2: 1 = the hub is part of a compound device +; Bits 3-4: 00 = global overcurrent protection, +; 01 = individual port overcurrent protection, +; 1x = no overcurrent protection +; Bits 5-6: Transaction Translator Think Time, 8*(value+1) full-speed bit times +; Bit 7: 1 = port indicators supported +; Other bits are reserved. +bPwrOn2PwrGood db ? +; Time in 2ms intervals between powering up a port and a port becoming ready. +bHubContrCurrent db ? +; Maximum current requirements of the Hub Controller electronics in mA. +; DeviceRemovable - variable length +; Bit 0 is reserved, bit i+1 = device at port i is non-removable. +; PortPwrCtrlMask - variable length +; Obsolete, exists for compatibility. We ignore it. +ends + +iglobal +align 4 +; Implementation of struct USBFUNC for hubs. +usb_hub_callbacks: + dd usb_hub_callbacks_end - usb_hub_callbacks + dd usb_hub_init + dd usb_hub_disconnect +usb_hub_callbacks_end: +usb_hub_pseudosrv dd usb_hub_callbacks +endg + +; This procedure is called when new hub is detected. +; It initializes the device. +; Technically, initialization implies sending several USB queries, +; so it is split in several procedures. The first is usb_hub_init, +; other are callbacks which will be called at some time in the future, +; when the device will respond. +; edx = usb_interface_descr, ecx = length rest +proc usb_hub_init + push ebx esi ; save used registers to be stdcall +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.pipe dd ? ; handle of the config pipe +.config dd ? ; pointer to usb_config_descr +.interface dd ? ; pointer to usb_interface_descr +end virtual +; Hubs use one IN interrupt endpoint for polling the device +; 1. Locate the descriptor of the interrupt endpoint. +; Loop over all descriptors owned by this interface. +.lookep: +; 1a. Skip the current descriptor. + movzx eax, [edx+usb_descr.bLength] + add edx, eax + sub ecx, eax + jb .errorep +; 1b. Length of data left must be at least sizeof.usb_endpoint_descr. + cmp ecx, sizeof.usb_endpoint_descr + jb .errorep +; 1c. If we have found another interface descriptor but not found our endpoint, +; this is an error: all subsequent descriptors belong to that interface +; (or further interfaces). + cmp [edx+usb_endpoint_descr.bDescriptorType], USB_INTERFACE_DESCR + jz .errorep +; 1d. Ignore all interface-related descriptors except endpoint descriptor. + cmp [edx+usb_endpoint_descr.bDescriptorType], USB_ENDPOINT_DESCR + jnz .lookep +; 1e. Length of endpoint descriptor must be at least sizeof.usb_endpoint_descr. + cmp [edx+usb_endpoint_descr.bLength], sizeof.usb_endpoint_descr + jb .errorep +; 1f. Ignore all endpoints except for INTERRUPT IN. + cmp [edx+usb_endpoint_descr.bEndpointAddress], 0 + jge .lookep + mov al, [edx+usb_endpoint_descr.bmAttributes] + and al, 3 + cmp al, INTERRUPT_PIPE + jnz .lookep +; We have located the descriptor for INTERRUPT IN endpoint, +; the pointer is in edx. +; 2. Allocate memory for the hub descriptor. +; Maximum length (assuming 255 downstream ports) is 40 bytes. +; 2a. Save registers. + push edx +; 2b. Call the allocator. + push 40 + pop eax + call malloc +; 2c. Restore registers. + pop ecx +; 2d. If failed, say something to the debug board and return error. + test eax, eax + jz .nomemory +; 2e. Store the pointer in esi. xchg eax,r32 is one byte shorter than mov. + xchg esi, eax +; 3. Open a pipe for the status endpoint with descriptor found in step 1. + mov ebx, [.pipe] + movzx eax, [ecx+usb_endpoint_descr.bEndpointAddress] + movzx edx, [ecx+usb_endpoint_descr.bInterval] + movzx ecx, [ecx+usb_endpoint_descr.wMaxPacketSize] + stdcall usb_open_pipe, ebx, eax, ecx, INTERRUPT_PIPE, edx +; If failed, free the memory allocated in step 2, +; say something to the debug board and return error. + test eax, eax + jz .free +; 4. Send control query for the hub descriptor, +; pass status pipe as a callback parameter, +; allow short packets. + mov dword [esi], 0xA0 + \ ; class-specific request + (USB_GET_DESCRIPTOR shl 8) + \ + (0 shl 16) + \ ; descriptor index 0 + (USB_HUB_DESCRIPTOR shl 24) + mov dword [esi+4], 40 shl 16 + stdcall usb_control_async, ebx, esi, esi, 40, usb_hub_got_config, eax, 1 +; 5. If failed, free the memory allocated in step 2, +; say something to the debug board and return error. + test eax, eax + jz .free +; Otherwise, return 1. usb_hub_got_config will overwrite it later. + xor eax, eax + inc eax + jmp .nothing +.free: + xchg eax, esi + call free + jmp .return0 +.errorep: + dbgstr 'Invalid config descriptor for a hub' + jmp .return0 +.nomemory: + dbgstr 'No memory for USB hub data' +.return0: + xor eax, eax +.nothing: + pop esi ebx ; restore used registers to be stdcall + retn 12 +endp + +; This procedure is called when the request for the hub descriptor initiated +; by usb_hub_init is finished, either successfully or unsuccessfully. +proc usb_hub_got_config stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword + push ebx ; save used registers to be stdcall +; 1. If failed, say something to the debug board, free the buffer +; and stop the initialization. + cmp [status], 0 + jnz .invalid +; 2. The length must be at least sizeof.usb_hub_descr. +; Note that [length] includes 8 bytes of setup packet. + cmp [length], 8 + sizeof.usb_hub_descr + jb .invalid +; 3. Sanity checks for the hub descriptor. + mov eax, [buffer] +if USB_DUMP_DESCRIPTORS + mov ecx, [length] + sub ecx, 8 + DEBUGF 1,'K : hub config:' + push eax +@@: + DEBUGF 1,' %x',[eax]:2 + inc eax + dec ecx + jnz @b + DEBUGF 1,'\n' + pop eax +end if + cmp [eax+usb_hub_descr.bLength], sizeof.usb_hub_descr + jb .invalid + cmp [eax+usb_hub_descr.bDescriptorType], USB_HUB_DESCRIPTOR + jnz .invalid + movzx ecx, [eax+usb_hub_descr.bNbrPorts] + test ecx, ecx + jz .invalid +; 4. We use sizeof.usb_hub_descr bytes plus DeviceRemovable info; +; size of DeviceRemovable is (NumPorts+1) bits, this gives +; floor(NumPorts/8)+1 bytes. Check that all data are present in the +; descriptor and were successfully read. + mov edx, ecx + shr edx, 3 + add edx, sizeof.usb_hub_descr + 1 + cmp [eax+usb_hub_descr.bLength], dl + jb .invalid + sub [length], 8 + cmp [length], edx + jb .invalid +; 5. Allocate the memory for usb_hub structure. +; Total size of variable-length data is ALIGN_UP(2*(floor(NumPorts/8)+1),4)+8*NumPorts. + lea edx, [sizeof.usb_hub+(edx-sizeof.usb_hub_descr)*2+3] + and edx, not 3 + lea eax, [edx+ecx*8] + push ecx edx + call malloc + pop edx ecx + test eax, eax + jz .nomemory + xchg eax, ebx +; 6. Fill usb_hub structure. + mov [ebx+usb_hub.NumPorts], ecx + add edx, ebx + mov [ebx+usb_hub.ConnectedDevicesPtr], edx + mov eax, [pipe] + mov [ebx+usb_hub.ConfigPipe], eax + mov edx, [eax+usb_pipe.Controller] + mov [ebx+usb_hub.Controller], edx + mov eax, [calldata] + mov [ebx+usb_hub.StatusPipe], eax + push esi edi + mov esi, [buffer] +; The following commands load bNbrPorts, wHubCharacteristics, bPwrOn2PwrGood. + mov edx, dword [esi+usb_hub_descr.bNbrPorts] + mov dl, 0 +; The following command zeroes AccStatusChange and stores +; HubCharacteristics and PowerOnInterval. + mov dword [ebx+usb_hub.AccStatusChange], edx + xor eax, eax + mov [ebx+usb_hub.Actions], eax +; Copy DeviceRemovable data. + lea edi, [ebx+sizeof.usb_hub] + add esi, sizeof.usb_hub_descr + mov edx, ecx + shr ecx, 3 + inc ecx + rep movsb + mov [ebx+usb_hub.StatusChangePtr], edi +; Zero ConnectedDevices. + mov edi, [ebx+usb_hub.ConnectedDevicesPtr] + mov ecx, edx + rep stosd + mov [ebx+usb_hub.ConnectedTimePtr], edi +; Set ConnectedTime to -1. + dec eax + mov ecx, edx + rep stosd + pop edi esi +; 7. Replace value of 1 returned from usb_hub_init to the real value. +; Note: hubs are part of the core USB code, so this code can work with +; internals of other parts. Another way, the only possible one for external +; drivers, is to use two memory allocations: one (returned from AddDevice and +; fixed after that) for pointer, another for real data. That would work also, +; but wastes one allocation. + mov eax, [pipe] + mov eax, [eax+usb_pipe.DeviceData] + add eax, [eax+usb_device_data.Interfaces] +.scan: + cmp [eax+usb_interface_data.DriverData], 1 + jnz @f + cmp [eax+usb_interface_data.DriverFunc], usb_hub_pseudosrv - USBSRV.usb_func + jz .scan_found +@@: + add eax, sizeof.usb_interface_data + jmp .scan +.scan_found: + mov [eax+usb_interface_data.DriverData], ebx +; 8. Insert the hub structure to the tail of the overall list of all hubs. + mov ecx, usb_hubs_list + mov edx, [ecx+usb_hub.Prev] + mov [ecx+usb_hub.Prev], ebx + mov [edx+usb_hub.Next], ebx + mov [ebx+usb_hub.Prev], edx + mov [ebx+usb_hub.Next], ecx +; 9. Start powering up all ports. + DEBUGF 1,'K : found hub with %d ports\n',[ebx+usb_hub.NumPorts] + lea eax, [ebx+usb_hub.ConfigBuffer] + xor ecx, ecx + mov dword [eax], 23h + \ ; class-specific request to hub port + (USB_SET_FEATURE shl 8) + \ + (PORT_POWER shl 16) + mov edx, [ebx+usb_hub.NumPorts] + mov dword [eax+4], edx + stdcall usb_control_async, [ebx+usb_hub.ConfigPipe], eax, ecx, ecx, usb_hub_port_powered, ebx, ecx +.freebuf: +; 10. Free the buffer for hub descriptor and return. + mov eax, [buffer] + call free + pop ebx ; restore used registers to be stdcall + ret +.nomemory: + dbgstr 'No memory for USB hub data' + jmp .freebuf +.invalid: + dbgstr 'Invalid hub descriptor' + jmp .freebuf +endp + +; This procedure is called when the request to power up some port is completed, +; either successfully or unsuccessfully. +proc usb_hub_port_powered stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; 1. Check whether the operation was successful. +; If not, say something to the debug board and ssstop the initialization. + cmp [status], 0 + jnz .invalid +; 2. Check whether all ports were powered. +; If so, go to 4. Otherwise, proceed to 3. + mov eax, [calldata] + dec dword [eax+usb_hub.ConfigBuffer+4] + jz .done +; 3. Power up the next port and return. + lea edx, [eax+usb_hub.ConfigBuffer] + xor ecx, ecx + stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, ecx, usb_hub_port_powered, eax, ecx +.nothing: + ret +.done: +; 4. All ports were powered. +; The hub requires some delay until power will be stable, the delay value +; is provided in the hub descriptor; we have copied that value to +; usb_hub.PowerOnInterval. Note the time and set the corresponding flag +; for usb_hub_process_deferred. + mov ecx, [timer_ticks] + mov [eax+usb_hub.PoweredOnTime], ecx + or [eax+usb_hub.Actions], HUB_WAIT_POWERED + jmp .nothing +.invalid: + dbgstr 'Error while powering hub ports' + jmp .nothing +endp + +; Requests notification about any changes in hub/ports configuration. +; Called when initial configuration is done and when a previous notification +; has been processed. +proc usb_hub_wait_change + mov ecx, [eax+usb_hub.NumPorts] + shr ecx, 3 + inc ecx + stdcall usb_normal_transfer_async, [eax+usb_hub.StatusPipe], \ + [eax+usb_hub.StatusChangePtr], ecx, usb_hub_changed, eax, 1 + ret +endp + +; This procedure is called when something has changed on the hub. +proc usb_hub_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; DEBUGF 1,'K : [%d] int pipe for hub %x\n',[timer_ticks],[calldata] +; 1. Check whether our request has failed. +; If so, say something to the debug board and stop processing notifications. + xor ecx, ecx + cmp [status], ecx + jnz .failed +; 2. If no data were retrieved, restart waiting. + mov eax, [calldata] + cmp [length], ecx + jz .continue +; 3. If size of data retrieved is less than maximal, pad with zeroes; +; this corresponds to 'state of other ports was not changed' + mov ecx, [eax+usb_hub.NumPorts] + shr ecx, 3 + inc ecx + sub ecx, [length] + push eax edi + mov edi, [buffer] + add edi, [length] + xor eax, eax + rep stosb + pop edi eax +.restart: +; State of some elements of the hub was changed. +; Find the first element that was changed, +; ask the hub about nature of the change, +; clear the corresponding change, +; reask the hub about status+change (it is possible that another change +; occurs between the first ask and clearing the change; we won't see that +; change, so we need to query the status after clearing the change), +; continue two previous steps until nothing changes, +; process all changes which were registered. +; When all changes for one element will be processed, return to here and look +; for other changed elements. + mov edx, [eax+usb_hub.StatusChangePtr] +; We keep all observed changes in the special var usb_hub.AccStatusChange; +; it will be logical OR of all observed StatusChange's. +; 4. No observed changes yet, zero usb_hub.AccStatusChange. + xor ecx, ecx + mov [eax+usb_hub.AccStatusChange], cl +; 5. Test whether there was a change in the hub itself. +; If so, query hub state. + btr dword [edx], ecx + jnc .no_hub_change +.next_hub_change: +; DEBUGF 1,'K : [%d] querying status of hub %x\n',[timer_ticks],eax + lea edx, [eax+usb_hub.ChangeConfigBuffer] + lea ecx, [eax+usb_hub.StatusData] + mov dword [edx], 0A0h + \ ; class-specific request from hub itself + (USB_GET_STATUS shl 8) + mov dword [edx+4], 4 shl 16 ; get 4 bytes + stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_status, eax, 0 + jmp .nothing +.no_hub_change: +; 6. Find the first port with changed state and clear the corresponding bit +; (so next scan after .restart will not consider this port again). +; If found, go to 8. Otherwise, advance to 7. + inc ecx +.test_port_change: + btr [edx], ecx + jc .found_port_change + inc ecx + cmp ecx, [eax+usb_hub.NumPorts] + jbe .test_port_change +.continue: +; 7. All changes have been processed. Wait for next notification. + call usb_hub_wait_change +.nothing: + ret +.found_port_change: + mov dword [eax+usb_hub.ChangeConfigBuffer+4], ecx +.next_port_change: +; 8. Query port state. Continue work in usb_hub_port_status callback. +; movzx ecx, [eax+usb_hub.ChangeConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : [%d] querying status of hub %x port %d\n',[timer_ticks],eax,ecx + lea edx, [eax+usb_hub.ChangeConfigBuffer] + mov dword [edx], 0A3h + \ ; class-specific request from hub port + (USB_GET_STATUS shl 8) + mov byte [edx+6], 4 ; data length = 4 bytes + lea ecx, [eax+usb_hub.StatusData] + stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_port_status, eax, 0 + jmp .nothing +.failed: + cmp [status], USB_STATUS_CLOSED + jz .nothing + dbgstr 'Querying hub notification failed' + jmp .nothing +endp + +; This procedure is called when the request of hub status is completed, +; either successfully or unsuccessfully. +proc usb_hub_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; 1. Check whether our request has failed. +; If so, say something to the debug board and stop processing notifications. + cmp [status], 0 + jnz .failed +; 2. Accumulate observed changes. + mov eax, [calldata] + mov dl, byte [eax+usb_hub.StatusChange] + or [eax+usb_hub.AccStatusChange], dl +.next_change: +; 3. Find the first change. If found, advance to 4. Otherwise, go to 5. + mov cl, C_HUB_OVER_CURRENT + btr dword [eax+usb_hub.StatusChange], 1 + jc .clear_hub_change + mov cl, C_HUB_LOCAL_POWER + btr dword [eax+usb_hub.StatusChange], 0 + jnc .final +.clear_hub_change: +; 4. Clear the change and continue in usb_hub_change_cleared callback. + lea edx, [eax+usb_hub.ChangeConfigBuffer] + mov dword [edx], 20h + \ ; class-specific request to hub itself + (USB_CLEAR_FEATURE shl 8) + mov [edx+2], cl ; feature selector + and dword [edx+4], 0 + stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_change_cleared, eax, 0 +.nothing: + ret +.final: +; 5. All changes cleared and accumulated, now process them. +; Note: that needs work. + DEBUGF 1,'K : hub status %x\n',[eax+usb_hub.AccStatusChange]:2 + test [eax+usb_hub.AccStatusChange], 1 + jz .no_local_power + test [eax+usb_hub.StatusData], 1 + jz .local_power_lost + dbgstr 'Hub local power is now good' + jmp .no_local_power +.local_power_lost: + dbgstr 'Hub local power is now lost' +.no_local_power: + test [eax+usb_hub.AccStatusChange], 2 + jz .no_overcurrent + test [eax+usb_hub.StatusData], 2 + jz .no_overcurrent + dbgstr 'Hub global overcurrent' +.no_overcurrent: +; 6. Process possible changes for other ports. + jmp usb_hub_changed.restart +.failed: + dbgstr 'Querying hub status failed' + jmp .nothing +endp + +; This procedure is called when the request to clear hub change is completed, +; either successfully or unsuccessfully. +proc usb_hub_change_cleared stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; 1. Check whether our request has failed. +; If so, say something to the debug board and stop processing notifications. + cmp [status], 0 + jnz .failed +; 2. If there is a change which was observed, but not yet cleared, +; go to the code which clears it. + mov eax, [calldata] + cmp [eax+usb_hub.StatusChange], 0 + jnz usb_hub_status.next_change +; 3. Otherwise, go to the code which queries the status. + jmp usb_hub_changed.next_hub_change +.failed: + dbgstr 'Clearing hub change failed' + ret +endp + +; This procedure is called when the request of port status is completed, +; either successfully or unsuccessfully. +proc usb_hub_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; 1. Check whether our request has failed. +; If so, say something to the debug board and stop processing notifications. + cmp [status], 0 + jnz .failed +; 2. Accumulate observed changes. + mov eax, [calldata] +; movzx ecx, [eax+usb_hub.ChangeConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : [%d] hub %x port %d status %x change %x\n',[timer_ticks],eax,ecx,[eax+usb_hub.StatusData]:4,[eax+usb_hub.StatusChange]:4 + mov dl, byte [eax+usb_hub.StatusChange] + or [eax+usb_hub.AccStatusChange], dl +.next_change: +; 3. Find the first change. If found, advance to 4. Otherwise, go to 5. +; Ignore change in reset status; it is cleared by synchronous code +; (usb_hub_process_deferred), so avoid unnecessary interference. +; mov cl, C_PORT_RESET + btr dword [eax+usb_hub.StatusChange], PORT_RESET +; jc .clear_port_change + mov cl, C_PORT_OVER_CURRENT + btr dword [eax+usb_hub.StatusChange], PORT_OVER_CURRENT + jc .clear_port_change + mov cl, C_PORT_SUSPEND + btr dword [eax+usb_hub.StatusChange], PORT_SUSPEND + jc .clear_port_change + mov cl, C_PORT_ENABLE + btr dword [eax+usb_hub.StatusChange], PORT_ENABLE + jc .clear_port_change + mov cl, C_PORT_CONNECTION + btr dword [eax+usb_hub.StatusChange], PORT_CONNECTION + jnc .final +.clear_port_change: +; 4. Clear the change and continue in usb_hub_port_changed callback. + call usb_hub_clear_port_change + jmp .nothing +.final: +; All changes cleared and accumulated, now process them. + movzx ecx, byte [eax+usb_hub.ChangeConfigBuffer+4] + dec ecx + DEBUGF 1,'K : final: hub %x port %d status %x change %x\n',eax,ecx,[eax+usb_hub.StatusData]:4,[eax+usb_hub.AccStatusChange]:2 +; 5. Process connect/disconnect events. +; 5a. Test whether there is such event. + test byte [eax+usb_hub.AccStatusChange], 1 shl PORT_CONNECTION + jz .nodisconnect +; 5b. If there was a connected device, notify the main code about disconnect. + push ebx + mov edx, [eax+usb_hub.ConnectedDevicesPtr] + xor ebx, ebx + xchg ebx, [edx+ecx*4] + test ebx, ebx + jz @f + push eax ecx + call usb_device_disconnected + pop ecx eax +@@: + pop ebx +; 5c. If the disconnect event corresponds to the port which is currently +; resetting, then another request from synchronous code could be in the fly, +; so aborting reset immediately would lead to problems with those requests. +; Thus, just set the corresponding status and let the synchronous code process. + test byte [eax+usb_hub.Actions], (HUB_RESET_SIGNAL or HUB_RESET_RECOVERY) + jz @f + mov edx, [eax+usb_hub.Controller] + cmp [edx+usb_controller.ResettingPort], cl + jnz @f + mov [edx+usb_controller.ResettingStatus], -1 +@@: +; 5d. If the current status is 'connected', store the current time as connect +; time and set the corresponding bit for usb_hub_process_deferred. +; Otherwise, set connect time to -1. +; If current time is -1, pretend that the event occured one tick later and +; store zero. + mov edx, [eax+usb_hub.ConnectedTimePtr] + test byte [eax+usb_hub.StatusData], 1 shl PORT_CONNECTION + jz .disconnected + or [eax+usb_hub.Actions], HUB_WAIT_CONNECT + push eax + call usb_hub_store_connected_time + pop eax + jmp @f +.disconnected: + or dword [edx+ecx*4], -1 +@@: +.nodisconnect: +; 6. Process port disabling. + test [eax+usb_hub.AccStatusChange], 1 shl PORT_ENABLE + jz .nodisable + test byte [eax+usb_hub.StatusData], 1 shl PORT_ENABLE + jnz .nodisable +; Note: that needs work. + dbgstr 'Port disabled' +.nodisable: +; 7. Process port overcurrent. + test [eax+usb_hub.AccStatusChange], 1 shl PORT_OVER_CURRENT + jz .noovercurrent + test byte [eax+usb_hub.StatusData], 1 shl PORT_OVER_CURRENT + jz .noovercurrent +; Note: that needs work. + dbgstr 'Port over-current' +.noovercurrent: +; 8. Process possible changes for other ports. + jmp usb_hub_changed.restart +.failed: + dbgstr 'Querying port status failed' +.nothing: + ret +endp + +; Helper procedure to store current time in ConnectedTime, +; advancing -1 to zero if needed. +proc usb_hub_store_connected_time + mov eax, [timer_ticks] +; transform -1 to 0, leave other values as is + cmp eax, -1 + sbb eax, -1 + mov [edx+ecx*4], eax + ret +endp + +; Helper procedure for several parts of hub code. +; Sends a request to clear the given feature of the port. +; eax -> usb_hub, cl = feature; +; as is should be called from async code, sync code should set +; edx to ConfigBuffer and call usb_hub_clear_port_change.buffer; +; port number (1-based) should be filled in [edx+4] by previous requests. +proc usb_hub_clear_port_change + lea edx, [eax+usb_hub.ChangeConfigBuffer] +.buffer: +; push edx +; movzx edx, byte [edx+4] +; dec edx +; DEBUGF 1,'K : [%d] hub %x port %d clear feature %d\n',[timer_ticks],eax,edx,cl +; pop edx + mov dword [edx], 23h + \ ; class-specific request to hub port + (USB_CLEAR_FEATURE shl 8) + mov byte [edx+2], cl + and dword [edx+4], 0xFF + stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, edx, 0, usb_hub_port_changed, eax, 0 + ret +endp + +; This procedure is called when the request to clear port change is completed, +; either successfully or unsuccessfully. +proc usb_hub_port_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; 1. Check whether our request has failed. +; If so, say something to the debug board and stop processing notifications. + cmp [status], 0 + jnz .failed +; 2. If the request was originated by synchronous code, no further processing +; is required. + mov eax, [calldata] + lea edx, [eax+usb_hub.ConfigBuffer] + cmp [buffer], edx + jz .nothing +; 3. If there is a change which was observed, but not yet cleared, +; go to the code which clears it. + cmp [eax+usb_hub.StatusChange], 0 + jnz usb_hub_port_status.next_change +; 4. Otherwise, go to the code which queries the status. + jmp usb_hub_changed.next_port_change +.failed: + dbgstr 'Clearing port change failed' +.nothing: + ret +endp + +; This procedure is called in the USB thread from usb_thread_proc, +; contains synchronous code which should be activated at certain time +; (e.g. reset a recently connected device after debounce interval 100ms). +; Returns the number of ticks when it should be called next time. +proc usb_hub_process_deferred +; 1. Top-of-stack will contain return value; initialize to infinite timeout. + push -1 +; 2. If wait for stable power is active, then +; either reschedule wakeup (if time is not over) +; or start processing notifications. + test byte [esi+usb_hub.Actions], HUB_WAIT_POWERED + jz .no_powered + movzx eax, [esi+usb_hub.PowerOnInterval] +; three following instructions are equivalent to edx = ceil(eax / 5) + 1 +; 1 extra tick is added to make sure that the interval is at least as needed +; (it is possible that PoweredOnTime was set just before timer interrupt, and +; this test goes on just after timer interrupt) + add eax, 9 +; two following instructions are equivalent to edx = floor(eax / 5) +; for any 0 <= eax < 40000000h + mov ecx, 33333334h + mul ecx + mov eax, [timer_ticks] + sub eax, [esi+usb_hub.PoweredOnTime] + sub eax, edx + jge .powered_on + neg eax + pop ecx + push eax + jmp .no_powered +.powered_on: + and [esi+usb_hub.Actions], not HUB_WAIT_POWERED + mov eax, esi + call usb_hub_wait_change +.no_powered: +; 3. If reset is pending, check whether we can start it and start it, if so. + test byte [esi+usb_hub.Actions], HUB_RESET_WAITING + jz .no_wait_reset + mov eax, [esi+usb_hub.Controller] + cmp [eax+usb_controller.ResettingPort], -1 + jnz .no_wait_reset + call usb_hub_initiate_reset +.no_wait_reset: +; 4. If reset signalling is active, wait for end of reset signalling +; and schedule wakeup in 1 tick. + test byte [esi+usb_hub.Actions], HUB_RESET_SIGNAL + jz .no_resetting_port +; It has no sense to query status several times per tick. + mov eax, [timer_ticks] + cmp eax, [esi+usb_hub.ResetTime] + jz @f + mov [esi+usb_hub.ResetTime], eax + movzx ecx, byte [esi+usb_hub.ConfigBuffer+4] + mov eax, usb_hub_resetting_port_status + call usb_hub_query_port_status +@@: + pop eax + push 1 +.no_resetting_port: +; 5. If reset recovery is active and time is not over, reschedule wakeup. + test byte [esi+usb_hub.Actions], HUB_RESET_RECOVERY + jz .no_reset_recovery + mov eax, [timer_ticks] + sub eax, [esi+usb_hub.ResetTime] + sub eax, USB_RESET_RECOVERY_TIME + jge .reset_done + neg eax + cmp [esp], eax + jb @f + mov [esp], eax +@@: + jmp .no_reset_recovery +.reset_done: +; 6. If reset recovery is active and time is over, clear 'reset recovery' flag, +; notify other code about a new device and let it do further steps. +; If that fails, stop reset process for this port and disable that port. + and [esi+usb_hub.Actions], not HUB_RESET_RECOVERY +; Bits 9-10 of port status encode port speed. +; If PORT_LOW_SPEED is set, the device is low-speed. Otherwise, +; PORT_HIGH_SPEED bit distinguishes full-speed and high-speed devices. +; This corresponds to values of USB_SPEED_FS=0, USB_SPEED_LS=1, USB_SPEED_HS=2. + mov eax, dword [esi+usb_hub.ResetStatusData] + shr eax, PORT_LOW_SPEED + and eax, 3 + test al, 1 + jz @f + mov al, 1 +@@: +; movzx ecx, [esi+usb_hub.ConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : [%d] hub %x port %d speed %d\n',[timer_ticks],esi,ecx,eax + push esi + mov esi, [esi+usb_hub.Controller] + cmp [esi+usb_controller.ResettingStatus], -1 + jz .disconnected_while_reset + mov edx, [esi+usb_controller.HardwareFunc] + call [edx+usb_hardware_func.NewDevice] + pop esi + test eax, eax + jnz .no_reset_recovery + mov eax, esi + call usb_hub_disable_resetting_port + jmp .no_reset_recovery +.disconnected_while_reset: + pop esi + mov eax, esi + call usb_hub_reset_aborted +.no_reset_recovery: +; 7. Handle recent connection events. +; Note: that should be done after step 6, because step 6 can clear +; HUB_RESET_IN_PROGRESS flag. +; 7a. Test whether there is such an event pending. If no, skip this step. + test byte [esi+usb_hub.Actions], HUB_WAIT_CONNECT + jz .no_wait_connect +; 7b. If we have started reset process for another port in the same hub, +; skip this step: the buffer for config requests can be used for that port. + test byte [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS + jnz .no_wait_connect +; 7c. Clear flag 'there are connection events which should be processed'. +; If there are another connection events, this flag will be set again. + and [esi+usb_hub.Actions], not HUB_WAIT_CONNECT +; 7d. Prepare for loop over all ports. + xor ecx, ecx +.test_wait_connect: +; 7e. For every port test for recent connection event. +; If none, continue the loop for the next port. + mov edx, [esi+usb_hub.ConnectedTimePtr] + mov eax, [edx+ecx*4] + cmp eax, -1 + jz .next_wait_connect + or [esi+usb_hub.Actions], HUB_WAIT_CONNECT +; 7f. Test whether initial delay is over. + sub eax, [timer_ticks] + neg eax + sub eax, USB_CONNECT_DELAY + jge .connect_delay_over +; 7g. The initial delay is not over; +; set the corresponding flag again, reschedule wakeup and continue the loop. + neg eax + cmp [esp], eax + jb @f + mov [esp], eax +@@: + jmp .next_wait_connect +.connect_delay_over: +; The initial delay is over. +; It is possible that there was disconnect event during that delay, probably +; with connect event after that. If so, we should restart the waiting. However, +; on the hardware level connect/disconnect events from hubs are implemented +; using polling with interval selected by the hub, so it is possible that +; we have not yet observed that disconnect event. +; Thus, we query port status+change data before all further processing. +; 7h. Send the request for status+change data. + push ecx +; Hub requests expect 1-based port number, not zero-based we operate with. + inc ecx + mov eax, usb_hub_connect_port_status + call usb_hub_query_port_status + pop ecx +; 3i. If request has been submitted successfully, set the flag +; 'reset in progress, config buffer is owned by reset process' and break +; from the loop. + test eax, eax + jz .next_wait_connect + or [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS + jmp .no_wait_connect +.next_wait_connect: +; 7j. Continue the loop for next port. + inc ecx + cmp ecx, [esi+usb_hub.NumPorts] + jb .test_wait_connect +.no_wait_connect: +; 8. Pop return value from top-of-stack and return. + pop eax + ret +endp + +; Helper procedure for other code. Called when reset process is aborted. +proc usb_hub_reset_aborted +; Clear 'reset in progress' flag and test for other devices which could be +; waiting for reset. + and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS + push esi + mov esi, [eax+usb_hub.Controller] + call usb_test_pending_port + pop esi + ret +endp + +; Helper procedure for usb_hub_process_deferred. +; Sends a request to query port status. +; esi -> usb_hub, eax = callback, ecx = 1-based port. +proc usb_hub_query_port_status +; dec ecx +; DEBUGF 1,'K : [%d] [main] hub %x port %d query status\n',[timer_ticks],esi,ecx +; inc ecx + add ecx, 4 shl 16 ; data length = 4 + lea edx, [esi+usb_hub.ConfigBuffer] + mov dword [edx], 0A3h + \ ; class-specific request from hub port + (USB_GET_STATUS shl 8) + mov dword [edx+4], ecx + lea ecx, [esi+usb_hub.ResetStatusData] + stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, ecx, 4, eax, esi, 0 + ret +endp + +; This procedure is called when the request to query port status +; initiated by usb_hub_process_deferred for testing connection is completed, +; either successfully or unsuccessfully. +proc usb_hub_connect_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword + push esi ; save used register to be stdcall + mov eax, [calldata] + mov esi, [pipe] +; movzx ecx, [eax+usb_hub.ConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : [%d] [connect test] hub %x port %d status %x change %x\n',[timer_ticks],eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4 +; 1. In any case, clear 'reset in progress' flag. +; If everything is ok, it would be set again. + and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS +; 2. If the request has failed, stop reset process. + cmp [status], 0 + jnz .nothing + mov edx, [eax+usb_hub.ConnectedTimePtr] + movzx ecx, byte [eax+usb_hub.ConfigBuffer+4] + dec ecx +; 3. Test whether there was a disconnect event. + test byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION + jz .reset +; 4. There was a disconnect event. +; There is another handler of connect/disconnect events, usb_hub_port_status. +; However, we do not know whether it has already processed this event +; or it will process it sometime later. +; If ConnectedTime is -1, then another handler has already run, +; there was no connection event, so just leave the value as -1. +; Otherwise, there are two possibilities: either another handler has not yet +; run (which is quite likely), or there was a connection event and the other +; handler has run exactly while our request was processed (otherwise our +; request would not been submitted; this is quite unlikely due to timing +; requirements, but not impossible). In this case, set ConnectedTime to the +; current time: in the likely case it prevents usb_hub_process_deferred from immediate +; issuing of another requests (which would be just waste of time); +; in the unlikely case it is still correct (although slightly increases +; the debounce interval). + cmp dword [edx+ecx*4], -1 + jz .nothing + call usb_hub_store_connected_time + jmp .nothing +.reset: +; 5. The device remained connected for the entire debounce interval; +; we can proceed with initialization. +; Clear connected time for this port and notify usb_hub_process_deferred that +; the new port is waiting for reset. + or dword [edx+ecx*4], -1 + or [eax+usb_hub.Actions], HUB_RESET_IN_PROGRESS + HUB_RESET_WAITING +.nothing: + pop esi ; restore used register to be stdcall + ret +endp + +; This procedure is called when the request to query port status +; initiated by usb_hub_process_deferred for testing reset status is completed, +; either successfully or unsuccessfully. +proc usb_hub_resetting_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; 1. If the request has failed, do nothing. + cmp [status], 0 + jnz .nothing +; 2. If reset signalling is still active, do nothing. + mov eax, [calldata] +; movzx ecx, [eax+usb_hub.ConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : hub %x port %d ResetStatusData = %x change = %x\n',eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4 + test byte [eax+usb_hub.ResetStatusData], 1 shl PORT_RESET + jnz .nothing +; 3. Store the current time to start reset recovery interval +; and clear 'reset signalling active' flag. + mov edx, [timer_ticks] + mov [eax+usb_hub.ResetTime], edx + and [eax+usb_hub.Actions], not HUB_RESET_SIGNAL +; 4. If the device has not been disconnected, set 'reset recovery active' bit. +; Otherwise, terminate reset process. + test byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION + jnz .disconnected + or [eax+usb_hub.Actions], HUB_RESET_RECOVERY +.common: +; In any case, clear change of resetting status. + lea edx, [eax+usb_hub.ConfigBuffer] + mov cl, C_PORT_RESET + call usb_hub_clear_port_change.buffer +.nothing: + ret +.disconnected: + call usb_hub_reset_aborted + jmp .common +endp + +; Helper procedure for usb_hub_process_deferred. Initiates reset signalling +; on the current port (given by 1-based value [ConfigBuffer+4]). +; esi -> usb_hub, eax -> usb_controller +proc usb_hub_initiate_reset +; 1. Store hub+port data in the controller structure. + movzx ecx, [esi+usb_hub.ConfigBuffer+4] + dec ecx + mov [eax+usb_controller.ResettingPort], cl + mov [eax+usb_controller.ResettingHub], esi +; 2. Store the current time and set 'reset signalling active' flag. + mov eax, [timer_ticks] + mov [esi+usb_hub.ResetTime], eax + and [esi+usb_hub.Actions], not HUB_RESET_WAITING + or [esi+usb_hub.Actions], HUB_RESET_SIGNAL +; 3. Send request to the hub to initiate request signalling. + lea edx, [esi+usb_hub.ConfigBuffer] +; DEBUGF 1,'K : [%d] hub %x port %d initiate reset\n',[timer_ticks],esi,ecx + mov dword [edx], 23h + \ + (USB_SET_FEATURE shl 8) + \ + (PORT_RESET shl 16) + and dword [edx+4], 0xFF + stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_reset_started, esi, 0 + test eax, eax + jnz @f + mov eax, esi + call usb_hub_reset_aborted +@@: + ret +endp + +; This procedure is called when the request to start reset signalling initiated +; by usb_hub_initiate_reset is completed, either successfully or unsuccessfully. +proc usb_hub_reset_started stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; If the request is successful, do nothing. +; Otherwise, clear 'reset signalling' flag and abort reset process. + mov eax, [calldata] +; movzx ecx, [eax+usb_hub.ConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : [%d] hub %x port %d reset started\n',[timer_ticks],eax,ecx + cmp [status], 0 + jz .nothing + and [eax+usb_hub.Actions], not HUB_RESET_SIGNAL + dbgstr 'Failed to reset hub port' + call usb_hub_reset_aborted +.nothing: + ret +endp + +; This procedure is called by the protocol layer if something has failed during +; initial stages of the configuration process, so the device should be disabled +; at hub level. +proc usb_hub_disable_resetting_port + and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS +; movzx ecx, [eax+usb_hub.ConfigBuffer+4] +; dec ecx +; DEBUGF 1,'K : [%d] hub %x port %d disable\n',[timer_ticks],eax,ecx + lea edx, [eax+usb_hub.ConfigBuffer] + mov cl, PORT_ENABLE + jmp usb_hub_clear_port_change.buffer +endp + +; This procedure is called when the hub is disconnected. +proc usb_hub_disconnect +virtual at esp + dd ? ; return address +.hubdata dd ? +end virtual +; 1. If the hub is disconnected during initial configuration, +; 1 is stored as hub data and there is nothing to do. + mov eax, [.hubdata] + cmp eax, 1 + jz .nothing +; 2. Remove the hub from the overall list. + mov ecx, [eax+usb_hub.Next] + mov edx, [eax+usb_hub.Prev] + mov [ecx+usb_hub.Prev], edx + mov [edx+usb_hub.Next], ecx +; 3. If some child is in reset process, abort reset. + push esi + mov esi, [eax+usb_hub.Controller] + cmp [esi+usb_controller.ResettingHub], eax + jnz @f + cmp [esi+usb_controller.ResettingPort], -1 + jz @f + push eax + call usb_test_pending_port + pop eax +@@: + pop esi +; 4. Loop over all children and notify other code that they were disconnected. + push ebx + xor ecx, ecx +.disconnect_children: + mov ebx, [eax+usb_hub.ConnectedDevicesPtr] + mov ebx, [ebx+ecx*4] + test ebx, ebx + jz @f + push eax ecx + call usb_device_disconnected + pop ecx eax +@@: + inc ecx + cmp ecx, [eax+usb_hub.NumPorts] + jb .disconnect_children +; 4. Free memory allocated for the hub data. + call free + pop ebx +.nothing: + retn 4 +endp diff --git a/kernel/trunk/bus/usb/init.inc b/kernel/trunk/bus/usb/init.inc new file mode 100644 index 0000000000..20eb6d72e5 --- /dev/null +++ b/kernel/trunk/bus/usb/init.inc @@ -0,0 +1,250 @@ +; Initialization of the USB subsystem. +; Provides usb_init procedure, includes all needed files. + +; General notes: +; * There is one entry point for external kernel code: usb_init is called +; from initialization code and initializes USB subsystem. +; * There are several entry points for API; see the docs for description. +; * There are several functions which are called from controller-specific +; parts of USB subsystem. The most important is usb_new_device, +; which is called when a new device has been connected (over some time), +; has been reset and is ready to start configuring. +; * IRQ handlers are very restricted. They can not take any locks, +; since otherwise a deadlock is possible: imagine that a code has taken the +; lock and was interrupted by IRQ handler. Now IRQ handler would wait for +; releasing the lock, and a lock owner would wait for exiting IRQ handler +; to get the control. +; * Thus, there is the special USB thread which processes almost all activity. +; IRQ handlers do the minimal processing and wake this thread. +; * Also the USB thread wakes occasionally to process tasks which can be +; predicted without interrupts. These include e.g. a periodic roothub +; scanning in UHCI and initializing in USB_CONNECT_DELAY ticks +; after connecting a new device. +; * The main procedure of USB thread, usb_thread_proc, does all its work +; by querying usb_hardware_func.ProcessDeferred for every controller +; and usb_hub_process_deferred for every hub. +; ProcessDeferred does controller-specific actions and calculates the time +; when it should be invoked again, possibly infinite. +; usb_thread_proc selects the minimum from all times returned by +; ProcessDeferred and sleeps until this moment is reached or the thread +; is awakened by IRQ handler. + +; Initializes the USB subsystem. +proc usb_init +; 1. Initialize all locks. + mov ecx, usb_controllers_list_mutex + call mutex_init + mov ecx, usb1_ep_mutex + call mutex_init + mov ecx, usb_gtd_mutex + call mutex_init + mov ecx, ehci_ep_mutex + call mutex_init + mov ecx, ehci_gtd_mutex + call mutex_init +; 2. Kick off BIOS from all USB controllers, calling the corresponding function +; *hci_kickoff_bios. Also count USB controllers for the next step. +; Note: USB1 companion(s) must go before the corresponding EHCI controller, +; otherwise BIOS could see a device moving from EHCI to a companion; +; first, this always wastes time; +; second, some BIOSes are buggy, do not expect that move and try to refer to +; previously-assigned controller instead of actual; sometimes that leads to +; hangoff. +; Thus, process controllers in PCI order. + mov esi, pcidev_list + push 0 +.kickoff: + mov esi, [esi+PCIDEV.fd] + cmp esi, pcidev_list + jz .done_kickoff + cmp word [esi+PCIDEV.class+1], 0x0C03 + jnz .kickoff + mov eax, uhci_kickoff_bios + cmp byte [esi+PCIDEV.class], 0x00 + jz .do_kickoff + mov eax, ohci_kickoff_bios + cmp byte [esi+PCIDEV.class], 0x10 + jz .do_kickoff + mov eax, ehci_kickoff_bios + cmp byte [esi+PCIDEV.class], 0x20 + jnz .kickoff +.do_kickoff: + inc dword [esp] + call eax + jmp .kickoff +.done_kickoff: + pop eax +; 3. If no controllers were found, exit. +; Otherwise, run the USB thread. + test eax, eax + jz .nothing + call create_usb_thread + jz .nothing +; 4. Initialize all USB controllers, calling usb_init_controller for each. +; Note: USB1 companion(s) should go before the corresponding EHCI controller, +; although this is not strictly necessary (this way, a companion would not try +; to initialize high-speed device only to see a disconnect when EHCI takes +; control). +; Thus, process all EHCI controllers in the first loop, all USB1 controllers +; in the second loop. (One loop in reversed PCI order could also be used, +; but seems less natural.) +; 4a. Loop over all PCI devices, call usb_init_controller +; for all EHCI controllers. + mov eax, pcidev_list +.scan_ehci: + mov eax, [eax+PCIDEV.fd] + cmp eax, pcidev_list + jz .done_ehci + cmp [eax+PCIDEV.class], 0x0C0320 + jnz .scan_ehci + mov edi, ehci_hardware_func + call usb_init_controller + jmp .scan_ehci +.done_ehci: +; 4b. Loop over all PCI devices, call usb_init_controller +; for all UHCI and OHCI controllers. + mov eax, pcidev_list +.scan_usb1: + mov eax, [eax+PCIDEV.fd] + cmp eax, pcidev_list + jz .done_usb1 + mov edi, uhci_hardware_func + cmp [eax+PCIDEV.class], 0x0C0300 + jz @f + mov edi, ohci_hardware_func + cmp [eax+PCIDEV.class], 0x0C0310 + jnz .scan_usb1 +@@: + call usb_init_controller + jmp .scan_usb1 +.done_usb1: +.nothing: + ret +endp + +uglobal +align 4 +usb_event dd ? +endg + +; Helper function for usb_init. Creates and initializes the USB thread. +proc create_usb_thread +; 1. Create the thread. + push edi + push 1 + pop ebx + mov ecx, usb_thread_proc + xor edx, edx + call new_sys_threads + pop edi +; If failed, say something to the debug board and return with ZF set. + test eax, eax + jns @f + DEBUGF 1,'K : cannot create kernel thread for USB, error %d\n',eax +.clear: + xor eax, eax + jmp .nothing +@@: +; 2. Wait while the USB thread initializes itself. +@@: + call change_task + cmp [usb_event], 0 + jz @b +; 3. If initialization failed, the USB thread sets [usb_event] to -1. +; Return with ZF set or cleared corresponding to the result. + cmp [usb_event], -1 + jz .clear +.nothing: + ret +endp + +; Helper function for IRQ handlers. Wakes the USB thread if ebx is nonzero. +proc usb_wakeup_if_needed + test ebx, ebx + jz usb_wakeup.nothing +usb_wakeup: + mov [check_idle_semaphore], 5 ; we really, really need a normal scheduler + xor edx, edx + mov eax, [usb_event] + mov ebx, [eax+EVENT.id] + xor esi, esi + call raise_event +.nothing: + ret +endp + +; Main loop of the USB thread. +proc usb_thread_proc +; 1. Initialize: create event to allow wakeup by interrupt handlers. + xor esi, esi + mov ecx, MANUAL_DESTROY + call create_event + test eax, eax + jnz @f +; If failed, set [usb_event] to -1 and terminate myself. + dbgstr 'cannot create event for USB thread' + or [usb_event], -1 + jmp sys_end +@@: + mov [usb_event], eax + push -1 ; initial timeout: infinite +usb_thread_wait: +; 2. Main loop: wait for either wakeup event or timeout. + pop ecx ; get timeout + mov eax, [usb_event] + mov ebx, [eax+EVENT.id] + call wait_event_timeout + push -1 ; default timeout: infinite +; 3. Main loop: call worker functions of all controllers; +; if some function schedules wakeup in timeout less than the current value, +; replace that value with the returned timeout. + mov esi, usb_controllers_list +@@: + mov esi, [esi+usb_controller.Next] + cmp esi, usb_controllers_list + jz .controllers_done + mov eax, [esi+usb_controller.HardwareFunc] + call [eax+usb_hardware_func.ProcessDeferred] + cmp [esp], eax + jb @b + mov [esp], eax + jmp @b +.controllers_done: +; 4. Main loop: call hub worker function for all hubs, +; similarly calculating minimum of all returned timeouts. +; When done, continue to 2. + mov esi, usb_hubs_list +@@: + mov esi, [esi+usb_hub.Next] + cmp esi, usb_hubs_list + jz usb_thread_wait + call usb_hub_process_deferred + cmp [esp], eax + jb @b + mov [esp], eax + jmp @b +endp + +iglobal +align 4 +usb_controllers_list: + dd usb_controllers_list + dd usb_controllers_list +usb_hubs_list: + dd usb_hubs_list + dd usb_hubs_list +endg +uglobal +align 4 +usb_controllers_list_mutex MUTEX +endg + +include "memory.inc" +include "hccommon.inc" +include "pipe.inc" +include "ohci.inc" +include "uhci.inc" +include "ehci.inc" +include "protocol.inc" +include "hub.inc" +include "scheduler.inc" diff --git a/kernel/trunk/bus/usb/memory.inc b/kernel/trunk/bus/usb/memory.inc new file mode 100644 index 0000000000..a0b9565f27 --- /dev/null +++ b/kernel/trunk/bus/usb/memory.inc @@ -0,0 +1,215 @@ +; Memory management for USB structures. +; Protocol layer uses the common kernel heap malloc/free. +; Hardware layer has special requirements: +; * memory blocks should be properly aligned +; * memory blocks should not cross page boundary +; Hardware layer allocates fixed-size blocks. +; Thus, the specific allocator is quite easy to write: +; allocate one page, split into blocks, maintain the single-linked +; list of all free blocks in each page. + +; Note: size must be a multiple of required alignment. + +; Data for one pool: dd pointer to the first page, MUTEX lock. + +uglobal +; Structures in UHCI and OHCI have equal sizes. +; Thus, functions and data for allocating/freeing can be shared; +; we keep them here rather than in controller-specific files. +align 4 +; Data for UHCI and OHCI endpoints pool. +usb1_ep_first_page dd ? +usb1_ep_mutex MUTEX +; Data for UHCI and OHCI general transfer descriptors pool. +usb_gtd_first_page dd ? +usb_gtd_mutex MUTEX +endg + +; sanity check: structures in UHCI and OHCI should be the same for allocation +if (sizeof.ohci_pipe=sizeof.uhci_pipe)&(ohci_pipe.SoftwarePart=uhci_pipe.SoftwarePart) + +; Allocates one endpoint structure for UHCI/OHCI. +; Returns pointer to software part (usb_pipe) in eax. +proc usb1_allocate_endpoint + push ebx + mov ebx, usb1_ep_mutex + stdcall usb_allocate_common, sizeof.ohci_pipe + test eax, eax + jz @f + add eax, ohci_pipe.SoftwarePart +@@: + pop ebx + ret +endp + +; Free one endpoint structure for UHCI/OHCI. +; Stdcall with one argument, pointer to software part (usb_pipe). +proc usb1_free_endpoint + sub dword [esp+4], ohci_pipe.SoftwarePart + jmp usb_free_common +endp + +else +; sanity check continued +.err allocate_endpoint/free_endpoint must be different for OHCI and UHCI +end if + +; sanity check: structures in UHCI and OHCI should be the same for allocation +if (sizeof.ohci_gtd=sizeof.uhci_gtd)&(ohci_gtd.SoftwarePart=uhci_gtd.SoftwarePart) + +; Allocates one general transfer descriptor structure for UHCI/OHCI. +; Returns pointer to software part (usb_gtd) in eax. +proc usb1_allocate_general_td + push ebx + mov ebx, usb_gtd_mutex + stdcall usb_allocate_common, sizeof.ohci_gtd + test eax, eax + jz @f + add eax, ohci_gtd.SoftwarePart +@@: + pop ebx + ret +endp + +; Free one general transfer descriptor structure for UHCI/OHCI. +; Stdcall with one argument, pointer to software part (usb_gtd). +proc usb1_free_general_td + sub dword [esp+4], ohci_gtd.SoftwarePart + jmp usb_free_common +endp + +else +; sanity check continued +.err allocate_general_td/free_general_td must be different for OHCI and UHCI +end if + +; Allocator for fixed-size blocks: allocate a block. +; [ebx-4] = pointer to the first page, ebx = pointer to MUTEX structure. +proc usb_allocate_common + push edi ; save used register to be stdcall +virtual at esp + dd ? ; saved edi + dd ? ; return address +.size dd ? +end virtual +; 1. Take the lock. + mov ecx, ebx + call mutex_lock +; 2. Find the first allocated page with a free block, if any. +; 2a. Initialize for the loop. + mov edx, ebx +.pageloop: +; 2b. Get the next page, keeping the current in eax. + mov eax, edx + mov edx, [edx-4] +; 2c. If there is no next page, we're out of luck; go to 4. + test edx, edx + jz .newpage + add edx, 0x1000 +@@: +; 2d. Get the pointer to the first free block on this page. +; If there is no free block, continue to 2b. + mov eax, [edx-8] + test eax, eax + jz .pageloop +; 2e. Get the pointer to the next free block. + mov ecx, [eax] +; 2f. Update the pointer to the first free block from eax to ecx. +; Normally [edx-8] still contains eax, if so, atomically set it to ecx +; and proceed to 3. +; However, the price of simplicity of usb_free_common (in particular, it +; doesn't take the lock) is that [edx-8] could (rarely) be changed while +; we processed steps 2d+2e. If so, return to 2d and retry. + lock cmpxchg [edx-8], ecx + jnz @b +.return: +; 3. Release the lock taken in step 1 and return. + push eax + mov ecx, ebx + call mutex_unlock + pop eax + pop edi ; restore used register to be stdcall + ret 4 +.newpage: +; 4. Allocate a new page. + push eax + stdcall kernel_alloc, 0x1000 + pop edx +; If failed, say something to the debug board and return zero. + test eax, eax + jz .nomemory +; 5. Add the new page to the tail of list of allocated pages. + mov [edx-4], eax +; 6. Initialize two service dwords in the end of page: +; first free block is (start of page) + (block size) +; (we will return first block at (start of page), so consider it allocated), +; no next page. + mov edx, eax + lea edi, [eax+0x1000-8] + add edx, [.size] + mov [edi], edx + and dword [edi+4], 0 +; 7. All blocks starting from edx are free; join them in a single-linked list. +@@: + mov ecx, edx + add edx, [.size] + mov [ecx], edx + cmp edx, edi + jbe @b + sub ecx, [.size] + and dword [ecx], 0 +; 8. Return (start of page). + jmp .return +.nomemory: + dbgstr 'no memory for USB descriptor' + xor eax, eax + jmp .return +endp + +; Allocator for fixed-size blocks: free a block. +proc usb_free_common + push ecx edx +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.block dd ? +end virtual +; Insert the given block to the head of free blocks in this page. + mov ecx, [.block] + mov edx, ecx + or edx, 0xFFF +@@: + mov eax, [edx+1-8] + mov [ecx], eax + lock cmpxchg [edx+1-8], ecx + jnz @b + pop edx ecx + ret 4 +endp + +; Helper procedure for OHCI: translate physical address in ecx +; of some transfer descriptor to linear address. +proc usb_td_to_virt +; Traverse all pages used for transfer descriptors, looking for the one +; with physical address as in ecx. + mov eax, [usb_gtd_first_page] +@@: + test eax, eax + jz .zero + push eax + call get_pg_addr + sub eax, ecx + jz .found + cmp eax, -0x1000 + ja .found + pop eax + mov eax, [eax+0x1000-4] + jmp @b +.found: +; When found, combine page address from eax with page offset from ecx. + pop eax + and ecx, 0xFFF + add eax, ecx +.zero: + ret +endp diff --git a/kernel/trunk/bus/usb/ohci.inc b/kernel/trunk/bus/usb/ohci.inc new file mode 100644 index 0000000000..f08ba4124a --- /dev/null +++ b/kernel/trunk/bus/usb/ohci.inc @@ -0,0 +1,1601 @@ +; Code for OHCI controllers. +; Note: it should be moved to an external driver, +; it was convenient to have this code compiled into the kernel during initial +; development, but there are no reasons to keep it here. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; OHCI register declarations +; All of the registers should be read and written as Dwords. +; Partition 1. Control and Status registers. +OhciRevisionReg = 0 +OhciControlReg = 4 +OhciCommandStatusReg = 8 +OhciInterruptStatusReg = 0Ch +OhciInterruptEnableReg = 10h +OhciInterruptDisableReg = 14h +; Partition 2. Memory Pointer registers. +OhciHCCAReg = 18h +OhciPeriodCurrentEDReg = 1Ch +OhciControlHeadEDReg = 20h +OhciControlCurrentEDReg = 24h +OhciBulkHeadEDReg = 28h +OhciBulkCurrentEDReg = 2Ch +OhciDoneHeadReg = 30h +; Partition 3. Frame Counter registers. +OhciFmIntervalReg = 34h +OhciFmRemainingReg = 38h +OhciFmNumberReg = 3Ch +OhciPeriodicStartReg = 40h +OhciLSThresholdReg = 44h +; Partition 4. Root Hub registers. +OhciRhDescriptorAReg = 48h +OhciRhDescriptorBReg = 4Ch +OhciRhStatusReg = 50h +OhciRhPortStatusReg = 54h + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= + +; OHCI-specific part of a pipe descriptor. +; * This structure corresponds to the Endpoint Descriptor aka ED from the OHCI +; specification. +; * The hardware requires 16-bytes alignment of the hardware part. +; Since the allocator (usb_allocate_common) allocates memory sequentially +; from page start (aligned on 0x1000 bytes), size of the structure must be +; divisible by 16. +struct ohci_pipe +; All addresses are physical. +Flags dd ? +; 1. Lower 7 bits (bits 0-6) are FunctionAddress. This is the USB address of +; the function containing the endpoint that this ED controls. +; 2. Next 4 bits (bits 7-10) are EndpointNumber. This is the USB address of +; the endpoint within the function. +; 3. Next 2 bits (bits 11-12) are Direction. This 2-bit field indicates the +; direction of data flow: 1 = IN, 2 = OUT. If neither IN nor OUT is +; specified, then the direction is determined from the PID field of the TD. +; For CONTROL endpoints, the transfer direction is different +; for different transfers, so the value of this field is 0 +; (3 would have the same effect) and the actual direction +; of one transfer is encoded in the Transfer Descriptor. +; 4. Next bit (bit 13) is Speed bit. It indicates the speed of the endpoint: +; full-speed (S = 0) or low-speed (S = 1). +; 5. Next bit (bit 14) is sKip bit. When this bit is set, the hardware +; continues on to the next ED on the list without attempting access +; to the TD queue or issuing any USB token for the endpoint. +; Always cleared. +; 6. Next bit (bit 15) is Format bit. It must be 0 for Control, Bulk and +; Interrupt endpoints and 1 for Isochronous endpoints. +; 7. Next 11 bits (bits 16-26) are MaximumPacketSize. This field indicates +; the maximum number of bytes that can be sent to or received from the +; endpoint in a single data packet. +TailP dd ? +; Physical address of the tail descriptor in the TD queue. +; The descriptor itself is not in the queue. See also HeadP. +HeadP dd ? +; 1. First bit (bit 0) is Halted bit. This bit is set by the hardware to +; indicate that processing of the TD queue on the endpoint is halted. +; 2. Second bit (bit 1) is toggleCarry bit. Whenever a TD is retired, this +; bit is written to contain the last data toggle value from the retired TD. +; 3. Next two bits (bits 2-3) are reserved and always zero. +; 4. With masked 4 lower bits, this is HeadP itself: physical address of the +; head descriptor in the TD queue, that is, next TD to be processed for this +; endpoint. Note that a TD must be 16-bytes aligned. +; Empty queue is characterized by the condition HeadP == TailP. +NextED dd ? +; If nonzero, then this entry is a physical address of the next ED to be +; processed. See also the description before NextVirt field of the usb_pipe +; structure. Additionally to that description, the following is specific for +; the OHCI controller: +; * n=5, N=32, there are 32 "leaf" periodic lists. +; * The 1ms periodic list also serves Isochronous endpoints, which should be +; in the end of the list. +; * There is no "next" list for Bulk and Control lists, they are processed +; separately from others. +; * There is no "next" list for Periodic list for 1ms interval. +SoftwarePart rd sizeof.usb_pipe/4 +; Software part, common for all controllers. +ends + +if sizeof.ohci_pipe mod 16 +.err ohci_pipe must be 16-bytes aligned +end if + +; This structure describes the static head of every list of pipes. +; The hardware requires 16-bytes alignment of this structure. +; All instances of this structure are located sequentially in uhci_controller, +; uhci_controller is page-aligned, so it is sufficient to make this structure +; 16-bytes aligned and verify that the first instance is 16-bytes aligned +; inside uhci_controller. +struct ohci_static_ep +Flags dd ? +; Same as ohci_pipe.Flags. +; sKip bit is set, so the hardware ignores other fields except NextED. + dd ? +; Corresponds to ohci_pipe.TailP. Not used. +NextList dd ? +; Virtual address of the next list. +NextED dd ? +; Same as ohci_pipe.NextED. +SoftwarePart rd sizeof.usb_static_ep/4 +; Software part, common for all controllers. + dd ? +; Padding for 16-bytes alignment. +ends + +if sizeof.ohci_static_ep mod 16 +.err ohci_static_ep must be 16-bytes aligned +end if + +; OHCI-specific part of controller data. +; * The structure describes the memory area used for controller data, +; additionally to the registers of the controller. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 256 bytes and corresponds to +; the HCCA from OHCI specification. +; * The hardware requires 256-bytes alignment of the hardware part, so +; the entire descriptor must be 256-bytes aligned. +; This structure is allocated with kernel_alloc (see usb_init_controller), +; this gives page-aligned data. +; * The controller is described by both ohci_controller and usb_controller +; structures, for each controller there is one ohci_controller and one +; usb_controller structure. These structures are located sequentially +; in the memory: beginning from some page start, there is ohci_controller +; structure - this enforces hardware alignment requirements - and then +; usb_controller structure. +; * The code keeps pointer to usb_controller structure. The ohci_controller +; structure is addressed as [ptr + ohci_controller.field - sizeof.ohci_controller]. +struct ohci_controller +; ------------------------------ hardware fields ------------------------------ +InterruptTable rd 32 +; Pointers to interrupt EDs. The hardware starts processing of periodic lists +; within the frame N from the ED pointed to by [InterruptTable+(N and 31)*4]. +; See also the description of periodic lists inside ohci_pipe structure. +FrameNumber dw ? +; The current frame number. This field is written by hardware only. +; This field is read by ohci_process_deferred and ohci_irq to +; communicate when control/bulk processing needs to be temporarily +; stopped/restarted. + dw ? +; Padding. Written as zero at every update of FrameNumber. +DoneHead dd ? +; Physical pointer to the start of Done Queue. +; When the hardware updates this field, it sets bit 0 to one if there is +; unmasked interrupt pending. + rb 120 +; Reserved for the hardware. +; ------------------------------ software fields ------------------------------ +IntEDs ohci_static_ep + rb 62 * sizeof.ohci_static_ep +; Heads of 63 Periodic lists, see the description in usb_pipe. +ControlED ohci_static_ep +; Head of Control list, see the description in usb_pipe. +BulkED ohci_static_ep +; Head of Bulk list, see the description in usb_pipe. +MMIOBase dd ? +; Virtual address of memory-mapped area with OHCI registers OhciXxxReg. +PoweredUp db ? +; 1 in normal work, 0 during early phases of the initialization. +; This field is initialized to zero during memory allocation +; (see usb_init_controller), set to one by ohci_init when ports of the root hub +; are powered up, so connect/disconnect events can be handled. + rb 3 ; alignment +DoneList dd ? +; List of descriptors which were processed by the controller and now need +; to be finalized. +DoneListEndPtr dd ? +; Pointer to dword which should receive a pointer to the next item in DoneList. +; If DoneList is empty, this is a pointer to DoneList itself; +; otherwise, this is a pointer to NextTD field of the last item in DoneList. +ends + +if ohci_controller.IntEDs mod 16 +.err Static endpoint descriptors must be 16-bytes aligned inside ohci_controller +end if + +; OHCI general transfer descriptor. +; * The structure describes transfers to be performed on Control, Bulk or +; Interrupt endpoints. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 16 bytes and corresponds to +; the General Transfer Descriptor aka general TD from OHCI specification. +; * The hardware requires 16-bytes alignment of the hardware part, so +; the entire descriptor must be 16-bytes aligned. Since the allocator +; (usb_allocate_common) allocates memory sequentially from page start +; (aligned on 0x1000 bytes), size of the structure must be divisible by 16. +struct ohci_gtd +; ------------------------------ hardware fields ------------------------------ +; All addresses in this part are physical. +Flags dd ? +; 1. Lower 18 bits (bits 0-17) are ignored and not modified by the hardware. +; 2. Next bit (bit 18) is bufferRounding bit. If this bit is 0, then the last +; data packet must exactly fill the defined data buffer. If this bit is 1, +; then the last data packet may be smaller than the defined buffer without +; causing an error condition on the TD. +; 3. Next 2 bits (bits 19-20) are Direction field. This field indicates the +; direction of data flow. If the Direction field in the ED is OUT or IN, +; this field is ignored and the direction from the ED is used instead. +; Otherwise, 0 = SETUP, 1 = OUT, 2 = IN, 3 is reserved. +; 4. Next 3 bits (bits 21-23) are DelayInterrupt field. This field contains +; the interrupt delay count for this TD. When a TD is complete, the hardware +; may wait up to DelayInterrupt frames before generating an interrupt. +; If DelayInterrupt is 7 (maximum possible), then there is no interrupt +; associated with completion of this TD. +; 5. Next 2 bits (bits 24-25) are DataToggle field. This field is used to +; generate/compare the data PID value (DATA0 or DATA1). It is updated after +; each successful transmission/reception of a data packet. The bit 25 +; is 0 when the data toggle value is acquired from the toggleCarry field in +; the ED and 1 when the data toggle value is taken from the bit 24. +; 6. Next 2 bits (bits 26-27) are ErrorCount field. For each transmission +; error, this value is incremented. If ErrorCount is 2 and another error +; occurs, the TD is retired with error. When a transaction completes without +; error, ErrorCount is reset to 0. +; 7. Upper 4 bits (bits 28-31) are ConditionCode field. This field contains +; the status of the last attempted transaction, one of USB_STATUS_* values. +CurBufPtr dd ? +; Physical address of the next memory location that will be accessed for +; transfer to/from the endpoint. 0 means zero-length data packet or that all +; bytes have been transferred. +NextTD dd ? +; This field has different meanings depending on the status of the descriptor. +; When the descriptor is queued for processing, but not yet processed: +; Physical address of the next TD for the endpoint. +; When the descriptor is processed by hardware, but not yet by software: +; Physical address of the previous processed TD. +; When the descriptor is processed by the IRQ handler, but not yet completed: +; Virtual pointer to the next processed TD. +BufEnd dd ? +; Physical address of the last byte in the buffer for this TD. + dd ? ; padding for 16-bytes alignment +SoftwarePart rd sizeof.usb_gtd/4 +; Common part for all controllers. +ends + +if sizeof.ohci_gtd mod 16 +.err ohci_gtd must be 16-bytes aligned +end if + +; OHCI isochronous transfer descriptor. +; * The structure describes transfers to be performed on Isochronous endpoints. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 32 bytes and corresponds to +; the Isochronous Transfer Descriptor aka isochronous TD from OHCI +; specification. +; * The hardware requires 32-bytes alignment of the hardware part, so +; the entire descriptor must be 32-bytes aligned. +; * The isochronous endpoints are not supported yet, so only hardware part is +; defined at the moment. +struct ohci_itd +StartingFrame dw ? +; This field contains the low order 16 bits of the frame number in which the +; first data packet of the Isochronous TD is to be sent. +Flags dw ? +; 1. Lower 5 bits (bits 0-4) are ignored and not modified by the hardware. +; 2. Next 3 bits (bits 5-7) are DelayInterrupt field. +; 3. Next 3 bits (bits 8-10) are FrameCount field. The TD describes +; FrameCount+1 data packets. +; 4. Next bit (bit 11) is ignored and not modified by the hardware. +; 5. Upper 4 bits (bits 12-15) are ConditionCode field. This field contains +; the completion code, one of USB_STATUS_* values, when the TD is moved to +; the Done Queue. +BufPage0 dd ? +; Lower 12 bits are ignored and not modified by the hardware. +; With masked 12 bits this field is the physical page containing all buffers. +NextTD dd ? +; Physical address of the next TD in the transfer queue. +BufEnd dd ? +; Physical address of the last byte in the buffer. +OffsetArray rw 8 +; Initialized by software, read by hardware: Offset for packet 0..7. +; Used to determine size and starting address of an isochronous data packet. +; Written by hardware, read by software: PacketStatusWord for packet 0..7. +; Contains completion code and, if applicable, size received for an isochronous +; data packet. +ends + +; Description of OHCI-specific data and functions for +; controller-independent code. +; Implements the structure usb_hardware_func from hccommon.inc for OHCI. +iglobal +align 4 +ohci_hardware_func: + dd 'OHCI' + dd sizeof.ohci_controller + dd ohci_init + dd ohci_process_deferred + dd ohci_set_device_address + dd ohci_get_device_address + dd ohci_port_disable + dd ohci_new_port.reset + dd ohci_set_endpoint_packet_size + dd usb1_allocate_endpoint + dd usb1_free_endpoint + dd ohci_init_pipe + dd ohci_unlink_pipe + dd usb1_allocate_general_td + dd usb1_free_general_td + dd ohci_alloc_transfer + dd ohci_insert_transfer + dd ohci_new_device +endg + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +; Controller-specific initialization function. +; Called from usb_init_controller. Initializes the hardware and +; OHCI-specific parts of software structures. +; eax = pointer to ohci_controller to be initialized +; [ebp-4] = pcidevice +proc ohci_init +; inherit some variables from the parent (usb_init_controller) +.devfn equ ebp - 4 +.bus equ ebp - 3 +; 1. Store pointer to ohci_controller for further use. + push eax + mov edi, eax +; 2. Initialize hardware fields of ohci_controller. +; Namely, InterruptTable needs to be initialized with +; physical addresses of heads of first 32 Periodic lists. +; Note that all static heads fit in one page, so one call +; to get_pg_addr is sufficient. +if (ohci_controller.IntEDs / 0x1000) <> (ohci_controller.BulkED / 0x1000) +.err assertion failed +end if +if ohci_controller.IntEDs >= 0x1000 +.err assertion failed +end if + lea esi, [eax+ohci_controller.IntEDs+32*sizeof.ohci_static_ep] + call get_pg_addr + add eax, ohci_controller.IntEDs + push 32 + pop ecx + mov edx, ecx +@@: + stosd + add eax, sizeof.ohci_static_ep + loop @b +; 3. Initialize static heads ohci_controller.IntEDs, .ControlED, .BulkED. +; Use the loop over groups: first group consists of first 32 Periodic +; descriptors, next group consists of next 16 Periodic descriptors, +; ..., last group consists of the last Periodic descriptor. +; 3a. Prepare for the loop. +; make edi point to start of ohci_controller.IntEDs, +; other registers are already set. +; -128 fits in one byte, +128 does not fit. + sub edi, -128 +; 3b. Loop over groups. On every iteration: +; edx = size of group, edi = pointer to the current group, +; esi = pointer to the next group, eax = physical address of the next group. +.init_static_eds: +; 3c. Get the size of the next group. + shr edx, 1 +; 3d. Exit the loop if there is no next group. + jz .init_static_eds_done +; 3e. Initialize the first half of the current group. +; Advance edi to the second half. + push eax esi + call ohci_init_static_ep_group + pop esi eax +; 3f. Initialize the second half of the current group +; with the same values. +; Advance edi to the next group, esi/eax to the next of the next group. + call ohci_init_static_ep_group + jmp .init_static_eds +.init_static_eds_done: +; 3g. Initialize the head of the last Periodic list. + xor eax, eax + xor esi, esi + call ohci_init_static_endpoint +; 3i. Initialize the heads of Control and Bulk lists. + call ohci_init_static_endpoint + call ohci_init_static_endpoint +; 4. Create a virtual memory area to talk with the controller. +; 4a. Enable memory & bus master access. + mov ch, [.bus] + mov cl, 0 + mov eax, ecx + mov bh, [.devfn] + mov bl, 4 + call pci_read_reg + or al, 6 + xchg eax, ecx + call pci_write_reg +; 4b. Read memory base address. + mov ah, [.bus] + mov al, 2 + mov bl, 10h + call pci_read_reg + and al, not 0Fh +; 4c. Create mapping for physical memory. 256 bytes are sufficient. + stdcall map_io_mem, eax, 100h, PG_SW+PG_NOCACHE + test eax, eax + jz .fail + stosd ; fill ohci_controller.MMIOBase + xchg eax, edi +; now edi = MMIOBase +; 5. Reset the controller if needed. +; 5a. Check operational state. +; 0 = reset, 1 = resume, 2 = operational, 3 = suspended + mov eax, [edi+OhciControlReg] + and al, 3 shl 6 + cmp al, 2 shl 6 + jz .operational +; 5b. State is not operational, reset is needed. +.reset: +; 5c. Save FmInterval register. + pushd [edi+OhciFmIntervalReg] +; 5d. Issue software reset and wait up to 10ms, checking status every 1 ms. + push 1 + pop ecx + push 10 + pop edx + mov [edi+OhciCommandStatusReg], ecx +@@: + mov esi, ecx + call delay_ms + test [edi+OhciCommandStatusReg], ecx + jz .resetdone + dec edx + jnz @b + pop eax + dbgstr 'controller reset timeout' + jmp .fail_unmap +.resetdone: +; 5e. Restore FmInterval register. + pop eax + mov edx, eax + and edx, 3FFFh + jz .setfminterval + cmp dx, 2EDFh ; default value? + jnz @f ; assume that BIOS has configured the value +.setfminterval: + mov eax, 27792EDFh ; default value +@@: + mov [edi+OhciFmIntervalReg], eax +; 5f. Set PeriodicStart to 90% of FmInterval. + movzx eax, ax +; Two following lines are equivalent to eax = floor(eax * 0.9) +; for any 0 <= eax < 1C71C71Dh, which of course is far from maximum 0FFFFh. + mov edx, 0E6666667h + mul edx + mov [edi+OhciPeriodicStartReg], edx +.operational: +; 6. Setup controller registers. + pop esi ; restore pointer to ohci_controller saved in step 1 +; 6a. Physical address of HCCA. + mov eax, esi + call get_pg_addr + mov [edi+OhciHCCAReg], eax +; 6b. Transition to operational state and clear all Enable bits. + mov cl, 2 shl 6 + mov [edi+OhciControlReg], ecx +; 6c. Physical addresses of head of Control and Bulk lists. +if ohci_controller.BulkED >= 0x1000 +.err assertion failed +end if + add eax, ohci_controller.ControlED + mov [edi+OhciControlHeadEDReg], eax + add eax, ohci_controller.BulkED - ohci_controller.ControlED + mov [edi+OhciBulkHeadEDReg], eax +; 6d. Zero Head registers: there are no active Control and Bulk descriptors yet. + xor eax, eax +; mov [edi+OhciPeriodCurrentEDReg], eax + mov [edi+OhciControlCurrentEDReg], eax + mov [edi+OhciBulkCurrentEDReg], eax +; mov [edi+OhciDoneHeadReg], eax +; 6e. Enable processing of all lists with control:bulk ratio = 1:1. + mov dword [edi+OhciControlReg], 10111100b +; 7. Get number of ports. + add esi, sizeof.ohci_controller + mov eax, [edi+OhciRhDescriptorAReg] + and eax, 0xF + mov [esi+usb_controller.NumPorts], eax +; 8. Initialize DoneListEndPtr to point to DoneList. + lea eax, [esi+ohci_controller.DoneList-sizeof.ohci_controller] + mov [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], eax +; 9. Hook interrupt. + mov ah, [.bus] + mov al, 0 + mov bh, [.devfn] + mov bl, 3Ch + call pci_read_reg +; al = IRQ + movzx eax, al + stdcall attach_int_handler, eax, ohci_irq, esi +; 10. Enable controller interrupt on HcDoneHead writeback and RootHubStatusChange. + mov dword [edi+OhciInterruptEnableReg], 80000042h + DEBUGF 1,'K : OHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] +; 11. Initialize ports of the controller. +; 11a. Initiate power up, disable all ports, clear all "changed" bits. + mov dword [edi+OhciRhStatusReg], 10000h ; SetGlobalPower + xor ecx, ecx +@@: + mov dword [edi+OhciRhPortStatusReg+ecx*4], 1F0101h ; SetPortPower+ClearPortEnable+clear "changed" bits + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb @b +; 11b. Wait for power up. +; VirtualBox has AReg == 0, delay_ms doesn't like zero value; ignore zero delay + push esi + mov esi, [edi+OhciRhDescriptorAReg] + shr esi, 24 + add esi, esi + jz @f + call delay_ms +@@: + pop esi +; 11c. Ports are powered up; now it is ok to process connect/disconnect events. + mov [esi+ohci_controller.PoweredUp-sizeof.ohci_controller], 1 + ; IRQ handler doesn't accept connect/disconnect events before this point +; 11d. We could miss some events while waiting for powering up; +; scan all ports manually and check for connected devices. + xor ecx, ecx +.port_loop: + test dword [edi+OhciRhPortStatusReg+ecx*4], 1 + jz .next_port +; There is a connected device; mark the port as 'connected' +; and save the connected time. +; Note that ConnectedTime must be set before 'connected' mark, +; otherwise the code in ohci_process_deferred could use incorrect time. + mov eax, [timer_ticks] + mov [esi+usb_controller.ConnectedTime+ecx*4], eax + lock bts [esi+usb_controller.NewConnected], ecx +.next_port: + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .port_loop +; 12. Return pointer to usb_controller. + xchg eax, esi + ret +.fail_unmap: +; On error after step 4, release the virtual memory area. + stdcall free_kernel_space, edi +.fail: +; On error, free the ohci_controller structure and return zero. +; Note that the pointer was placed in the stack at step 1. +; Note also that there can be no errors after step 8, +; where that pointer is popped from the stack. + pop ecx +.nothing: + xor eax, eax + ret +endp + +; Helper procedure for step 3 of ohci_init. +; Initializes the static head of one list. +; eax = physical address of the "next" list, esi = pointer to the "next" list, +; edi = pointer to head to initialize. +; Advances edi to the next head, keeps eax/esi. +proc ohci_init_static_endpoint + mov byte [edi+ohci_static_ep.Flags+1], 1 shl (14 - 8) ; sKip this endpoint + mov [edi+ohci_static_ep.NextED], eax + mov [edi+ohci_static_ep.NextList], esi + add edi, ohci_static_ep.SoftwarePart + call usb_init_static_endpoint + add edi, sizeof.ohci_static_ep - ohci_static_ep.SoftwarePart + ret +endp + +; Helper procedure for step 3 of ohci_init. +; Initializes one half of group of static heads. +; edx = size of the next group = half of size of the group, +; edi = pointer to the group, eax = physical address of the next group, +; esi = pointer to the next group. +; Advances eax, esi, edi to next group, keeps edx. +proc ohci_init_static_ep_group + push edx +@@: + call ohci_init_static_endpoint + add eax, sizeof.ohci_static_ep + add esi, sizeof.ohci_static_ep + dec edx + jnz @b + pop edx + ret +endp + +; Controller-specific pre-initialization function: take ownership from BIOS. +; Some BIOSes, although not all of them, provide legacy emulation +; for USB keyboard and/or mice as PS/2-devices. In this case, +; we must notify the BIOS that we don't need that emulation and know how to +; deal with USB devices. +proc ohci_kickoff_bios +; 1. Get the physical address of MMIO registers. + mov ah, [esi+PCIDEV.bus] + mov bh, [esi+PCIDEV.devfn] + mov al, 2 + mov bl, 10h + call pci_read_reg + and al, not 0Fh +; 2. Create mapping for physical memory. 256 bytes are sufficient. + stdcall map_io_mem, eax, 100h, PG_SW+PG_NOCACHE + test eax, eax + jz .nothing +; 3. Some BIOSes enable controller interrupts as a result of giving +; controller away. At this point the system knows nothing about how to serve +; OHCI interrupts, so such an interrupt will send the system into an infinite +; loop handling the same IRQ again and again. Thus, we need to block OHCI +; interrupts. We can't do this at the controller level until step 5, +; because the controller is currently owned by BIOS, so we block all hardware +; interrupts on this processor until step 5. + pushf + cli +; 4. Take the ownership over the controller. +; 4a. Check whether BIOS handles this controller at all. + mov edx, 100h + test dword [eax+OhciControlReg], edx + jz .has_ownership +; 4b. Send "take ownership" command to the BIOS. +; (This should generate SMI, BIOS should release its ownership in SMI handler.) + mov dword [eax+OhciCommandStatusReg], 8 +; 4c. Wait for result no more than 50 ms, checking for status every 1 ms. + push 50 + pop ecx +@@: + test dword [eax+OhciControlReg], edx + jz .has_ownership + push esi + push 1 + pop esi + call delay_ms + pop esi + loop @b + dbgstr 'warning: taking OHCI ownership from BIOS timeout' +.has_ownership: +; 5. Disable all controller interrupts until the system will be ready to +; process them. + mov dword [eax+OhciInterruptDisableReg], 0C000007Fh +; 6. Now we can unblock interrupts in the processor. + popf +; 7. Release memory mapping created in step 2 and return. + stdcall free_kernel_space, eax +.nothing: + ret +endp + +; IRQ handler for OHCI controllers. +ohci_irq.noint: +; Not our interrupt: restore registers and return zero. + xor eax, eax + pop edi esi ebx + ret + +proc ohci_irq + push ebx esi edi ; save used registers to be cdecl +virtual at esp + rd 3 ; saved registers + dd ? ; return address +.controller dd ? +end virtual +; 1. ebx will hold whether some deferred processing is needed, +; that cannot be done from the interrupt handler. Initialize to zero. + xor ebx, ebx +; 2. Get the mask of events which should be processed. + mov esi, [.controller] + mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] + mov eax, [edi+OhciInterruptStatusReg] +; 3. Check whether that interrupt has been generated by our controller. +; (One IRQ can be shared by several devices.) + and eax, [edi+OhciInterruptEnableReg] + jz .noint +; 4. Get the physical pointer to the last processed descriptor. +; All processed descriptors form single-linked list from last to first +; with the help of NextTD field. The list is restarted every time when +; the controller writes to DoneHead, so grab the pointer now (before the next +; step) or it could be lost (the controller could write new value to DoneHead +; any time after WorkDone bit is cleared in OhciInterruptStatusReg). + mov ecx, [esi+ohci_controller.DoneHead-sizeof.ohci_controller] + and ecx, not 1 +; 5. Clear the events we know of. +; Note that this should be done before processing of events: +; new events could arise while we are processing those, this way we won't lose +; them (the controller would generate another interrupt +; after completion of this one). + mov [edi+OhciInterruptStatusReg], eax +; 6. Save the mask of events for further reference. + push eax +; 7. Handle 'transfer is done' events. +; 7a. Test whether there are such events. + test al, 2 + jz .skip_donehead +; There are some 'transfer is done' events, processed descriptors are linked +; through physical addresses in the reverse order. +; We can't do much in an interrupt handler, since callbacks could require +; waiting for locks and that can't be done in an interrupt handler. +; However, we can't also just defer all work to the USB thread, since +; it is possible that previous lists are not yet processed and it is hard +; to store unlimited number of list heads. Thus, we reverse the current list, +; append it to end of the previous list (if there is one) and defer other +; processing to the USB thread; this way there always is no more than one list +; (possibly joined from several controller-reported lists). +; The list traversal requires converting physical addresses to virtual pointers, +; so we may as well store pointers instead of physical addresses. +; 7b. Prepare for the reversing loop. + push ebx + xor ebx, ebx + test ecx, ecx + jz .tddone + call usb_td_to_virt + test eax, eax + jz .tddone + lea edx, [eax+ohci_gtd.NextTD] +; 7c. Reverse the list, converting physical to virtual. On every iteration: +; ecx = physical address of the current item +; eax = virtual pointer to the current item +; edx = virtual pointer to the last item.NextTD (first in the reverse list) +; ebx = virtual pointer to the next item (previous in the reverse list) +.tdloop: + mov ecx, [eax+ohci_gtd.NextTD] + mov [eax+ohci_gtd.NextTD], ebx + lea ebx, [eax+ohci_gtd.SoftwarePart] + test ecx, ecx + jz .tddone + call usb_td_to_virt + test eax, eax + jnz .tdloop +.tddone: + mov ecx, ebx + pop ebx +; 7d. The list is reversed, +; ecx = pointer to the first item, edx = pointer to the last item.NextTD. +; If the list is empty (unusual case), step 7 is done. + test ecx, ecx + jz .skip_donehead +; 7e. Otherwise, append this list to the end of previous one. +; Note that in theory the interrupt handler and the USB thread +; could execute in parallel. +.append_restart: +; Atomically get DoneListEndPtr in eax and set it to edx. + mov eax, [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller] + lock cmpxchg [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], edx + jnz .append_restart +; Store pointer to the new list. +; Note: we cannot perform any operations with [DoneListEndPtr] +; until we switch DoneListEndPtr to a new descriptor: +; it is possible that after first line of .append_restart loop +; ohci_process_deferred obtains the control, finishes processing +; of the old list, sets DoneListEndPtr to address of DoneList, +; frees all old descriptors, so eax would point to invalid location. +; This way, .append_restart loop would detect that DoneListEndPtr +; has changed, so eax needs to be re-read. + mov [eax], ecx +; 7f. Notify the USB thread that there is new work. + inc ebx +.skip_donehead: +; 8. Handle start-of-frame events. +; 8a. Test whether there are such events. + test byte [esp], 4 + jz .skip_sof +; We enable SOF interrupt only when some pipes are waiting after changes. + spin_lock_irqsave [esi+usb_controller.WaitSpinlock] +; 8b. Make sure that there was at least one frame update +; since the request. If not, wait for the next SOF. + movzx eax, [esi+ohci_controller.FrameNumber-sizeof.ohci_controller] + cmp eax, [esi+usb_controller.StartWaitFrame] + jz .sof_unlock +; 8c. Copy WaitPipeRequest* to ReadyPipeHead*. + mov eax, [esi+usb_controller.WaitPipeRequestAsync] + mov [esi+usb_controller.ReadyPipeHeadAsync], eax + mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] + mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax +; 8d. It is possible that pipe change is due to removal and +; Control/BulkCurrentED registers still point to one of pipes to be removed. +; The code responsible for disconnect events has temporarily stopped +; Control/Bulk processing, so it is safe to clear Control/BulkCurrentED. +; After that, restart processing. + xor edx, edx + mov [edi+OhciControlCurrentEDReg], edx + mov [edi+OhciBulkCurrentEDReg], edx + mov dword [edi+OhciCommandStatusReg], 6 + or dword [edi+OhciControlReg], 30h +; 8e. Disable further interrupts on SOF. +; Note: OhciInterruptEnableReg/OhciInterruptDisableReg have unusual semantics. + mov dword [edi+OhciInterruptDisableReg], 4 +; Notify the USB thread that there is new work (with pipes from ReadyPipeHead*). + inc ebx +.sof_unlock: + spin_unlock_irqrestore [esi+usb_controller.RemoveSpinlock] +.skip_sof: +; Handle roothub events. +; 9. Test whether there are such events. + test byte [esp], 40h + jz .skip_roothub +; 10. Check the status of the roothub itself. +; 10a. Global overcurrent? + test dword [edi+OhciRhStatusReg], 2 + jz @f +; Note: this needs work. + dbgstr 'global overcurrent' +@@: +; 10b. Clear roothub events. + mov dword [edi+OhciRhStatusReg], 80020000h +; 11. Check the status of individual ports. +; Look for connect/disconnect and reset events. +; 11a. Prepare for the loop: start from port 0. + xor ecx, ecx +.portloop: +; 11b. Get the port status and changes of it. +; Accumulate change information. +; Look to "11.12.3 Port Change Information Processing" of the USB2 spec. + xor eax, eax +.accloop: + mov edx, [edi+OhciRhPortStatusReg+ecx*4] + xor ax, ax + or eax, edx + test edx, 1F0000h + jz .accdone + mov dword [edi+OhciRhPortStatusReg+ecx*4], 1F0000h + jmp .accloop +.accdone: +; debugging output, not needed for work +; test eax, 1F0000h +; jz @f +; DEBUGF 1,'K : ohci irq [%d] status of port %d is %x\n',[timer_ticks],ecx,eax +;@@: +; 11c. Ignore any events until all ports are powered up. +; They will be processed by ohci_init. + cmp [esi+ohci_controller.PoweredUp-sizeof.ohci_controller], 0 + jz .nextport +; Handle changing of connection status. + test eax, 10000h + jz .nocsc +; There was a connect or disconnect event at this port. +; 11d. Disconnect the old device on this port, if any. +; if the port was resetting, indicate fail and signal + cmp cl, [esi+usb_controller.ResettingPort] + jnz @f + mov [esi+usb_controller.ResettingStatus], -1 + inc ebx +@@: + lock bts [esi+usb_controller.NewDisconnected], ecx +; notify the USB thread that new work is waiting + inc ebx +; 11e. Change connected status. For the connection event, also +; store the connection time; any further processing is permitted only +; after USB_CONNECT_DELAY ticks. + test al, 1 + jz .disconnect +; Note: ConnectedTime must be stored before setting the 'connected' bit, +; otherwise ohci_process_deferred could use an old time. + mov eax, [timer_ticks] + mov [esi+usb_controller.ConnectedTime+ecx*4], eax + lock bts [esi+usb_controller.NewConnected], ecx + jmp .nextport +.disconnect: + lock btr [esi+usb_controller.NewConnected], ecx + jmp .nextport +.nocsc: +; 11f. Process 'reset done' events. + test eax, 100000h + jz .nextport + test al, 10h + jnz .nextport + mov edx, [timer_ticks] + mov [esi+usb_controller.ResetTime], edx + mov [esi+usb_controller.ResettingStatus], 2 + inc ebx +.nextport: +; 11g. Continue the loop for the next port. + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .portloop +.skip_roothub: +; 12. Restore the stack after step 6. + pop eax +; 13. Notify the USB thread if some deferred processing is required. + call usb_wakeup_if_needed +; 14. Interrupt processed; return something non-zero. + mov al, 1 + pop edi esi ebx ; restore used registers to be stdcall + ret +endp + +; This procedure is called from usb_set_address_callback +; and stores USB device address in the ohci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe, cl = address +proc ohci_set_device_address + mov byte [ebx+ohci_pipe.Flags-ohci_pipe.SoftwarePart], cl +; Wait until the hardware will forget the old value. + call usb_subscribe_control + ret +endp + +; This procedure returns USB device address from the usb_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe +; out: eax = endpoint address +proc ohci_get_device_address + mov eax, [ebx+ohci_pipe.Flags-ohci_pipe.SoftwarePart] + and eax, 7Fh + ret +endp + +; This procedure is called from usb_set_address_callback +; if the device does not accept SET_ADDRESS command and needs +; to be disabled at the port level. +; in: esi -> usb_controller, ecx = port +proc ohci_port_disable + mov edx, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] + mov dword [edx+OhciRhPortStatusReg+ecx*4], 1 + ret +endp + +; This procedure is called from usb_get_descr8_callback when +; the packet size for zero endpoint becomes known and +; stores the packet size in ohci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size +proc ohci_set_endpoint_packet_size + mov byte [ebx+ohci_pipe.Flags+2-ohci_pipe.SoftwarePart], cl +; Wait until the hardware will forget the old value. + call usb_subscribe_control + ret +endp + +; This procedure is called from API usb_open_pipe and processes +; the controller-specific part of this API. See docs. +; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, +; esi -> usb_controller, eax -> usb_gtd for the first TD, +; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type +proc ohci_init_pipe +virtual at ebp+8 +.config_pipe dd ? +.endpoint dd ? +.maxpacket dd ? +.type dd ? +.interval dd ? +end virtual +; 1. Initialize the queue of transfer descriptors: empty. + sub eax, ohci_gtd.SoftwarePart + call get_phys_addr + mov [edi+ohci_pipe.TailP-ohci_pipe.SoftwarePart], eax + mov [edi+ohci_pipe.HeadP-ohci_pipe.SoftwarePart], eax +; 2. Generate ohci_pipe.Flags, see the description in ohci_pipe. + mov eax, [ecx+ohci_pipe.Flags-ohci_pipe.SoftwarePart] + and eax, 0x207F ; keep Speed bit and FunctionAddress + mov edx, [.endpoint] + and edx, 15 + shl edx, 7 + or eax, edx + mov [edi+ohci_pipe.Flags-ohci_pipe.SoftwarePart], eax + mov eax, [.maxpacket] + mov word [edi+ohci_pipe.Flags+2-ohci_pipe.SoftwarePart], ax + cmp [.type], CONTROL_PIPE + jz @f + test byte [.endpoint], 80h + setnz al + inc eax + shl al, 3 + or byte [edi+ohci_pipe.Flags+1-ohci_pipe.SoftwarePart], al +@@: +; 3. Insert the new pipe to the corresponding list of endpoints. +; 3a. Use Control list for control pipes, Bulk list for bulk pipes. + lea edx, [esi+ohci_controller.ControlED.SoftwarePart-sizeof.ohci_controller] + cmp [.type], BULK_PIPE + jb .insert ; control pipe + lea edx, [esi+ohci_controller.BulkED.SoftwarePart-sizeof.ohci_controller] + jz .insert ; bulk pipe +.interrupt_pipe: +; 3b. For interrupt pipes, let the scheduler select the appropriate list +; based on the current bandwidth distribution and the requested bandwidth. +; This could fail if the requested bandwidth is not available; +; if so, return an error. + lea edx, [esi + ohci_controller.IntEDs - sizeof.ohci_controller] + lea eax, [esi + ohci_controller.IntEDs + 32*sizeof.ohci_static_ep - sizeof.ohci_controller] + push 64 + pop ecx + call usb1_select_interrupt_list + test edx, edx + jz .return0 +; 3c. Insert endpoint at edi to the head of list in edx. +; Inserting to tail would work as well, +; but let's be consistent with other controllers. +.insert: + mov ecx, [edx+usb_pipe.NextVirt] + mov [edi+usb_pipe.NextVirt], ecx + mov [edi+usb_pipe.PrevVirt], edx + mov [ecx+usb_pipe.PrevVirt], edi + mov [edx+usb_pipe.NextVirt], edi + mov ecx, [edx+ohci_pipe.NextED-ohci_pipe.SoftwarePart] + mov [edi+ohci_pipe.NextED-ohci_pipe.SoftwarePart], ecx + lea eax, [edi-ohci_pipe.SoftwarePart] + call get_phys_addr + mov [edx+ohci_pipe.NextED-ohci_pipe.SoftwarePart], eax +; 4. Return something non-zero. + ret +.return0: + xor eax, eax + ret +endp + +; This function is called from ohci_process_deferred when +; a new device was connected at least USB_CONNECT_DELAY ticks +; and therefore is ready to be configured. +; ecx = port, esi -> usb_controller +proc ohci_new_port +; test whether we are configuring another port +; if so, postpone configuring and return + bts [esi+usb_controller.PendingPorts], ecx + cmp [esi+usb_controller.ResettingPort], -1 + jnz .nothing + btr [esi+usb_controller.PendingPorts], ecx +; fall through to ohci_new_port.reset + +; This function is called from usb_test_pending_port. +; It starts reset signalling for the port. Note that in USB first stages +; of configuration can not be done for several ports in parallel. +.reset: +; reset port + and [esi+usb_controller.ResettingHub], 0 + mov [esi+usb_controller.ResettingPort], cl +; Note: setting status must be the last action: +; it is possible that the device has been disconnected +; after timeout of USB_CONNECT_DELAY but before call to ohci_new_port. +; In this case, ohci_irq would not set reset status to 'failed', +; because ohci_irq would not know that this port is to be reset. +; However, the hardware would generate another interrupt +; in a response to reset a disconnected port, and this time +; ohci_irq knows that it needs to generate 'reset failed' event +; (because ResettingPort is now filled). + push edi + mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] + mov dword [edi+OhciRhPortStatusReg+ecx*4], 10h + pop edi +.nothing: + ret +endp + +; This procedure is called from the several places in main USB code +; and allocates required packets for the given transfer. +; ebx = pipe, other parameters are passed through the stack: +; buffer,size = data to transfer +; flags = same as in usb_open_pipe: bit 0 = allow short transfer, other bits reserved +; td = pointer to the current end-of-queue descriptor +; direction = +; 0000b for normal transfers, +; 1000b for control SETUP transfer, +; 1101b for control OUT transfer, +; 1110b for control IN transfer +; returns eax = pointer to the new end-of-queue descriptor +; (not included in the queue itself) or 0 on error +proc ohci_alloc_transfer stdcall uses edi, \ + buffer:dword, size:dword, flags:dword, td:dword, direction:dword +locals +origTD dd ? +packetSize dd ? ; must be the last variable, see usb_init_transfer +endl +; 1. Save original value of td: +; it will be useful for rollback if something would fail. + mov eax, [td] + mov [origTD], eax +; One transfer descriptor can describe up to two pages. +; In the worst case (when the buffer is something*1000h+0FFFh) +; this corresponds to 1001h bytes. If the requested size is +; greater, we should split the transfer into several descriptors. +; Boundaries to split must be multiples of endpoint transfer size +; to avoid short packets except in the end of the transfer, +; 1000h is always a good value. +; 2. While the remaining data cannot fit in one packet, +; allocate page-sized descriptors. + mov edi, 1000h + mov [packetSize], edi +.fullpackets: + cmp [size], edi + jbe .lastpacket + call ohci_alloc_packet + test eax, eax + jz .fail + mov [td], eax + add [buffer], edi + sub [size], edi + jmp .fullpackets +; 3. The remaining data can fit in one descriptor; +; allocate the last descriptor with size = size of remaining data. +.lastpacket: + mov eax, [size] + mov [packetSize], eax + call ohci_alloc_packet + test eax, eax + jz .fail +; 4. Enable an immediate interrupt on completion of the last packet. + and byte [ecx+ohci_gtd.Flags+2-ohci_gtd.SoftwarePart], not (7 shl (21-16)) +; 5. If a short transfer is ok for a caller, set the corresponding bit in +; the last descriptor, but not in others. +; Note: even if the caller says that short transfers are ok, +; all packets except the last one are marked as 'must be complete': +; if one of them will be short, the software intervention is needed +; to skip remaining packets; ohci_process_finalized_td will handle this +; transparently to the caller. + test [flags], 1 + jz @f + or byte [ecx+ohci_gtd.Flags+2-ohci_gtd.SoftwarePart], 1 shl (18-16) +@@: + ret +.fail: + mov edi, ohci_hardware_func + mov eax, [td] + stdcall usb_undo_tds, [origTD] + xor eax, eax + ret +endp + +; Helper procedure for ohci_alloc_transfer. +; Allocates and initializes one transfer descriptor. +; ebx = pipe, other parameters are passed through the stack; +; fills the current last descriptor and +; returns eax = next descriptor (not filled). +proc ohci_alloc_packet +; inherit some variables from the parent ohci_alloc_transfer +virtual at ebp-8 +.origTD dd ? +.packetSize dd ? + rd 2 +.buffer dd ? +.transferSize dd ? +.Flags dd ? +.td dd ? +.direction dd ? +end virtual +; 1. Allocate the next TD. + call usb1_allocate_general_td + test eax, eax + jz .nothing +; 2. Initialize controller-independent parts of both TDs. + push eax + call usb_init_transfer + pop eax +; 3. Save the returned value (next descriptor). + push eax +; 4. Store the physical address of the next descriptor. + sub eax, ohci_gtd.SoftwarePart + call get_phys_addr + mov [ecx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart], eax +; 5. For zero-length transfers, store zero in both fields for buffer addresses. +; Otherwise, fill them with real values. + xor eax, eax + mov [ecx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart], eax + mov [ecx+ohci_gtd.BufEnd-ohci_gtd.SoftwarePart], eax + cmp [.packetSize], eax + jz @f + mov eax, [.buffer] + call get_phys_addr + mov [ecx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart], eax + mov eax, [.buffer] + add eax, [.packetSize] + dec eax + call get_phys_addr + mov [ecx+ohci_gtd.BufEnd-ohci_gtd.SoftwarePart], eax +@@: +; 6. Generate Flags field: +; - set bufferRounding (bit 18) to zero = disallow short transfers; +; for the last transfer in a row, ohci_alloc_transfer would set the real value; +; - set Direction (bits 19-20) to lower 2 bits of [.direction]; +; - set DelayInterrupt (bits 21-23) to 7 = do not generate interrupt; +; for the last transfer in a row, ohci_alloc_transfer would set the real value; +; - set DataToggle (bits 24-25) to next 2 bits of [.direction]; +; - set ConditionCode (bits 28-31) to 1111b as a indicator that there was no +; attempts to perform this transfer yet; +; - zero all other bits. + mov eax, [.direction] + mov edx, eax + and eax, 3 + shl eax, 19 + and edx, (3 shl 2) + shl edx, 24 - 2 + lea eax, [eax + edx + (7 shl 21) + (15 shl 28)] + mov [ecx+ohci_gtd.Flags-ohci_gtd.SoftwarePart], eax +; 7. Restore the returned value saved in step 3. + pop eax +.nothing: + ret +endp + +; This procedure is called from the several places in main USB code +; and activates the transfer which was previously allocated by +; ohci_alloc_transfer. +; ecx -> last descriptor for the transfer, ebx -> usb_pipe +proc ohci_insert_transfer +; 1. Advance the queue of transfer descriptors. + mov eax, [ecx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart] + mov [ebx+ohci_pipe.TailP-ohci_pipe.SoftwarePart], eax +; 2. For control and bulk pipes, notify the controller that +; there is new work in control/bulk queue respectively. +ohci_notify_new_work: + mov edx, [ebx+usb_pipe.Controller] + mov edx, [edx+ohci_controller.MMIOBase-sizeof.ohci_controller] + cmp [ebx+usb_pipe.Type], CONTROL_PIPE + jz .control + cmp [ebx+usb_pipe.Type], BULK_PIPE + jnz .nothing +.bulk: + mov dword [edx+OhciCommandStatusReg], 4 + jmp .nothing +.control: + mov dword [edx+OhciCommandStatusReg], 2 +.nothing: + ret +endp + +; This function is called from ohci_process_deferred when +; a new device has been reset and needs to be configured. +proc ohci_port_after_reset +; 1. Get the status. +; If reset has been failed (device disconnected during reset), +; continue to next device (if there is one). + xor eax, eax + xchg al, [esi+usb_controller.ResettingStatus] + test al, al + js usb_test_pending_port +; If the controller has disabled the port (e.g. overcurrent), +; continue to next device (if there is one). + movzx ecx, [esi+usb_controller.ResettingPort] + mov eax, [edi+OhciRhPortStatusReg+ecx*4] + test al, 2 + jnz @f + DEBUGF 1,'K : USB port disabled after reset, status=%x\n',eax + jmp usb_test_pending_port +@@: + push ecx +; 2. Get LowSpeed bit to bit 0 of eax and call the worker procedure +; to notify the protocol layer about new OHCI device. + mov eax, [edi+OhciRhPortStatusReg+ecx*4] + DEBUGF 1,'K : port_after_reset [%d] status of port %d is %x\n',[timer_ticks],ecx,eax + shr eax, 9 + call ohci_new_device + pop ecx +; 3. If something at the protocol layer has failed +; (no memory, no bus address), disable the port and stop the initialization. + test eax, eax + jnz .nothing +.disable_exit: + mov dword [edi+OhciRhPortStatusReg+ecx*4], 1 + jmp usb_test_pending_port +.nothing: + ret +endp + +; This procedure is called from uhci_port_init and from hub support code +; when a new device is connected and has been reset. +; It calls usb_new_device at the protocol layer with correct parameters. +; in: esi -> usb_controller, eax = speed; +; OHCI is USB1 device, so only low bit of eax (LowSpeed) is used. +proc ohci_new_device +; 1. Clear all bits of speed except bit 0. + and eax, 1 +; 2. Store the speed for the protocol layer. + mov [esi+usb_controller.ResettingSpeed], al +; 3. Create pseudo-pipe in the stack. +; See ohci_init_pipe: only .Controller and .Flags fields are used. + shl eax, 13 + push esi ; .Controller + mov ecx, esp + sub esp, 12 ; ignored fields + push eax ; .Flags +; 4. Notify the protocol layer. + call usb_new_device +; 5. Cleanup the stack after step 3 and return. + add esp, 20 + ret +endp + +; This procedure is called in the USB thread from usb_thread_proc, +; processes regular actions and those actions which can't be safely done +; from interrupt handler. +; Returns maximal time delta before the next call. +proc ohci_process_deferred + push ebx edi ; save used registers to be stdcall +; 1. Initialize the return value. + push -1 +; 2. Process disconnect events. + call usb_disconnect_stage2 +; 3. Check for connected devices. +; If there is a connected device which was connected less than +; USB_CONNECT_DELAY ticks ago, plan to wake up when the delay will be over. +; Otherwise, call ohci_new_port. + mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] + xor ecx, ecx + cmp [esi+usb_controller.NewConnected], ecx + jz .skip_newconnected +.portloop: + bt [esi+usb_controller.NewConnected], ecx + jnc .noconnect + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ConnectedTime+ecx*4] + sub eax, USB_CONNECT_DELAY + jge .connected + neg eax + cmp [esp], eax + jb .nextport + mov [esp], eax + jmp .nextport +.connected: + lock btr [esi+usb_controller.NewConnected], ecx + jnc .nextport + call ohci_new_port +.noconnect: +.nextport: + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .portloop +.skip_newconnected: +; 4. Check for end of reset signalling. If so, call ohci_port_after_reset. + cmp [esi+usb_controller.ResettingStatus], 2 + jnz .no_reset_recovery + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ResetTime] + sub eax, USB_RESET_RECOVERY_TIME + jge .reset_done + neg eax + cmp [esp], eax + jb .skip_roothub + mov [esp], eax + jmp .skip_roothub +.no_reset_recovery: + cmp [esi+usb_controller.ResettingStatus], 0 + jz .skip_roothub +.reset_done: + call ohci_port_after_reset +.skip_roothub: +; 5. Finalize transfers processed by hardware. +; It is better to perform this step after processing disconnect events, +; although not strictly obligatory. This way, an active transfer aborted +; due to disconnect would be handled with more specific USB_STATUS_CLOSED, +; not USB_STATUS_NORESPONSE. +; Loop over all items in DoneList, call ohci_process_finalized_td for each. + xor ebx, ebx + xchg ebx, [esi+ohci_controller.DoneList-sizeof.ohci_controller] +.tdloop: + test ebx, ebx + jz .tddone + call ohci_process_finalized_td + jmp .tdloop +.tddone: +; 6. Process wait-done notifications, test for new wait requests. +; Note: that must be done after steps 2 and 5 which could create new requests. +; 6a. Call the worker function from main USB code. + call usb_process_wait_lists +; 6b. If no new requests, skip the rest of this step. + test eax, eax + jz @f +; 6c. OHCI is not allowed to cache anything; we don't know what is +; processed right now, but we can be sure that the controller will not +; use any removed structure starting from the next frame. +; Schedule SOF event. + spin_lock_irq [esi+usb_controller.RemoveSpinlock] + mov eax, [esi+usb_controller.WaitPipeListAsync] + mov [esi+usb_controller.WaitPipeRequestAsync], eax + mov eax, [esi+usb_controller.WaitPipeListPeriodic] + mov [esi+usb_controller.WaitPipeRequestPeriodic], eax +; temporarily stop bulk and interrupt processing; +; this is required for handler of SOF event + and dword [edi+OhciControlReg], not 30h +; remember the frame number when processing has been stopped +; (needs to be done after stopping) + movzx eax, [esi+ohci_controller.FrameNumber-sizeof.ohci_controller] + mov [esi+usb_controller.StartWaitFrame], eax +; make sure that the next SOF will happen after the request + mov dword [edi+OhciInterruptStatusReg], 4 +; enable interrupt on SOF +; Note: OhciInterruptEnableReg/OhciInterruptDisableReg have unusual semantics, +; so there should be 'mov' here, not 'or' + mov dword [edi+OhciInterruptEnableReg], 4 + spin_unlock_irq [esi+usb_controller.RemoveSpinlock] +@@: +; 7. Restore the return value and return. + pop eax + pop edi ebx ; restore used registers to be stdcall + ret +endp + +; Helper procedure for ohci_process_deferred. Processes one completed TD. +; in: esi -> usb_controller, ebx -> usb_gtd, out: ebx -> next usb_gtd. +proc ohci_process_finalized_td +; DEBUGF 1,'K : processing %x\n',ebx +; 1. Check whether the pipe has been closed, either due to API call or due to +; disconnect; if so, the callback will be called by usb_pipe_closed with +; correct status, so go to step 6 with ebx = 0 (do not free the TD). + mov edx, [ebx+usb_gtd.Pipe] + test [edx+usb_pipe.Flags], USB_FLAG_CLOSED + jz @f + lea eax, [ebx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart] + xor ebx, ebx + jmp .next_td2 +@@: +; 2. Remove the descriptor from the descriptors queue. + call usb_unlink_td +; 3. Get number of bytes that remain to be transferred. +; If CurBufPtr is zero, everything was transferred. + xor edx, edx + cmp [ebx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart], edx + jz .gotlen +; Otherwise, the remaining length is +; (BufEnd and 0xFFF) - (CurBufPtr and 0xFFF) + 1, +; plus 0x1000 if BufEnd and CurBufPtr are in different pages. + mov edx, [ebx+ohci_gtd.BufEnd-ohci_gtd.SoftwarePart] + mov eax, [ebx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart] + mov ecx, edx + and edx, 0xFFF + inc edx + xor ecx, eax + and ecx, -0x1000 + jz @f + add edx, 0x1000 +@@: + and eax, 0xFFF + sub edx, eax +.gotlen: +; The actual length is Length - (remaining length). + sub edx, [ebx+usb_gtd.Length] + neg edx +; 4. Check for error. If so, go to 7. + push ebx + mov eax, [ebx+ohci_gtd.Flags-ohci_gtd.SoftwarePart] + shr eax, 28 + jnz .error +.notify: +; 5. Successful completion. +; 5a. Check whether this descriptor has an associated callback. + mov ecx, [ebx+usb_gtd.Callback] + test ecx, ecx + jz .ok_nocallback +; 5b. If so, call the callback. + stdcall_verify ecx, [ebx+usb_gtd.Pipe], eax, \ + [ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] + jmp .next_td +.ok_nocallback: +; 5c. Otherwise, add length of the current descriptor to the next descriptor. + mov eax, [ebx+usb_gtd.NextVirt] + add [eax+usb_gtd.Length], edx +.next_td: +; 6. Free the current descriptor and advance to the next item. +; If the current item is the last in the list, +; set DoneListEndPtr to pointer to DoneList. + cmp ebx, [esp] + jz @f + stdcall usb1_free_general_td, ebx +@@: + pop ebx + lea eax, [ebx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart] +.next_td2: + push ebx + mov ebx, eax + lea edx, [esi+ohci_controller.DoneList-sizeof.ohci_controller] + xor ecx, ecx ; no next item + lock cmpxchg [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], edx + jz .last +; The current item is not the last. +; It is possible, although very rare, that ohci_irq has already advanced +; DoneListEndPtr, but not yet written NextTD. Wait until NextTD is nonzero. +@@: + mov ecx, [ebx] + test ecx, ecx + jz @b +.last: + pop ebx +; ecx = the next item + push ecx +; Free the current item, set ebx to the next item, continue to 5a. + test ebx, ebx + jz @f + stdcall usb1_free_general_td, ebx +@@: + pop ebx + ret +.error: +; 7. There was an error while processing this descriptor. +; The hardware has stopped processing the queue. +; 7a. Save status and length. + push eax + push edx +; DEBUGF 1,'K : TD failed:\n' +; DEBUGF 1,'K : %x %x %x %x\n',[ebx-ohci_gtd.SoftwarePart],[ebx-ohci_gtd.SoftwarePart+4],[ebx-ohci_gtd.SoftwarePart+8],[ebx-ohci_gtd.SoftwarePart+12] +; DEBUGF 1,'K : %x %x %x %x\n',[ebx-ohci_gtd.SoftwarePart+16],[ebx-ohci_gtd.SoftwarePart+20],[ebx-ohci_gtd.SoftwarePart+24],[ebx-ohci_gtd.SoftwarePart+28] +; mov eax, [ebx+usb_gtd.Pipe] +; DEBUGF 1,'K : pipe: %x %x %x %x\n',[eax-ohci_pipe.SoftwarePart],[eax-ohci_pipe.SoftwarePart+4],[eax-ohci_pipe.SoftwarePart+8],[eax-ohci_pipe.SoftwarePart+12] +; 7b. Traverse the list of descriptors looking for the final packet +; for this transfer. +; Free and unlink non-final descriptors, except the current one. +; Final descriptor will be freed in step 6. + call usb_is_final_packet + jnc .found_final + mov ebx, [ebx+usb_gtd.NextVirt] +virtual at esp +.length dd ? +.error_code dd ? +.current_item dd ? +end virtual +.look_final: + call usb_unlink_td + call usb_is_final_packet + jnc .found_final + push [ebx+usb_gtd.NextVirt] + stdcall usb1_free_general_td, ebx + pop ebx + jmp .look_final +.found_final: +; 7c. If error code is USB_STATUS_UNDERRUN and the last TD allows short packets, +; it is not an error. +; Note: all TDs except the last one in any transfer stage are marked +; as short-packet-is-error to stop controller from further processing +; of that stage; we need to restart processing from a TD following the last. +; After that, go to step 5 with eax = 0 (no error). + cmp dword [.error_code], USB_STATUS_UNDERRUN + jnz .no_underrun + test byte [ebx+ohci_gtd.Flags+2-ohci_gtd.SoftwarePart], 1 shl (18-16) + jz .no_underrun + and dword [.error_code], 0 + mov ecx, [ebx+usb_gtd.Pipe] + mov edx, [ecx+ohci_pipe.HeadP-ohci_pipe.SoftwarePart] + and edx, 2 +.advance_queue: + mov eax, [ebx+usb_gtd.NextVirt] + sub eax, ohci_gtd.SoftwarePart + call get_phys_addr + or eax, edx + mov [ecx+ohci_pipe.HeadP-ohci_pipe.SoftwarePart], eax + push ebx + mov ebx, ecx + call ohci_notify_new_work + pop ebx + pop edx eax + jmp .notify +; 7d. Abort the entire transfer. +; There are two cases: either there is only one transfer stage +; (everything except control transfers), then ebx points to the last TD and +; all previous TD were unlinked and dismissed (if possible), +; or there are several stages (a control transfer) and ebx points to the last +; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, +; because Setup stage can not produce short packets); for Data stage, we need +; to unlink and free (if possible) one more TD and advance ebx to the next one. +.no_underrun: + cmp [ebx+usb_gtd.Callback], 0 + jnz .halted + cmp ebx, [.current_item] + push [ebx+usb_gtd.NextVirt] + jz @f + stdcall usb1_free_general_td, ebx +@@: + pop ebx + call usb_unlink_td +.halted: +; 7e. For bulk/interrupt transfers we have no choice but halt the queue, +; the driver should intercede (through some API which is not written yet). +; Control pipes normally recover at the next SETUP transaction (first stage +; of any control transfer), so we hope on the best and just advance the queue +; to the next transfer. (According to the standard, "A control pipe may also +; support functional stall as well, but this is not recommended."). +; Advance the transfer queue to the next descriptor. + mov ecx, [ebx+usb_gtd.Pipe] + mov edx, [ecx+ohci_pipe.HeadP-ohci_pipe.SoftwarePart] + and edx, 2 ; keep toggleCarry bit + cmp [ecx+usb_pipe.Type], CONTROL_PIPE + jnz @f + inc edx ; set Halted bit +@@: + jmp .advance_queue +endp + +; This procedure is called when a pipe is closing (either due to API call +; or due to disconnect); it unlinks the pipe from the corresponding list. +; esi -> usb_controller, ebx -> usb_pipe +proc ohci_unlink_pipe + cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE + jnz @f + mov eax, [ebx+ohci_pipe.Flags-ohci_pipe.SoftwarePart] + bt eax, 13 + setc cl + bt eax, 11 + setc ch + shr eax, 16 + stdcall usb1_interrupt_list_unlink, eax, ecx +@@: + mov edx, [ebx+usb_pipe.NextVirt] + mov eax, [ebx+usb_pipe.PrevVirt] + mov [edx+usb_pipe.PrevVirt], eax + mov [eax+usb_pipe.NextVirt], edx + mov edx, [ebx+ohci_pipe.NextED-ohci_pipe.SoftwarePart] + mov [eax+ohci_pipe.NextED-ohci_pipe.SoftwarePart], edx + ret +endp diff --git a/kernel/trunk/bus/usb/pipe.inc b/kernel/trunk/bus/usb/pipe.inc new file mode 100644 index 0000000000..087fd19cf0 --- /dev/null +++ b/kernel/trunk/bus/usb/pipe.inc @@ -0,0 +1,813 @@ +; Functions for USB pipe manipulation: opening/closing, sending data etc. +; +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; USB pipe types +CONTROL_PIPE = 0 +ISOCHRONOUS_PIPE = 1 +BULK_PIPE = 2 +INTERRUPT_PIPE = 3 + +; Status codes for transfer callbacks. +; Taken from OHCI as most verbose controller in this sense. +USB_STATUS_OK = 0 ; no error +USB_STATUS_CRC = 1 ; CRC error +USB_STATUS_BITSTUFF = 2 ; bit stuffing violation +USB_STATUS_TOGGLE = 3 ; data toggle mismatch +USB_STATUS_STALL = 4 ; device returned STALL +USB_STATUS_NORESPONSE = 5 ; device not responding +USB_STATUS_PIDCHECK = 6 ; invalid PID check bits +USB_STATUS_WRONGPID = 7 ; unexpected PID value +USB_STATUS_OVERRUN = 8 ; too many data from endpoint +USB_STATUS_UNDERRUN = 9 ; too few data from endpoint +USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer +USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer +USB_STATUS_CLOSED = 16 ; pipe closed + ; either explicitly with USBClosePipe + ; or implicitly due to device disconnect + +; flags for usb_pipe.Flags +USB_FLAG_CLOSED = 1 ; pipe is closed, no new transfers +; pipe is closed, return error instead of submitting any new transfer +USB_FLAG_CAN_FREE = 2 +; pipe is closed via explicit call to USBClosePipe, so it can be freed without +; any driver notification; if this flag is not set, then the pipe is closed due +; to device disconnect, so it must remain valid until return from disconnect +; callback provided by the driver +USB_FLAG_EXTRA_WAIT = 4 +; The pipe was in wait list, while another event occured; +; when the first wait will be done, reinsert the pipe to wait list +USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= + +; Pipe descriptor. +; * An USB pipe is described by two structures, for hardware and for software. +; * This is the software part. The hardware part is defined in a driver +; of the corresponding controller. +; * The hardware part is located immediately before usb_pipe, +; both are allocated at once by controller-specific code +; (it knows the total length, which depends on the hardware part). +struct usb_pipe +Controller dd ? +; Pointer to usb_controller structure corresponding to this pipe. +; Must be the first dword after hardware part, see *hci_new_device. +; +; Every endpoint is included into one of processing lists: +; * Bulk list contains all Bulk endpoints. +; * Control list contains all Control endpoints. +; * Several Periodic lists serve Interrupt endpoints with different interval. +; - There are N=2^n "leaf" periodic lists for N ms interval, one is processed +; in the frames 0,N,2N,..., another is processed in the frames +; 1,1+N,1+2N,... and so on. The hardware starts processing of periodic +; endpoints in every frame from the list identified by lower n bits of the +; frame number; the addresses of these N lists are written to the +; controller data area during the initialization. +; - We assume that n=5, N=32 to simplify the code and compact the data. +; OHCI works in this way. UHCI and EHCI actually have n=10, N=1024, +; but this is an overkill for interrupt endpoints; the large value of N is +; useful only for isochronous transfers in UHCI and EHCI. UHCI/EHCI code +; initializes "leaf" lists k,k+32,k+64,...,k+(1024-32) to the same value, +; giving essentially N=32. +; This restriction means that the actual maximum interval of polling any +; interrupt endpoint is 32ms, which seems to be a reasonable value. +; - Similarly, there are 16 lists for 16-ms interval, 8 lists for 8-ms +; interval and so on. Finally, there is one list for 1ms interval. Their +; addresses are not directly known to the controller. +; - The hardware serves endpoints following a physical link from the hardware +; part. +; - The hardware links are organized as follows. If the list item is not the +; last, it's hardware link points to the next item. The hardware link of +; the last item points to the first item of the "next" list. +; - The "next" list for k-th and (k+M)-th periodic lists for interval 2M ms +; is the k-th periodic list for interval M ms, M >= 1. In this scheme, +; if two "previous" lists are served in the frames k,k+2M,k+4M,... +; and k+M,k+3M,k+5M,... correspondingly, the "next" list is served in +; the frames k,k+M,k+2M,k+3M,k+4M,k+5M,..., which is exactly what we want. +; - The links between Periodic, Control, Bulk lists and the processing of +; Isochronous endpoints are controller-specific. +; * The head of every processing list is a static entry which does not +; correspond to any real pipe. It is described by usb_static_ep +; structure, not usb_pipe. For OHCI and UHCI, sizeof.usb_static_ep plus +; sizeof hardware part is 20h, the total number of lists is +; 32+16+8+4+2+1+1+1 = 65, so all these structures fit in one page, +; leaving space for other data. This is another reason for 32ms limit. +; * Static endpoint descriptors are kept in *hci_controller structure. +; * All items in every processing list, including the static head, are +; organized in a double-linked list using .NextVirt and .PrevVirt fields. +; * [[item.NextVirt].PrevVirt] = [[item.PrevVirt].NextVirt] for all items. +NextVirt dd ? +; Next endpoint in the processing list. +; See also PrevVirt field and the description before NextVirt field. +PrevVirt dd ? +; Previous endpoint in the processing list. +; See also NextVirt field and the description before NextVirt field. +; +; Every pipe has the associated transfer queue, that is, the double-linked +; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt +; endpoints this list consists of usb_gtd structures +; (GTD = General Transfer Descriptors), for Isochronous endpoints +; this list consists of usb_itd structures, which are not developed yet. +; The pipe needs to know only the last TD; the first TD can be +; obtained as [[pipe.LastTD].NextVirt]. +LastTD dd ? +; Last TD in the transfer queue. +; +; All opened pipes corresponding to the same physical device are organized in +; the double-linked list using .NextSibling and .PrevSibling fields. +; The head of this list is kept in usb_device_data structure (OpenedPipeList). +; This list is used when the device is disconnected and all pipes for the +; device should be closed. +; Also, all pipes closed due to disconnect must remain valid at least until +; driver-provided disconnect function returns; all should-be-freed-but-not-now +; pipes for one device are organized in another double-linked list with +; the head in usb_device_data.ClosedPipeList; this list uses the same link +; fields, one pipe can never be in both lists. +NextSibling dd ? +; Next pipe for the physical device. +PrevSibling dd ? +; Previous pipe for the physical device. +; +; When hardware part of pipe is changed, some time is needed before further +; actions so that hardware reacts on this change. During that time, +; all changed pipes are organized in single-linked list with the head +; usb_controller.WaitPipeList* and link field NextWait. +; Currently there are two possible reasons to change: +; change of address/packet size in initial configuration, +; close of the pipe. They are distinguished by USB_FLAG_CLOSED. +NextWait dd ? +Lock MUTEX +; Mutex that guards operations with transfer queue for this pipe. +Type db ? +; Type of pipe, one of {CONTROL,ISOCHRONOUS,BULK,INTERRUPT}_PIPE. +Flags db ? +; Combination of flags, USB_FLAG_*. + rb 2 ; dword alignment +DeviceData dd ? +; Pointer to usb_device_data, common for all pipes for one device. +ends + +; This structure describes the static head of every list of pipes. +struct usb_static_ep +; software fields +Bandwidth dd ? +; valid only for interrupt/isochronous USB1 lists +; The offsets of the following two fields must be the same in this structure +; and in usb_pipe. +NextVirt dd ? +PrevVirt dd ? +ends + +; This structure represents one transfer descriptor +; ('g' stands for "general" as opposed to isochronous usb_itd). +; Note that one transfer can have several descriptors: +; a control transfer has three stages. +; Additionally, every controller has a limit on transfer length with +; one descriptor (packet size for UHCI, 1K for OHCI, 4K for EHCI), +; large transfers must be split into individual packets according to that limit. +struct usb_gtd +Callback dd ? +; Zero for intermediate descriptors, pointer to callback function +; for final descriptor. See the docs for description of the callback. +UserData dd ? +; Dword which is passed to Callback as is, not used by USB code itself. +; Two following fields organize all descriptors for one pipe in +; the linked list. +NextVirt dd ? +PrevVirt dd ? +Pipe dd ? +; Pointer to the parent usb_pipe. +Buffer dd ? +; Pointer to data for this descriptor. +Length dd ? +; Length of data for this descriptor. +ends + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +USB_STDCALL_VERIFY = 1 +macro stdcall_verify [arg] +{ +common +if USB_STDCALL_VERIFY + pushad + stdcall arg + call verify_regs + popad +else + stdcall arg +end if +} + +; Initialization of usb_static_ep structure, +; called from controller-specific initialization; edi -> usb_static_ep +proc usb_init_static_endpoint + mov [edi+usb_static_ep.NextVirt], edi + mov [edi+usb_static_ep.PrevVirt], edi + ret +endp + +; Part of API for drivers, see documentation for USBOpenPipe. +proc usb_open_pipe stdcall uses ebx esi edi,\ + config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword +locals +targetsmask dd ? ; S-Mask for USB2 +bandwidth dd ? +target dd ? +endl +; 1. Verify type of pipe: it must be one of *_PIPE constants. +; Isochronous pipes are not supported yet. + mov eax, [type] + cmp eax, INTERRUPT_PIPE + ja .badtype + cmp al, ISOCHRONOUS_PIPE + jnz .goodtype +.badtype: + dbgstr 'unsupported type of USB pipe' + jmp .return0 +.goodtype: +; 2. Allocate memory for pipe and transfer queue. +; Empty transfer queue consists of one inactive TD. + mov ebx, [config_pipe] + mov esi, [ebx+usb_pipe.Controller] + mov edx, [esi+usb_controller.HardwareFunc] + call [edx+usb_hardware_func.AllocPipe] + test eax, eax + jz .nothing + mov edi, eax + mov edx, [esi+usb_controller.HardwareFunc] + call [edx+usb_hardware_func.AllocTD] + test eax, eax + jz .free_and_return0 +; 3. Initialize transfer queue: pointer to transfer descriptor, +; pointers in transfer descriptor, queue lock. + mov [edi+usb_pipe.LastTD], eax + mov [eax+usb_gtd.NextVirt], eax + mov [eax+usb_gtd.PrevVirt], eax + mov [eax+usb_gtd.Pipe], edi + lea ecx, [edi+usb_pipe.Lock] + call mutex_init +; 4. Initialize software part of pipe structure, except device-related fields. + mov al, byte [type] + mov [edi+usb_pipe.Type], al + xor eax, eax + mov [edi+usb_pipe.Flags], al + mov [edi+usb_pipe.DeviceData], eax + mov [edi+usb_pipe.Controller], esi + or [edi+usb_pipe.NextWait], -1 +; 5. Initialize device-related fields: +; for zero endpoint, set .NextSibling = .PrevSibling = this; +; for other endpoins, copy device data, take the lock guarding pipe list +; for the device and verify that disconnect processing has not yet started +; for the device. (Since disconnect processing also takes that lock, +; either it has completed or it will not start until we release the lock.) +; Note: usb_device_disconnected should not see the new pipe until +; initialization is complete, so that lock will be held during next steps +; (disconnect processing should either not see it at all, or see fully +; initialized pipe). + cmp [endpoint], eax + jz .zero_endpoint + mov ecx, [ebx+usb_pipe.DeviceData] + mov [edi+usb_pipe.DeviceData], ecx + call mutex_lock + test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + jz .common +.fail: +; If disconnect processing has completed, unlock the mutex, free memory +; allocated in step 2 and return zero. + call mutex_unlock + mov edx, [esi+usb_controller.HardwareFunc] + stdcall [edx+usb_hardware_func.FreeTD], [edi+usb_pipe.LastTD] +.free_and_return0: + mov edx, [esi+usb_controller.HardwareFunc] + stdcall [edx+usb_hardware_func.FreePipe], edi +.return0: + xor eax, eax + jmp .nothing +.zero_endpoint: + mov [edi+usb_pipe.NextSibling], edi + mov [edi+usb_pipe.PrevSibling], edi +.common: +; 6. Initialize hardware part of pipe structure. +; 6a. Acquire the corresponding mutex. + lea ecx, [esi+usb_controller.ControlLock] + cmp [type], BULK_PIPE + jb @f ; control pipe + lea ecx, [esi+usb_controller.BulkLock] + jz @f ; bulk pipe + lea ecx, [esi+usb_controller.PeriodicLock] +@@: + call mutex_lock +; 6b. Let the controller-specific code do its job. + push ecx + mov edx, [esi+usb_controller.HardwareFunc] + mov eax, [edi+usb_pipe.LastTD] + mov ecx, [config_pipe] + call [edx+usb_hardware_func.InitPipe] + pop ecx +; 6c. Release the mutex. + push eax + call mutex_unlock + pop eax +; 6d. If controller-specific code indicates failure, +; release the lock taken in step 5, free memory allocated in step 2 +; and return zero. + test eax, eax + jz .fail +; 7. The pipe is initialized. If this is not the first pipe for the device, +; insert it to the tail of pipe list for the device, +; increment number of pipes, +; release the lock taken at step 5. + mov ecx, [edi+usb_pipe.DeviceData] + test ecx, ecx + jz @f + mov eax, [ebx+usb_pipe.PrevSibling] + mov [edi+usb_pipe.NextSibling], ebx + mov [edi+usb_pipe.PrevSibling], eax + mov [ebx+usb_pipe.PrevSibling], edi + mov [eax+usb_pipe.NextSibling], edi + inc [ecx+usb_device_data.NumPipes] + call mutex_unlock +@@: +; 8. Return pointer to usb_pipe. + mov eax, edi +.nothing: + ret +endp + +; This procedure is called several times during initial device configuration, +; when usb_device_data structure is reallocated. +; It (re)initializes all pointers in usb_device_data. +; ebx -> usb_pipe +proc usb_reinit_pipe_list + push eax +; 1. (Re)initialize the lock guarding pipe list. + mov ecx, [ebx+usb_pipe.DeviceData] + call mutex_init +; 2. Initialize list of opened pipes: two entries, the head and ebx. + add ecx, usb_device_data.OpenedPipeList - usb_pipe.NextSibling + mov [ecx+usb_pipe.NextSibling], ebx + mov [ecx+usb_pipe.PrevSibling], ebx + mov [ebx+usb_pipe.NextSibling], ecx + mov [ebx+usb_pipe.PrevSibling], ecx +; 3. Initialize list of closed pipes: empty list, only the head is present. + add ecx, usb_device_data.ClosedPipeList - usb_device_data.OpenedPipeList + mov [ecx+usb_pipe.NextSibling], ecx + mov [ecx+usb_pipe.PrevSibling], ecx + pop eax + ret +endp + +; Part of API for drivers, see documentation for USBClosePipe. +proc usb_close_pipe + push ebx esi ; save used registers to be stdcall +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.pipe dd ? +end virtual +; 1. Lock the pipe list for the device. + mov ebx, [.pipe] + mov esi, [ebx+usb_pipe.Controller] + mov ecx, [ebx+usb_pipe.DeviceData] + call mutex_lock +; 2. Set the flag "the driver has abandoned this pipe, free it at any time". + lea ecx, [ebx+usb_pipe.Lock] + call mutex_lock + or [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE + call mutex_unlock +; 3. Call the worker function. + call usb_close_pipe_nolock +; 4. Unlock the pipe list for the device. + mov ecx, [ebx+usb_pipe.DeviceData] + call mutex_unlock +; 5. Wakeup the USB thread so that it can proceed with releasing that pipe. + push edi + call usb_wakeup + pop edi +; 6. Return. + pop esi ebx ; restore used registers to be stdcall + retn 4 +endp + +; Worker function for pipe closing. Called by usb_close_pipe API and +; from disconnect processing. +; The lock guarding pipe list for the device should be held by the caller. +; ebx -> usb_pipe, esi -> usb_controller +proc usb_close_pipe_nolock +; 1. Set the flag "pipe is closed, ignore new transfers". +; If it was already set, do nothing. + lea ecx, [ebx+usb_pipe.Lock] + call mutex_lock + bts dword [ebx+usb_pipe.Flags], USB_FLAG_CLOSED_BIT + jc .closed + call mutex_unlock +; 2. Remove the pipe from the list of opened pipes. + mov eax, [ebx+usb_pipe.NextSibling] + mov edx, [ebx+usb_pipe.PrevSibling] + mov [eax+usb_pipe.PrevSibling], edx + mov [edx+usb_pipe.NextSibling], eax +; 3. Unlink the pipe from hardware structures. +; 3a. Acquire the corresponding lock. + lea edx, [esi+usb_controller.WaitPipeListAsync] + lea ecx, [esi+usb_controller.ControlLock] + cmp [ebx+usb_pipe.Type], BULK_PIPE + jb @f ; control pipe + lea ecx, [esi+usb_controller.BulkLock] + jz @f ; bulk pipe + add edx, usb_controller.WaitPipeListPeriodic - usb_controller.WaitPipeListAsync + lea ecx, [esi+usb_controller.PeriodicLock] +@@: + push edx + call mutex_lock + push ecx +; 3b. Let the controller-specific code do its job. + mov eax, [esi+usb_controller.HardwareFunc] + call [eax+usb_hardware_func.UnlinkPipe] +; 3c. Release the corresponding lock. + pop ecx + call mutex_unlock +; 4. Put the pipe into wait queue. + pop edx + cmp [ebx+usb_pipe.NextWait], -1 + jz .insert_new + or [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT + jmp .inserted +.insert_new: + mov eax, [edx] + mov [ebx+usb_pipe.NextWait], eax + mov [edx], ebx +.inserted: +; 5. Return. + ret +.closed: + call mutex_unlock + xor eax, eax + ret +endp + +; This procedure is called when a pipe with USB_FLAG_CLOSED is removed from the +; corresponding wait list. It means that the hardware has fully forgot about it. +; ebx -> usb_pipe, esi -> usb_controller +proc usb_pipe_closed + push edi + mov edi, [esi+usb_controller.HardwareFunc] +; 1. Loop over all transfers, calling the driver with USB_STATUS_CLOSED +; and freeing all descriptors. + mov edx, [ebx+usb_pipe.LastTD] + test edx, edx + jz .no_transfer + mov edx, [edx+usb_gtd.NextVirt] +.transfer_loop: + cmp edx, [ebx+usb_pipe.LastTD] + jz .transfer_done + mov ecx, [edx+usb_gtd.Callback] + test ecx, ecx + jz .no_callback + push edx + stdcall_verify ecx, ebx, USB_STATUS_CLOSED, \ + [edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData] + pop edx +.no_callback: + push [edx+usb_gtd.NextVirt] + stdcall [edi+usb_hardware_func.FreeTD], edx + pop edx + jmp .transfer_loop +.transfer_done: + stdcall [edi+usb_hardware_func.FreeTD], edx +.no_transfer: +; 2. Decrement number of pipes for the device. +; If this pipe is the last pipe, go to 5. + mov ecx, [ebx+usb_pipe.DeviceData] + call mutex_lock + dec [ecx+usb_device_data.NumPipes] + jz .last_pipe + call mutex_unlock +; 3. If the flag "the driver has abandoned this pipe" is set, +; free memory and return. + test [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE + jz .nofree + stdcall [edi+usb_hardware_func.FreePipe], ebx + pop edi + ret +; 4. Otherwise, add it to the list of closed pipes and return. +.nofree: + add ecx, usb_device_data.ClosedPipeList - usb_pipe.NextSibling + mov edx, [ecx+usb_pipe.PrevSibling] + mov [ebx+usb_pipe.NextSibling], ecx + mov [ebx+usb_pipe.PrevSibling], edx + mov [ecx+usb_pipe.PrevSibling], ebx + mov [edx+usb_pipe.NextSibling], ebx + pop edi + ret +.last_pipe: +; That was the last pipe for the device. +; 5. Notify device driver(s) about disconnect. + call mutex_unlock + movzx eax, [ecx+usb_device_data.NumInterfaces] + test eax, eax + jz .notify_done + add ecx, [ecx+usb_device_data.Interfaces] +.notify_loop: + mov edx, [ecx+usb_interface_data.DriverFunc] + test edx, edx + jz @f + mov edx, [edx+USBSRV.usb_func] + cmp [edx+USBFUNC.strucsize], USBFUNC.device_disconnect + 4 + jb @f + mov edx, [edx+USBFUNC.device_disconnect] + test edx, edx + jz @f + push eax ecx + stdcall_verify edx, [ecx+usb_interface_data.DriverData] + pop ecx eax +@@: + add ecx, sizeof.usb_interface_data + dec eax + jnz .notify_loop +.notify_done: +; 6. Bus address, if assigned, can now be reused. + call [edi+usb_hardware_func.GetDeviceAddress] + test eax, eax + jz @f + bts [esi+usb_controller.ExistingAddresses], eax +@@: + dbgstr 'USB device disconnected' +; 7. All drivers have returned from disconnect callback, +; so all drivers should not use any device-related pipes. +; Free the remaining pipes. + mov eax, [ebx+usb_pipe.DeviceData] + add eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling + push eax + mov eax, [eax+usb_pipe.NextSibling] +.free_loop: + cmp eax, [esp] + jz .free_done + push [eax+usb_pipe.NextSibling] + stdcall [edi+usb_hardware_func.FreePipe], eax + pop eax + jmp .free_loop +.free_done: + stdcall [edi+usb_hardware_func.FreePipe], ebx + pop eax +; 8. Free the usb_device_data structure. + sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling + call free +; 9. Return. +.nothing: + pop edi + ret +endp + +; Part of API for drivers, see documentation for USBNormalTransferAsync. +proc usb_normal_transfer_async stdcall uses ebx edi,\ + pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword +; 1. Sanity check: callback must be nonzero. +; (It is important for other parts of code.) + xor eax, eax + cmp [callback], eax + jz .nothing +; 2. Lock the transfer queue. + mov ebx, [pipe] + lea ecx, [ebx+usb_pipe.Lock] + call mutex_lock +; 3. If the pipe has already been closed (presumably due to device disconnect), +; release the lock taken in step 2 and return zero. + xor eax, eax + test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + jnz .unlock +; 4. Allocate and initialize TDs for the transfer. + mov edx, [ebx+usb_pipe.Controller] + mov edi, [edx+usb_controller.HardwareFunc] + stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], [ebx+usb_pipe.LastTD], 0 +; If failed, release the lock taken in step 2 and return zero. + test eax, eax + jz .unlock +; 5. Store callback and its parameters in the last descriptor for this transfer. + mov ecx, [eax+usb_gtd.PrevVirt] + mov edx, [callback] + mov [ecx+usb_gtd.Callback], edx + mov edx, [calldata] + mov [ecx+usb_gtd.UserData], edx + mov edx, [buffer] + mov [ecx+usb_gtd.Buffer], edx +; 6. Advance LastTD pointer and activate transfer. + push [ebx+usb_pipe.LastTD] + mov [ebx+usb_pipe.LastTD], eax + call [edi+usb_hardware_func.InsertTransfer] + pop eax +; 7. Release the lock taken in step 2 and +; return pointer to the first descriptor for the new transfer. +.unlock: + push eax + lea ecx, [ebx+usb_pipe.Lock] + call mutex_unlock + pop eax +.nothing: + ret +endp + +; Part of API for drivers, see documentation for USBControlTransferAsync. +proc usb_control_async stdcall uses ebx edi,\ + pipe:dword, config:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword +locals +last_td dd ? +endl +; 1. Sanity check: callback must be nonzero. +; (It is important for other parts of code.) + cmp [callback], 0 + jz .return0 +; 2. Lock the transfer queue. + mov ebx, [pipe] + lea ecx, [ebx+usb_pipe.Lock] + call mutex_lock +; 3. If the pipe has already been closed (presumably due to device disconnect), +; release the lock taken in step 2 and return zero. + test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + jnz .unlock_return0 +; A control transfer contains two or three stages: +; Setup stage, optional Data stage, Status stage. +; 4. Allocate and initialize TDs for the Setup stage. +; Payload is 8 bytes from [config]. + mov edx, [ebx+usb_pipe.Controller] + mov edi, [edx+usb_controller.HardwareFunc] + stdcall [edi+usb_hardware_func.AllocTransfer], [config], 8, 0, [ebx+usb_pipe.LastTD], (2 shl 2) + 0 + ; short transfer is an error, direction is DATA0, token is SETUP + mov [last_td], eax + test eax, eax + jz .fail +; 5. Allocate and initialize TDs for the Data stage, if [size] is nonzero. +; Payload is [size] bytes from [buffer]. + mov edx, [config] + mov ecx, (3 shl 2) + 1 ; DATA1, token is OUT + cmp byte [edx], 0 + jns @f + cmp [size], 0 + jz @f + inc ecx ; token is IN +@@: + cmp [size], 0 + jz .nodata + push ecx + stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], eax, ecx + pop ecx + test eax, eax + jz .fail + mov [last_td], eax +.nodata: +; 6. Allocate and initialize TDs for the Status stage. +; No payload. + xor ecx, 3 ; IN becomes OUT, OUT becomes IN + stdcall [edi+usb_hardware_func.AllocTransfer], 0, 0, 0, eax, ecx + test eax, eax + jz .fail +; 7. Store callback and its parameters in the last descriptor for this transfer. + mov ecx, [eax+usb_gtd.PrevVirt] + mov edx, [callback] + mov [ecx+usb_gtd.Callback], edx + mov edx, [calldata] + mov [ecx+usb_gtd.UserData], edx + mov edx, [buffer] + mov [ecx+usb_gtd.Buffer], edx +; 8. Advance LastTD pointer and activate transfer. + push [ebx+usb_pipe.LastTD] + mov [ebx+usb_pipe.LastTD], eax + call [edi+usb_hardware_func.InsertTransfer] +; 9. Release the lock taken in step 2 and +; return pointer to the first descriptor for the new transfer. + lea ecx, [ebx+usb_pipe.Lock] + call mutex_unlock + pop eax + ret +.fail: + mov eax, [last_td] + test eax, eax + jz .unlock_return0 + stdcall usb_undo_tds, [ebx+usb_pipe.LastTD] +.unlock_return0: + lea ecx, [ebx+usb_pipe.Lock] + call mutex_unlock +.return0: + xor eax, eax + ret +endp + +; Initialize software part of usb_gtd. Called from controller-specific code +; somewhere in AllocTransfer with eax -> next (inactive) usb_gtd, +; ebx -> usb_pipe, ebp frame from call to AllocTransfer with [.td] -> +; current (initializing) usb_gtd. +; Returns ecx = [.td]. +proc usb_init_transfer +virtual at ebp-4 +.Size dd ? + rd 2 +.Buffer dd ? + dd ? +.Flags dd ? +.td dd ? +end virtual + mov [eax+usb_gtd.Pipe], ebx + mov ecx, [.td] + mov [eax+usb_gtd.PrevVirt], ecx + mov edx, [ecx+usb_gtd.NextVirt] + mov [ecx+usb_gtd.NextVirt], eax + mov [eax+usb_gtd.NextVirt], edx + mov [edx+usb_gtd.PrevVirt], eax + mov edx, [.Size] + mov [ecx+usb_gtd.Length], edx + xor edx, edx + mov [ecx+usb_gtd.Callback], edx + mov [ecx+usb_gtd.UserData], edx + ret +endp + +; Free all TDs for the current transfer if something has failed +; during initialization (e.g. no memory for the next TD). +; Stdcall with one stack argument = first TD for the transfer +; and eax = last initialized TD for the transfer. +proc usb_undo_tds + push [eax+usb_gtd.NextVirt] +@@: + cmp eax, [esp+8] + jz @f + push [eax+usb_gtd.PrevVirt] + stdcall [edi+usb_hardware_func.FreeTD], eax + pop eax + jmp @b +@@: + pop ecx + mov [eax+usb_gtd.NextVirt], ecx + mov [ecx+usb_gtd.PrevVirt], eax + ret 4 +endp + +; Helper procedure for handling short packets in controller-specific code. +; Returns with CF cleared if this is the final packet in some stage: +; for control transfers that means one of Data and Status stages, +; for other transfers - the final packet in the only stage. +proc usb_is_final_packet + cmp [ebx+usb_gtd.Callback], 0 + jnz .nothing + mov eax, [ebx+usb_gtd.NextVirt] + cmp [eax+usb_gtd.Callback], 0 + jz .stc + mov eax, [ebx+usb_gtd.Pipe] + cmp [eax+usb_pipe.Type], CONTROL_PIPE + jz .nothing +.stc: + stc +.nothing: + ret +endp + +; Helper procedure for controller-specific code: +; removes one TD from the transfer queue, ebx -> usb_gtd to remove. +proc usb_unlink_td + mov ecx, [ebx+usb_gtd.Pipe] + add ecx, usb_pipe.Lock + call mutex_lock + mov eax, [ebx+usb_gtd.PrevVirt] + mov edx, [ebx+usb_gtd.NextVirt] + mov [edx+usb_gtd.PrevVirt], eax + mov [eax+usb_gtd.NextVirt], edx + call mutex_unlock + ret +endp + +if USB_STDCALL_VERIFY +proc verify_regs +virtual at esp + dd ? ; return address +.edi dd ? +.esi dd ? +.ebp dd ? +.esp dd ? +.ebx dd ? +.edx dd ? +.ecx dd ? +.eax dd ? +end virtual + cmp ebx, [.ebx] + jz @f + dbgstr 'ERROR!!! ebx changed' +@@: + cmp esi, [.esi] + jz @f + dbgstr 'ERROR!!! esi changed' +@@: + cmp edi, [.edi] + jz @f + dbgstr 'ERROR!!! edi changed' +@@: + cmp ebp, [.ebp] + jz @f + dbgstr 'ERROR!!! ebp changed' +@@: + ret +endp +end if diff --git a/kernel/trunk/bus/usb/protocol.inc b/kernel/trunk/bus/usb/protocol.inc new file mode 100644 index 0000000000..10fbcf2e72 --- /dev/null +++ b/kernel/trunk/bus/usb/protocol.inc @@ -0,0 +1,926 @@ +; Implementation of the USB protocol for device enumeration. +; Manage a USB device when it becomes ready for USB commands: +; configure, enumerate, load the corresponding driver(s), +; pass device information to the driver. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; USB standard request codes +USB_GET_STATUS = 0 +USB_CLEAR_FEATURE = 1 +USB_SET_FEATURE = 3 +USB_SET_ADDRESS = 5 +USB_GET_DESCRIPTOR = 6 +USB_SET_DESCRIPTOR = 7 +USB_GET_CONFIGURATION = 8 +USB_SET_CONFIGURATION = 9 +USB_GET_INTERFACE = 10 +USB_SET_INTERFACE = 11 +USB_SYNCH_FRAME = 12 + +; USB standard descriptor types +USB_DEVICE_DESCR = 1 +USB_CONFIG_DESCR = 2 +USB_STRING_DESCR = 3 +USB_INTERFACE_DESCR = 4 +USB_ENDPOINT_DESCR = 5 +USB_DEVICE_QUALIFIER_DESCR = 6 +USB_OTHER_SPEED_CONFIG_DESCR = 7 +USB_INTERFACE_POWER_DESCR = 8 + +; Possible speeds of USB devices +USB_SPEED_FS = 0 ; full-speed +USB_SPEED_LS = 1 ; low-speed +USB_SPEED_HS = 2 ; high-speed + +; Compile-time setting. If set, the code will dump all descriptors as they are +; read to the debug board. +USB_DUMP_DESCRIPTORS = 1 + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= +; USB descriptors. See USB specification for detailed explanations. +; First two bytes of every descriptor have the same meaning. +struct usb_descr +bLength db ? +; Size of this descriptor in bytes +bDescriptorType db ? +; One of USB_*_DESCR constants. +ends + +; USB device descriptor +struct usb_device_descr usb_descr +bcdUSB dw ? +; USB Specification Release number in BCD, e.g. 110h = USB 1.1 +bDeviceClass db ? +; USB Device Class Code +bDeviceSubClass db ? +; USB Device Subclass Code +bDeviceProtocol db ? +; USB Device Protocol Code +bMaxPacketSize0 db ? +; Maximum packet size for zero endpoint +idVendor dw ? +; Vendor ID +idProduct dw ? +; Product ID +bcdDevice dw ? +; Device release number in BCD +iManufacturer db ? +; Index of string descriptor describing manufacturer +iProduct db ? +; Index of string descriptor describing product +iSerialNumber db ? +; Index of string descriptor describing serial number +bNumConfigurations db ? +; Number of possible configurations +ends + +; USB configuration descriptor +struct usb_config_descr usb_descr +wTotalLength dw ? +; Total length of data returned for this configuration +bNumInterfaces db ? +; Number of interfaces in this configuration +bConfigurationValue db ? +; Value for SET_CONFIGURATION control request +iConfiguration db ? +; Index of string descriptor describing this configuration +bmAttributes db ? +; Bit 6 is SelfPowered, bit 5 is RemoteWakeupSupported, +; bit 7 must be 1, other bits must be 0 +bMaxPower db ? +; Maximum power consumption from the bus in 2mA units +ends + +; USB interface descriptor +struct usb_interface_descr usb_descr +; The following two fields work in pair. Sometimes one interface can work +; in different modes; e.g. videostream from web-cameras requires different +; bandwidth depending on resolution/quality/compression settings. +; Each mode of each interface has its own descriptor with its own endpoints +; following; all descriptors for one interface have the same bInterfaceNumber, +; and different bAlternateSetting. +; By default, any interface operates in mode with bAlternateSetting = 0. +; Often this is the only mode. If there are another modes, the active mode +; is selected by SET_INTERFACE(bAlternateSetting) control request. +bInterfaceNumber db ? +bAlternateSetting db ? +bNumEndpoints db ? +; Number of endpoints used by this interface, excluding zero endpoint +bInterfaceClass db ? +; USB Interface Class Code +bInterfaceSubClass db ? +; USB Interface Subclass Code +bInterfaceProtocol db ? +; USB Interface Protocol Code +iInterface db ? +; Index of string descriptor describing this interface +ends + +; USB endpoint descriptor +struct usb_endpoint_descr usb_descr +bEndpointAddress db ? +; Lower 4 bits form endpoint number, +; upper bit is 0 for OUT endpoints and 1 for IN endpoints, +; other bits must be zero +bmAttributes db ? +; Lower 2 bits form transfer type, one of *_PIPE, +; other bits must be zero for non-isochronous endpoints; +; refer to the USB specification for meaning in isochronous case +wMaxPacketSize dw ? +; Lower 11 bits form maximum packet size, +; next two bits specify the number of additional transactions per microframe +; for high-speed periodic endpoints, other bits must be zero. +bInterval db ? +; Interval for polling endpoint for data transfers. +; Isochronous and high-speed interrupt endpoints: poll every 2^(bInterval-1) +; (micro)frames +; Full/low-speed interrupt endpoints: poll every bInterval frames +; High-speed bulk/control OUT endpoints: maximum NAK rate +ends + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +; When a new device is ready to be configured, a controller-specific code +; calls usb_new_device. +; The sequence of further actions: +; * open pipe for the zero endpoint (usb_new_device); +; maximum packet size is not known yet, but it must be at least 8 bytes, +; so it is safe to send packets with <= 8 bytes +; * issue SET_ADDRESS control request (usb_new_device) +; * set the new device address in the pipe (usb_set_address_callback) +; * notify a controller-specific code that initialization of other ports +; can be started (usb_set_address_callback) +; * issue GET_DESCRIPTOR control request for first 8 bytes of device descriptor +; (usb_after_set_address) +; * first 8 bytes of device descriptor contain the true packet size for zero +; endpoint, so set the true packet size (usb_get_descr8_callback) +; * first 8 bytes of a descriptor contain the full size of this descriptor, +; issue GET_DESCRIPTOR control request for the full device descriptor +; (usb_after_set_endpoint_size) +; * issue GET_DESCRIPTOR control request for first 8 bytes of configuration +; descriptor (usb_get_descr_callback) +; * issue GET_DESCRIPTOR control request for full configuration descriptor +; (usb_know_length_callback) +; * issue SET_CONFIGURATION control request (usb_set_config_callback) +; * parse configuration descriptor, load the corresponding driver(s), +; pass the configuration descriptor to the driver and let the driver do +; the further work (usb_got_config_callback) + +; This function is called from controller-specific part +; when a new device is ready to be configured. +; in: ecx -> pseudo-pipe, part of usb_pipe +; in: esi -> usb_controller +; in: [esi+usb_controller.ResettingHub] is the pointer to usb_hub for device, +; NULL if the device is connected to the root hub +; in: [esi+usb_controller.ResettingPort] is the port for the device, zero-based +; in: [esi+usb_controller.ResettingSpeed] is the speed of the device, one of +; USB_SPEED_xx. +; out: eax = 0 <=> failed, the caller should disable the port. +proc usb_new_device + push ebx edi ; save used registers to be stdcall +; 1. Allocate resources. Any device uses the following resources: +; - device address in the bus +; - memory for device data +; - pipe for zero endpoint +; If some allocation fails, we must undo our actions. Closing the pipe +; is a hard task, so we avoid it and open the pipe as the last resource. +; The order for other two allocations is quite arbitrary. +; 1a. Allocate a bus address. + push ecx + call usb_set_address_request + pop ecx +; 1b. If failed, just return zero. + test eax, eax + jz .nothing +; 1c. Allocate memory for device data. +; For now, we need sizeof.usb_device_data and extra 8 bytes for GET_DESCRIPTOR +; input and output, see usb_after_set_address. Later we will reallocate it +; to actual size needed for descriptors. + push sizeof.usb_device_data + 8 + pop eax + push ecx + call malloc + pop ecx +; 1d. If failed, free the bus address and return zero. + test eax, eax + jz .nomemory +; 1e. Open pipe for endpoint zero. +; For now, we do not know the actual maximum packet size; +; for full-speed devices it can be any of 8, 16, 32, 64 bytes, +; low-speed devices must have 8 bytes, high-speed devices must have 64 bytes. +; Thus, we must use some fake "maximum packet size" until the actual size +; will be known. However, the maximum packet size must be at least 8, and +; initial stages of the configuration process involves only packets of <= 8 +; bytes, they will be transferred correctly as long as +; the fake "maximum packet size" is also at least 8. +; Thus, any number >= 8 is suitable for actual hardware. +; However, software emulation of EHCI in VirtualBox assumes that high-speed +; control transfers are those originating from pipes with max packet size = 64, +; even on early stages of the configuration process. This is incorrect, +; but we have no specific preferences, so let VirtualBox be happy and use 64 +; as the fake "maximum packet size". + push eax +; We will need many zeroes. +; "push edi" is one byte, "push 0" is two bytes; save space, use edi. + xor edi, edi + stdcall usb_open_pipe, ecx, edi, 64, edi, edi +; Put pointer to pipe into ebx. "xchg eax,reg" is one byte, mov is two bytes. + xchg eax, ebx + pop eax +; 1f. If failed, free the memory, the bus address and return zero. + test ebx, ebx + jz .freememory +; 2. Store pointer to device data in the pipe structure. + mov [ebx+usb_pipe.DeviceData], eax +; 3. Init device data, using usb_controller.Resetting* variables. + mov [eax+usb_device_data.NumPipes], 1 + mov [eax+usb_device_data.ConfigDataSize], edi + mov [eax+usb_device_data.Interfaces], edi + movzx ecx, [esi+usb_controller.ResettingPort] +; Note: the following write zeroes +; usb_device_data.DeviceDescrSize, usb_device_data.NumInterfaces, +; usb_device_data.Speed. + mov dword [eax+usb_device_data.Port], ecx + mov dl, [esi+usb_controller.ResettingSpeed] + mov [eax+usb_device_data.Speed], dl + mov edx, [esi+usb_controller.ResettingHub] + mov [eax+usb_device_data.Hub], edx +; 4. Store pointer to the config pipe in the hub data. +; Config pipe serves as device identifier. +; Root hubs use the array inside usb_controller structure, +; non-root hubs use the array immediately after usb_hub structure. + test edx, edx + jz .roothub + mov edx, [edx+usb_hub.ConnectedDevicesPtr] + mov [edx+ecx*4], ebx + jmp @f +.roothub: + mov [esi+usb_controller.DevicesByPort+ecx*4], ebx +@@: + call usb_reinit_pipe_list +; 5. Issue SET_ADDRESS control request, using buffer filled in step 1a. +; Use the return value from usb_control_async as our return value; +; if it is zero, then something has failed. + lea eax, [esi+usb_controller.SetAddressBuffer] + stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi +.nothing: +; 6. Return. + pop edi ebx ; restore used registers to be stdcall + ret +; Handlers of failures in steps 1b, 1d, 1f. +.freememory: + call free + jmp .freeaddr +.nomemory: + dbgstr 'No memory for device data' +.freeaddr: + mov ecx, dword [esi+usb_controller.SetAddressBuffer+2] + bts [esi+usb_controller.ExistingAddresses], ecx + xor eax, eax + jmp .nothing +endp + +; Helper procedure for usb_new_device. +; Allocates a new USB address and fills usb_controller.SetAddressBuffer +; with data for SET_ADDRESS(allocated_address) request. +; out: eax = 0 <=> failed +; Destroys edi. +proc usb_set_address_request +; There are 128 bits, one for each possible address. +; Note: only the USB thread works with usb_controller.ExistingAddresses, +; so there is no need for synchronization. +; We must find a bit set to 1 and clear it. +; 1. Find the first dword which has a nonzero bit = which is nonzero. + mov ecx, 128/32 + lea edi, [esi+usb_controller.ExistingAddresses] + xor eax, eax + repz scasd +; 2. If all dwords are zero, return an error. + jz .error +; 3. The dword at [edi-4] is nonzero. Find the lowest nonzero bit. + bsf eax, [edi-4] +; Now eax = bit number inside the dword at [edi-4]. +; 4. Clear the bit. + btr [edi-4], eax +; 5. Generate the address by edi = memory address and eax = bit inside dword. +; Address = eax + 8 * (edi-4 - (esi+usb_controller.ExistingAddress)). + sub edi, esi + lea edi, [eax+(edi-4-usb_controller.ExistingAddresses)*8] +; 6. Store the allocated address in SetAddressBuffer and fill remaining fields. +; Note that usb_controller is zeroed at allocation, so only command byte needs +; to be filled. + mov byte [esi+usb_controller.SetAddressBuffer+1], USB_SET_ADDRESS + mov dword [esi+usb_controller.SetAddressBuffer+2], edi +; 7. Return non-zero value in eax. + inc eax +.nothing: + ret +.error: + dbgstr 'cannot allocate USB address' + xor eax, eax + jmp .nothing +endp + +; This procedure is called by USB stack when SET_ADDRESS request initiated by +; usb_new_device is completed, either successfully or unsuccessfully. +; Note that USB stack uses esi = pointer to usb_controller. +proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword + push ebx ; save ebx to be stdcall +; Load data to registers for further references. + mov ebx, [pipe] + mov ecx, dword [esi+usb_controller.SetAddressBuffer+2] + mov eax, [esi+usb_controller.HardwareFunc] +; 1. Check whether the device has accepted new address. If so, proceed to 2. +; Otherwise, go to 3. + cmp [status], 0 + jnz .error +; 2. Address accepted. +; 2a. The controller-specific structure for the control pipe still uses +; zero address. Call the controller-specific function to change it to +; the actual address. +; Note that the hardware could cache the controller-specific structure, +; so setting the address could take some time until the cache is evicted. +; Thus, the call is asynchronous; meet us in usb_after_set_address when it will +; be safe to continue. + dbgstr 'address set in device' + call [eax+usb_hardware_func.SetDeviceAddress] +; 2b. If the port is in non-root hub, clear 'reset in progress' flag. +; In any case, proceed to 4. + mov eax, [esi+usb_controller.ResettingHub] + test eax, eax + jz .return + and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS +.return: +; 4. Address configuration done, we can proceed with other ports. +; Call the worker function for that. + call usb_test_pending_port +.nothing: + pop ebx ; restore ebx to be stdcall + ret +.error: +; 3. Device error: device not responding, disconnect etc. + DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status] +; 3a. The address has not been accepted. Mark it as free. + bts dword [esi+usb_controller.ExistingAddresses], ecx +; 3b. Disable the port with bad device. +; For the root hub, call the controller-specific function and go to 6. +; For non-root hubs, let the hub code do its work and return (the request +; could take some time, the hub code is responsible for proceeding). + cmp [esi+usb_controller.ResettingHub], 0 + jz .roothub + mov eax, [esi+usb_controller.ResettingHub] + call usb_hub_disable_resetting_port + jmp .nothing +.roothub: + movzx ecx, [esi+usb_controller.ResettingPort] + call [eax+usb_hardware_func.PortDisable] + jmp .return +endp + +; This procedure is called from usb_subscription_done when the hardware cache +; is cleared after request from usb_set_address_callback. +; in: ebx -> usb_pipe +proc usb_after_set_address + dbgstr 'address set for controller' +; Issue control transfer GET_DESCRIPTOR(DEVICE_DESCR) for first 8 bytes. +; Remember, we still do not know the actual packet size; +; 8-bytes-request is safe. +; usb_new_device has allocated 8 extra bytes besides sizeof.usb_device_data; +; use them for both input and output. + mov eax, [ebx+usb_pipe.DeviceData] + add eax, usb_device_data.DeviceDescriptor + mov dword [eax], \ + 80h + \ ; device-to-host, standard, device-wide + (USB_GET_DESCRIPTOR shl 8) + \ ; request + (0 shl 16) + \ ; descriptor index: there is only one + (USB_DEVICE_DESCR shl 24) ; descriptor type + mov dword [eax+4], 8 shl 16 ; data length + stdcall usb_control_async, ebx, eax, eax, 8, usb_get_descr8_callback, eax, 0 + ret +endp + +; This procedure is called by USB stack when GET_DESCRIPTOR(DEVICE_DESCR) +; request initiated by usb_after_set_address is completed, either successfully +; or unsuccessfully. +; Note that USB stack uses esi = pointer to usb_controller. +proc usb_get_descr8_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; mov eax, [buffer] +; DEBUGF 1,'K : descr8: l=%x; %x %x %x %x %x %x %x %x\n',[length],\ +; [eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2 + push edi ebx ; save used registers to be stdcall + mov ebx, [pipe] +; 1. Check whether the operation was successful. +; If not, say something to the debug board and stop the initialization. + cmp [status], 0 + jnz .error +; 2. Length of descriptor must be at least sizeof.usb_device_descr bytes. +; If not, say something to the debug board and stop the initialization. + mov eax, [ebx+usb_pipe.DeviceData] + cmp [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bLength], sizeof.usb_device_descr + jb .error +; 3. Now first 8 bytes of device descriptor are known; +; set DeviceDescrSize accordingly. + mov [eax+usb_device_data.DeviceDescrSize], 8 +; 4. The controller-specific structure for the control pipe still uses +; the fake "maximum packet size". Call the controller-specific function to +; change it to the actual packet size from the device. +; Note that the hardware could cache the controller-specific structure, +; so changing it could take some time until the cache is evicted. +; Thus, the call is asynchronous; meet us in usb_after_set_endpoint_size +; when it will be safe to continue. + movzx ecx, [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bMaxPacketSize0] + mov eax, [esi+usb_controller.HardwareFunc] + call [eax+usb_hardware_func.SetEndpointPacketSize] +.nothing: +; 5. Return. + pop ebx edi ; restore used registers to be stdcall + ret +.error: + dbgstr 'error with USB device descriptor' + jmp .nothing +endp + +; This procedure is called from usb_subscription_done when the hardware cache +; is cleared after request from usb_get_descr8_callback. +; in: ebx -> usb_pipe +proc usb_after_set_endpoint_size +; 1. Reallocate memory for device data: +; add memory for now-known size of device descriptor and extra 8 bytes +; for further actions. +; 1a. Allocate new memory. + mov eax, [ebx+usb_pipe.DeviceData] + movzx eax, [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bLength] +; save length for step 2 + push eax + add eax, sizeof.usb_device_data + 8 +; Note that malloc destroys ebx. + push ebx + call malloc + pop ebx +; 1b. If failed, say something to the debug board and stop the initialization. + test eax, eax + jz .nomemory +; 1c. Copy data from old memory to new memory and switch the pointer in usb_pipe. + push eax + push esi edi + mov esi, [ebx+usb_pipe.DeviceData] + mov [ebx+usb_pipe.DeviceData], eax + mov edi, eax + mov eax, esi +repeat sizeof.usb_device_data / 4 + movsd +end repeat + pop edi esi + call usb_reinit_pipe_list +; 1d. Free the old memory. +; Note that free destroys ebx. + push ebx + call free + pop ebx + pop eax +; 2. Issue control transfer GET_DESCRIPTOR(DEVICE) for full descriptor. +; restore length saved in step 1a + pop edx + add eax, sizeof.usb_device_data + mov dword [eax], \ + 80h + \ ; device-to-host, standard, device-wide + (USB_GET_DESCRIPTOR shl 8) + \ ; request + (0 shl 16) + \ ; descriptor index: there is only one + (USB_DEVICE_DESCR shl 24) ; descriptor type + and dword [eax+4], 0 + mov [eax+6], dl ; data length + stdcall usb_control_async, ebx, eax, eax, edx, usb_get_descr_callback, eax, 0 +; 3. Return. + ret +.nomemory: + dbgstr 'No memory for device data' + ret +endp + +; This procedure is called by USB stack when GET_DESCRIPTOR(DEVICE) +; request initiated by usb_after_set_endpoint_size is completed, +; either successfully or unsuccessfully. +proc usb_get_descr_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; Note: the prolog is the same as in usb_get_descr8_callback. + push edi ebx ; save used registers to be stdcall +; 1. Check whether the operation was successful. +; If not, say something to the debug board and stop the initialization. + cmp [status], 0 + jnz usb_get_descr8_callback.error +; The full descriptor is known, dump it if specified by compile-time option. +if USB_DUMP_DESCRIPTORS + mov eax, [buffer] + mov ecx, [length] + sub ecx, 8 + jbe .skipdebug + DEBUGF 1,'K : device descriptor:' +@@: + DEBUGF 1,' %x',[eax]:2 + inc eax + dec ecx + jnz @b + DEBUGF 1,'\n' +.skipdebug: +end if +; 2. Check that bLength is the same as was in the previous request. +; If not, say something to the debug board and stop the initialization. +; It is important, because usb_after_set_endpoint_size has allocated memory +; according to the old bLength. Note that [length] for control transfers +; includes 8 bytes of setup packet, so data length = [length] - 8. + mov eax, [buffer] + movzx ecx, [eax+usb_device_descr.bLength] + add ecx, 8 + cmp [length], ecx + jnz usb_get_descr8_callback.error +; Amuse the user if she is watching the debug board. + mov cl, [eax+usb_device_descr.bNumConfigurations] + DEBUGF 1,'K : found USB device with ID %x:%x, %d configuration(s)\n',\ + [eax+usb_device_descr.idVendor]:4,\ + [eax+usb_device_descr.idProduct]:4,\ + cl +; 3. If there are no configurations, stop the initialization. + cmp [eax+usb_device_descr.bNumConfigurations], 0 + jz .nothing +; 4. Copy length of device descriptor to device data structure. + movzx edx, [eax+usb_device_descr.bLength] + mov [eax+usb_device_data.DeviceDescrSize-usb_device_data.DeviceDescriptor], dl +; 5. Issue control transfer GET_DESCRIPTOR(CONFIGURATION). We do not know +; the full length of that descriptor, so start with first 8 bytes, they contain +; the full length. +; usb_after_set_endpoint_size has allocated 8 extra bytes after the +; device descriptor, use them for both input and output. + add eax, edx + mov dword [eax], \ + 80h + \ ; device-to-host, standard, device-wide + (USB_GET_DESCRIPTOR shl 8) + \ ; request + (0 shl 16) + \ ; descriptor index: there is only one + (USB_CONFIG_DESCR shl 24) ; descriptor type + mov dword [eax+4], 8 shl 16 ; data length + stdcall usb_control_async, [pipe], eax, eax, 8, usb_know_length_callback, eax, 0 +.nothing: +; 6. Return. + pop ebx edi ; restore used registers to be stdcall + ret +endp + +; This procedure is called by USB stack when GET_DESCRIPTOR(CONFIGURATION) +; request initiated by usb_get_descr_callback is completed, +; either successfully or unsuccessfully. +proc usb_know_length_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword + push ebx ; save used registers to be stdcall +; 1. Check whether the operation was successful. +; If not, say something to the debug board and stop the initialization. + cmp [status], 0 + jnz .error +; 2. Get the total length of data associated with config descriptor and store +; it in device data structure. The total length must be at least +; sizeof.usb_config_descr bytes; if not, say something to the debug board and +; stop the initialization. + mov eax, [buffer] + mov edx, [pipe] + movzx ecx, [eax+usb_config_descr.wTotalLength] + mov eax, [edx+usb_pipe.DeviceData] + cmp ecx, sizeof.usb_config_descr + jb .error + mov [eax+usb_device_data.ConfigDataSize], ecx +; 3. Reallocate memory for device data: +; include usb_device_data structure, device descriptor, +; config descriptor with all associated data, and extra bytes +; sufficient for 8 bytes control packet and for one usb_interface_data struc. +; Align extra bytes to dword boundary. +if sizeof.usb_interface_data > 8 +.extra_size = sizeof.usb_interface_data +else +.extra_size = 8 +end if +; 3a. Allocate new memory. + movzx edx, [eax+usb_device_data.DeviceDescrSize] + lea eax, [ecx+edx+sizeof.usb_device_data+.extra_size+3] + and eax, not 3 + push eax + call malloc + pop edx +; 3b. If failed, say something to the debug board and stop the initialization. + test eax, eax + jz .nomemory +; 3c. Copy data from old memory to new memory and switch the pointer in usb_pipe. + push eax + mov ebx, [pipe] + push esi edi + mov esi, [ebx+usb_pipe.DeviceData] + mov edi, eax + mov [ebx+usb_pipe.DeviceData], eax + mov eax, esi + movzx ecx, [esi+usb_device_data.DeviceDescrSize] + sub edx, .extra_size + mov [esi+usb_device_data.Interfaces], edx + add ecx, sizeof.usb_device_data + 8 + mov edx, ecx + shr ecx, 2 + and edx, 3 + rep movsd + mov ecx, edx + rep movsb + pop edi esi + call usb_reinit_pipe_list +; 3d. Free old memory. + call free + pop eax +; 4. Issue control transfer GET_DESCRIPTOR(DEVICE) for full descriptor. + movzx ecx, [eax+usb_device_data.DeviceDescrSize] + mov edx, [eax+usb_device_data.ConfigDataSize] + lea eax, [eax+ecx+sizeof.usb_device_data] + mov dword [eax], \ + 80h + \ ; device-to-host, standard, device-wide + (USB_GET_DESCRIPTOR shl 8) + \ ; request + (0 shl 16) + \ ; descriptor index: there is only one + (USB_CONFIG_DESCR shl 24) ; descriptor type + and dword [eax+4], 0 + mov word [eax+6], dx ; data length + stdcall usb_control_async, [pipe], eax, eax, edx, usb_set_config_callback, eax, 0 +.nothing: +; 5. Return. + pop ebx ; restore used registers to be stdcall + ret +.error: + dbgstr 'error with USB configuration descriptor' + jmp .nothing +.nomemory: + dbgstr 'No memory for device data' + jmp .nothing +endp + +; This procedure is called by USB stack when GET_DESCRIPTOR(CONFIGURATION) +; request initiated by usb_know_length_callback is completed, +; either successfully or unsuccessfully. +proc usb_set_config_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +; Note that the prolog is the same as in usb_know_length_callback. + push ebx ; save used registers to be stdcall +; 1. Check whether the operation was successful. +; If not, say something to the debug board and stop the initialization. + xor ecx, ecx + mov ebx, [pipe] + cmp [status], ecx + jnz usb_know_length_callback.error +; The full descriptor is known, dump it if specified by compile-time option. +if USB_DUMP_DESCRIPTORS + mov eax, [buffer] + mov ecx, [length] + sub ecx, 8 + jbe .skip_debug + DEBUGF 1,'K : config descriptor:' +@@: + DEBUGF 1,' %x',[eax]:2 + inc eax + dec ecx + jnz @b + DEBUGF 1,'\n' +.skip_debug: + xor ecx, ecx +end if +; 2. Issue control transfer SET_CONFIGURATION to activate this configuration. +; Usually this is the only configuration. +; Use extra bytes allocated by usb_know_length_callback; +; offset from device data start is stored in Interfaces. + mov eax, [ebx+usb_pipe.DeviceData] + mov edx, [buffer] + add eax, [eax+usb_device_data.Interfaces] + mov dl, [edx+usb_config_descr.bConfigurationValue] + mov dword [eax], USB_SET_CONFIGURATION shl 8 + mov dword [eax+4], ecx + mov byte [eax+2], dl + stdcall usb_control_async, [pipe], eax, ecx, ecx, usb_got_config_callback, [buffer], ecx + pop ebx ; restore used registers to be stdcall + ret +endp + +; This procedure is called by USB stack when SET_CONFIGURATION +; request initiated by usb_set_config_callback is completed, +; either successfully or unsuccessfully. +; If successfully, the device is configured and ready to work, +; pass the device to the corresponding driver(s). +proc usb_got_config_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword +locals +InterfacesData dd ? +NumInterfaces dd ? +driver dd ? +endl +; 1. If there was an error, say something to the debug board and stop the +; initialization. + cmp [status], 0 + jz @f + dbgstr 'USB error in SET_CONFIGURATION' + ret +@@: + push ebx edi ; save used registers to be stdcall +; 2. Sanity checks: the total length must be the same as before (because we +; have allocated memory assuming the old value), length of config descriptor +; must be at least sizeof.usb_config_descr (we use fields from it), +; there must be at least one interface. + mov ebx, [pipe] + mov ebx, [ebx+usb_pipe.DeviceData] + mov eax, [calldata] + mov edx, [ebx+usb_device_data.ConfigDataSize] + cmp [eax+usb_config_descr.wTotalLength], dx + jnz .invalid + cmp [eax+usb_config_descr.bLength], 9 + jb .invalid + movzx edx, [eax+usb_config_descr.bNumInterfaces] + test edx, edx + jnz @f +.invalid: + dbgstr 'error: invalid configuration descriptor' + jmp .nothing +@@: +; 3. Store the number of interfaces in device data structure. + mov [ebx+usb_device_data.NumInterfaces], dl +; 4. If there is only one interface (which happens quite often), +; the memory allocated in usb_know_length_callback is sufficient. +; Otherwise (which also happens quite often), reallocate device data. +; 4a. Check whether there is only one interface. If so, skip this step. + cmp edx, 1 + jz .has_memory +; 4b. Allocate new memory. + mov eax, [ebx+usb_device_data.Interfaces] + lea eax, [eax+edx*sizeof.usb_interface_data] + call malloc +; 4c. If failed, say something to the debug board and +; stop the initialization. + test eax, eax + jnz @f + dbgstr 'No memory for device data' + jmp .nothing +@@: +; 4d. Copy data from old memory to new memory and switch the pointer in usb_pipe. + push eax + push esi + mov ebx, [pipe] + mov edi, eax + mov esi, [ebx+usb_pipe.DeviceData] + mov [ebx+usb_pipe.DeviceData], eax + mov eax, esi + mov ecx, [esi+usb_device_data.Interfaces] + shr ecx, 2 + rep movsd + pop esi + call usb_reinit_pipe_list +; 4e. Free old memory. + call free + pop ebx +.has_memory: +; 5. Initialize interfaces table: zero all contents. + mov edi, [ebx+usb_device_data.Interfaces] + add edi, ebx + mov [InterfacesData], edi + movzx ecx, [ebx+usb_device_data.NumInterfaces] +if sizeof.usb_interface_data <> 8 +You have changed sizeof.usb_interface_data? Modify this place too. +end if + add ecx, ecx + xor eax, eax + rep stosd +; No interfaces are found yet. + mov [NumInterfaces], eax +; 6. Get the pointer to config descriptor data. +; Note: if there was reallocation, [buffer] is not valid anymore, +; so calculate value based on usb_device_data. + movzx eax, [ebx+usb_device_data.DeviceDescrSize] + lea eax, [eax+ebx+sizeof.usb_device_data] + mov [calldata], eax + mov ecx, [ebx+usb_device_data.ConfigDataSize] +; 7. Loop over all descriptors, +; scan for interface descriptors with bAlternateSetting = 0, +; load the corresponding driver, call its AddDevice function. +.descriptor_loop: +; While in loop: eax points to the current descriptor, +; ecx = number of bytes left, the iteration starts only if ecx is nonzero, +; edx = size of the current descriptor. +; 7a. The first byte is always accessible; it contains the length of +; the current descriptor. Validate that the length is at least 2 bytes, +; and the entire descriptor is readable (the length is at most number of +; bytes left). + movzx edx, [eax+usb_descr.bLength] + cmp edx, sizeof.usb_descr + jb .invalid + cmp ecx, edx + jb .invalid +; 7b. Check descriptor type. Ignore all non-INTERFACE descriptor. + cmp byte [eax+usb_descr.bDescriptorType], USB_INTERFACE_DESCR + jz .interface +.next_descriptor: +; 7c. Advance pointer, decrease length left, if there is still something left, +; continue the loop. + add eax, edx + sub ecx, edx + jnz .descriptor_loop +.done: +.nothing: + pop edi ebx ; restore used registers to be stdcall + ret +.interface: +; 7d. Validate the descriptor length. + cmp edx, sizeof.usb_interface_descr + jb .next_descriptor +; 7e. If bAlternateSetting is nonzero, this descriptor actually describes +; another mode of already known interface and belongs to the already loaded +; driver; amuse the user and continue to 7c. + cmp byte [eax+usb_interface_descr.bAlternateSetting], 0 + jz @f + DEBUGF 1,'K : note: alternate setting with %x/%x/%x\n',\ + [eax+usb_interface_descr.bInterfaceClass]:2,\ + [eax+usb_interface_descr.bInterfaceSubClass]:2,\ + [eax+usb_interface_descr.bInterfaceProtocol]:2 + jmp .next_descriptor +@@: +; 7f. Check that the new interface does not overflow allocated table. + mov edx, [NumInterfaces] + inc dl + jz .invalid + cmp dl, [ebx+usb_device_data.NumInterfaces] + ja .invalid +; 7g. We have found a new interface. Advance bookkeeping vars. + mov [NumInterfaces], edx + add [InterfacesData], sizeof.usb_interface_data +; 7h. Save length left and pointer to the current interface descriptor. + push ecx eax +; Amuse the user if she is watching the debug board. + DEBUGF 1,'K : USB interface class/subclass/protocol = %x/%x/%x\n',\ + [eax+usb_interface_descr.bInterfaceClass]:2,\ + [eax+usb_interface_descr.bInterfaceSubClass]:2,\ + [eax+usb_interface_descr.bInterfaceProtocol]:2 +; 7i. Select the correct driver based on interface class. +; For hubs, go to 7j. Otherwise, go to 7k. +; Note: this should be rewritten as table-based lookup when more drivers will +; be available. + cmp byte [eax+usb_interface_descr.bInterfaceClass], 9 + jz .found_hub + mov edx, usb_hid_name + cmp byte [eax+usb_interface_descr.bInterfaceClass], 3 + jz .load_driver + mov edx, usb_print_name + cmp byte [eax+usb_interface_descr.bInterfaceClass], 7 + jz .load_driver + mov edx, usb_stor_name + cmp byte [eax+usb_interface_descr.bInterfaceClass], 8 + jz .load_driver + mov edx, usb_other_name + jmp .load_driver +.found_hub: +; 7j. Hubs are a part of USB stack, thus, integrated into the kernel. +; Use the pointer to hub callbacks and go to 7m. + mov eax, usb_hub_pseudosrv - USBSRV.usb_func + jmp .driver_loaded +.load_driver: +; 7k. Load the corresponding driver. + push ebx esi edi + stdcall get_service, edx + pop edi esi ebx +; 7l. If failed, say something to the debug board and go to 7p. + test eax, eax + jnz .driver_loaded + dbgstr 'failed to load class driver' + jmp .next_descriptor2 +.driver_loaded: +; 7m. Call AddDevice function of the driver. +; Note that top of stack contains a pointer to the current interface, +; saved by step 7h. + mov [driver], eax + mov eax, [eax+USBSRV.usb_func] + pop edx + push edx +; Note: usb_hub_init assumes that edx points to usb_interface_descr, +; ecx = length rest; if you change the code, modify usb_hub_init also. + stdcall [eax+USBFUNC.add_device], [pipe], [calldata], edx +; 7n. If failed, say something to the debug board and go to 7p. + test eax, eax + jnz .store_data + dbgstr 'USB device initialization failed' + jmp .next_descriptor2 +.store_data: +; 7o. Store the returned value and the driver handle to InterfacesData. +; Note that step 7g has already advanced InterfacesData. + mov edx, [InterfacesData] + mov [edx+usb_interface_data.DriverData-sizeof.usb_interface_data], eax + mov eax, [driver] + mov [edx+usb_interface_data.DriverFunc-sizeof.usb_interface_data], eax +.next_descriptor2: +; 7p. Restore registers saved in step 7h, get the descriptor length and +; continue to 7c. + pop eax ecx + movzx edx, byte [eax+usb_descr.bLength] + jmp .next_descriptor +endp + +; Driver names, see step 7i of usb_got_config_callback. +iglobal +usb_hid_name db 'usbhid',0 +usb_stor_name db 'usbstor',0 +usb_print_name db 'usbprint',0 +usb_other_name db 'usbother',0 +endg diff --git a/kernel/trunk/bus/usb/scheduler.inc b/kernel/trunk/bus/usb/scheduler.inc new file mode 100644 index 0000000000..933eb9cbee --- /dev/null +++ b/kernel/trunk/bus/usb/scheduler.inc @@ -0,0 +1,508 @@ +; Implementation of periodic transaction scheduler for USB. +; Bandwidth dedicated to periodic transactions is limited, so +; different pipes should be scheduled as uniformly as possible. + +; USB1 scheduler. +; Algorithm is simple: +; when adding a pipe, optimize the following quantity: +; * for every millisecond, take all bandwidth scheduled to periodic transfers, +; * calculate maximum over all milliseconds, +; * select a variant which minimizes that maximum; +; when removing a pipe, do nothing (except for bookkeeping). + +; sanity check: structures in UHCI and OHCI should be the same +if (sizeof.ohci_static_ep=sizeof.uhci_static_ep)&(ohci_static_ep.SoftwarePart=uhci_static_ep.SoftwarePart)&(ohci_static_ep.NextList=uhci_static_ep.NextList) +; Select a list for a new pipe. +; in: esi -> usb_controller, maxpacket, type, interval can be found in the stack +; in: ecx = 2 * maximal interval = total number of periodic lists + 1 +; in: edx -> {u|o}hci_static_ep for the first list +; in: eax -> byte past {u|o}hci_static_ep for the last list in the first group +; out: edx -> usb_static_ep for the selected list or zero if failed +proc usb1_select_interrupt_list +; inherit some variables from usb_open_pipe +virtual at ebp-8 +.bandwidth dd ? +.target dd ? + dd ? + dd ? +.config_pipe dd ? +.endpoint dd ? +.maxpacket dd ? +.type dd ? +.interval dd ? +end virtual + push ebx edi ; save used registers to be stdcall + push eax ; save eax for checks in step 3 +; 1. Only intervals 2^k ms can be supported. +; The core specification says that the real interval should not be greater +; than the interval given by the endpoint descriptor, but can be less. +; Determine the actual interval as 2^k ms. + mov eax, ecx +; 1a. Set [.interval] to 1 if it was zero; leave it as is otherwise + cmp [.interval], 1 + adc [.interval], 0 +; 1b. Divide ecx by two while it is strictly greater than [.interval]. +@@: + shr ecx, 1 + cmp [.interval], ecx + jb @b +; ecx = the actual interval +; +; For example, let ecx = 8, eax = 64. +; The scheduler space is 32 milliseconds, +; we need to schedule something every 8 ms; +; there are 8 variants: schedule at times 0,8,16,24, +; schedule at times 1,9,17,25,..., schedule at times 7,15,23,31. +; Now concentrate: there are three nested loops, +; * the innermost loop calculates the total periodic bandwidth scheduled +; in the given millisecond, +; * the intermediate loop calculates the maximum over all milliseconds +; in the given variant, that is the quantity we're trying to minimize, +; * the outermost loop checks all variants. +; 2. Calculate offset between the first list and the first list for the +; selected interval, in bytes; save in the stack for step 4. + sub eax, ecx + sub eax, ecx + imul eax, sizeof.ohci_static_ep + push eax + imul ebx, ecx, sizeof.ohci_static_ep +; 3. Select the best variant. +; 3a. The outermost loop. +; Prepare for the loop: set the current optimal bandwidth to maximum +; possible value (so that any variant will pass the first comparison), +; calculate delta for the intermediate loop. + or [.bandwidth], -1 +.varloop: +; 3b. The intermediate loop. +; Prepare for the loop: set the maximum to be calculated to zero, +; save counter of the outermost loop. + xor edi, edi + push edx +virtual at esp +.cur_variant dd ? ; step 3b +.result_delta dd ? ; step 2 +.group1_limit dd ? ; function prolog +end virtual +.calc_max_bandwidth: +; 3c. The innermost loop. Sum over all lists. + xor eax, eax + push edx +.calc_bandwidth: + add eax, [edx+ohci_static_ep.SoftwarePart+usb_static_ep.Bandwidth] + mov edx, [edx+ohci_static_ep.NextList] + test edx, edx + jnz .calc_bandwidth + pop edx +; 3d. The intermediate loop continued: update maximum. + cmp eax, edi + jb @f + mov edi, eax +@@: +; 3e. The intermediate loop continued: advance counter. + add edx, ebx + cmp edx, [.group1_limit] + jb .calc_max_bandwidth +; 3e. The intermediate loop done: restore counter of the outermost loop. + pop edx +; 3f. The outermost loop continued: if the current variant is +; better (maybe not strictly) then the previous optimum, update +; the optimal bandwidth and resulting list. + cmp edi, [.bandwidth] + ja @f + mov [.bandwidth], edi + mov [.target], edx +@@: +; 3g. The outermost loop continued: advance counter. + add edx, sizeof.ohci_static_ep + dec ecx + jnz .varloop +; 4. Get the pointer to the best list. + pop edx ; restore value from step 2 + pop eax ; purge stack var from prolog + add edx, [.target] +; 5. Calculate bandwidth for the new pipe. + mov eax, [.maxpacket] ; TODO: calculate real bandwidth + and eax, (1 shl 11) - 1 +; 6. TODO: check that bandwidth for the new pipe plus old bandwidth +; still fits to maximum allowed by the core specification. +; 7. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return. + add edx, ohci_static_ep.SoftwarePart + add [edx+usb_static_ep.Bandwidth], eax + pop edi ebx ; restore used registers to be stdcall + ret +endp +; sanity check, part 2 +else +.err select_interrupt_list must be different for UHCI and OHCI +end if + +; Pipe is removing, update the corresponding lists. +; We do not reorder anything, so just update book-keeping variable +; in the list header. +proc usb1_interrupt_list_unlink +virtual at esp + dd ? ; return address +.maxpacket dd ? +.lowspeed db ? +.direction db ? + rb 2 +end virtual +; find list header + mov edx, ebx +@@: + mov edx, [edx+usb_pipe.NextVirt] + cmp [edx+usb_pipe.Controller], esi + jnz @b +; subtract pipe bandwidth +; TODO: calculate real bandwidth + mov eax, [.maxpacket] + and eax, (1 shl 11) - 1 + sub [edx+usb_static_ep.Bandwidth], eax + ret 8 +endp + +; USB2 scheduler. +; There are two parts: high-speed pipes and split-transaction pipes. +; Split-transaction scheduler is currently a stub. +; High-speed scheduler uses the same algorithm as USB1 scheduler: +; when adding a pipe, optimize the following quantity: +; * for every microframe, take all bandwidth scheduled to periodic transfers, +; * calculate maximum over all microframe, +; * select a variant which minimizes that maximum; +; when removing a pipe, do nothing (except for bookkeeping). +; in: esi -> usb_controller +; out: edx -> usb_static_ep, eax = S-Mask +proc ehci_select_hs_interrupt_list +; inherit some variables from usb_open_pipe +virtual at ebp-12 +.targetsmask dd ? +.bandwidth dd ? +.target dd ? + dd ? + dd ? +.config_pipe dd ? +.endpoint dd ? +.maxpacket dd ? +.type dd ? +.interval dd ? +end virtual +; prolog, initialize local vars + or [.bandwidth], -1 + or [.target], -1 + or [.targetsmask], -1 + push ebx edi ; save used registers to be stdcall +; 1. In EHCI, every list describes one millisecond = 8 microframes. +; Thus, there are two significantly different branches: +; for pipes with interval >= 8 microframes, advance to 2, +; for pipes which should be planned in every frame (one or more microframes), +; go to 9. +; Note: the actual interval for high-speed devices is 2^([.interval]-1), +; (the core specification forbids [.interval] == 0) + mov ecx, [.interval] + dec ecx + cmp ecx, 3 + jb .every_frame +; 2. Determine the actual interval in milliseconds. + sub ecx, 3 + cmp ecx, 5 ; maximum 32ms + jbe @f + push 5 + pop ecx +@@: +; There are four nested loops, +; * Loop #4 (the innermost one) calculates the total periodic bandwidth +; scheduled in the given microframe of the given millisecond. +; * Loop #3 calculates the maximum over all milliseconds +; in the given variant, that is the quantity we're trying to minimize. +; * Loops #1 and #2 check all variants; +; loop #1 is responsible for the target millisecond, +; loop #2 is responsible for the microframe within millisecond. +; 3. Prepare for loops. +; ebx = number of iterations of loop #1 +; [esp] = delta of counter for loop #3, in bytes +; [esp+4] = delta between the first group and the target group, in bytes + push 1 + pop ebx + push sizeof.ehci_static_ep + pop edx + shl ebx, cl + shl edx, cl + mov eax, 64*sizeof.ehci_static_ep + sub eax, edx + sub eax, edx + push eax + push edx +; 4. Select the best variant. +; 4a. Loop #1: initialize counter = pointer to ehci_static_ep for +; the target millisecond in the first group. + lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] +.varloop0: +; 4b. Loop #2: initialize counter = microframe within the target millisecond. + xor ecx, ecx +.varloop: +; 4c. Loop #3: save counter of loop #1, +; initialize counter with the value of loop #1 counter, +; initialize maximal bandwidth = zero. + xor edi, edi + push edx +virtual at esp +.saved_counter1 dd ? ; step 4c +.loop3_delta dd ? ; step 3 +.target_delta dd ? ; step 3 +end virtual +.calc_max_bandwidth: +; 4d. Loop #4: initialize counter with the value of loop #3 counter, +; initialize total bandwidth = zero. + xor eax, eax + push edx +.calc_bandwidth: +; 4e. Loop #4: add the bandwidth from the current list +; and advance to the next list, while there is one. + add ax, [edx+ehci_static_ep.Bandwidths+ecx*2] + mov edx, [edx+ehci_static_ep.NextList] + test edx, edx + jnz .calc_bandwidth +; 4f. Loop #4 end: restore counter of loop #3. + pop edx +; 4g. Loop #3: update maximal bandwidth. + cmp eax, edi + jb @f + mov edi, eax +@@: +; 4h. Loop #3: advance the counter and repeat while within the first group. + lea eax, [esi+ehci_controller.IntEDs+32*sizeof.ehci_static_ep-sizeof.ehci_controller] + add edx, [.loop3_delta] + cmp edx, eax + jb .calc_max_bandwidth +; 4i. Loop #3 end: restore counter of loop #1. + pop edx +; 4j. Loop #2: if the current variant is better (maybe not strictly) +; then the previous optimum, update the optimal bandwidth and the target. + cmp edi, [.bandwidth] + ja @f + mov [.bandwidth], edi + mov [.target], edx + push 1 + pop eax + shl eax, cl + mov [.targetsmask], eax +@@: +; 4k. Loop #2: continue 8 times for every microframe. + inc ecx + cmp ecx, 8 + jb .varloop +; 4l. Loop #1: advance counter and repeat ebx times, +; ebx was calculated in step 3. + add edx, sizeof.ehci_static_ep + dec ebx + jnz .varloop0 +; 5. Get the pointer to the best list. + pop edx ; restore value from step 3 + pop edx ; get delta calculated in step 3 + add edx, [.target] +; 6. Calculate bandwidth for the new pipe. +; TODO1: calculate real bandwidth + mov eax, [.maxpacket] + mov ecx, eax + and eax, (1 shl 11) - 1 + shr ecx, 11 + inc ecx + and ecx, 3 + imul eax, ecx +; 7. TODO2: check that bandwidth for the new pipe plus old bandwidth +; still fits to maximum allowed by the core specification +; current [.bandwidth] + new bandwidth <= limit; +; USB2 specification allows maximum 60000*80% bit times for periodic microframe +; 8. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return. + mov ecx, [.targetsmask] + add [edx+ehci_static_ep.Bandwidths+ecx*2], ax + add edx, ehci_static_ep.SoftwarePart + push 1 + pop eax + shl eax, cl + pop edi ebx ; restore used registers to be stdcall + ret +.every_frame: +; The pipe should be scheduled every frame in two or more microframes. +; 9. Calculate maximal bandwidth for every microframe: three nested loops. +; 9a. The outermost loop: ebx = microframe to calculate. + xor ebx, ebx +.calc_all_bandwidths: +; 9b. The intermediate loop: +; edx = pointer to ehci_static_ep in the first group, [esp] = counter, +; edi = maximal bandwidth + lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] + xor edi, edi + push 32 +.calc_max_bandwidth2: +; 9c. The innermost loop: calculate bandwidth for the given microframe +; in the given frame. + xor eax, eax + push edx +.calc_bandwidth2: + add ax, [edx+ehci_static_ep.Bandwidths+ebx*2] + mov edx, [edx+ehci_static_ep.NextList] + test edx, edx + jnz .calc_bandwidth2 + pop edx +; 9d. The intermediate loop continued: update maximal bandwidth. + cmp eax, edi + jb @f + mov edi, eax +@@: + add edx, sizeof.ehci_static_ep + dec dword [esp] + jnz .calc_max_bandwidth2 + pop eax +; 9e. Push the calculated maximal bandwidth and continue the outermost loop. + push edi + inc ebx + cmp ebx, 8 + jb .calc_all_bandwidths +virtual at esp +.bandwidth7 dd ? +.bandwidth6 dd ? +.bandwidth5 dd ? +.bandwidth4 dd ? +.bandwidth3 dd ? +.bandwidth2 dd ? +.bandwidth1 dd ? +.bandwidth0 dd ? +end virtual +; 10. Select the best variant. +; edx = S-Mask = bitmask of scheduled microframes + push 0x11 + pop edx + cmp ecx, 1 + ja @f + mov dl, 0x55 + jz @f + mov dl, 0xFF +@@: +; try all variants edx, edx shl 1, edx shl 2, ... +; until they fit in the lower byte (8 microframes per frame) +.select_best_mframe: + xor edi, edi + mov ecx, edx + mov eax, esp +.calc_mframe: + add cl, cl + jnc @f + cmp edi, [eax] + jae @f + mov edi, [eax] +@@: + add eax, 4 + test cl, cl + jnz .calc_mframe + cmp [.bandwidth], edi + jb @f + mov [.bandwidth], edi + mov [.targetsmask], edx +@@: + add dl, dl + jnc .select_best_mframe +; 11. Restore stack after step 9. + add esp, 8*4 +; 12. Get the pointer to the target list (responsible for every microframe). + lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+62*sizeof.ehci_static_ep-sizeof.ehci_controller] +; 13. TODO1: calculate real bandwidth. + mov eax, [.maxpacket] + mov ecx, eax + and eax, (1 shl 11) - 1 + shr ecx, 11 + inc ecx + and ecx, 3 + imul eax, ecx +; 14. TODO2: check that current [.bandwidth] + new bandwidth <= limit; +; USB2 specification allows maximum 60000*80% bit times for periodic microframe. +; Update bandwidths including the new pipe. + mov ecx, [.targetsmask] + lea edi, [edx+ehci_static_ep.Bandwidths-ehci_static_ep.SoftwarePart] +.update_bandwidths: + shr ecx, 1 + jnc @f + add [edi], ax +@@: + add edi, 2 + test ecx, ecx + jnz .update_bandwidths +; 15. Return target list and target S-Mask. + mov eax, [.targetsmask] + pop edi ebx ; restore used registers to be stdcall + ret +endp + +; Pipe is removing, update the corresponding lists. +; We do not reorder anything, so just update book-keeping variable +; in the list header. +proc ehci_hs_interrupt_list_unlink +; get target list + mov edx, [ebx+ehci_pipe.BaseList-ehci_pipe.SoftwarePart] +; TODO: calculate real bandwidth + movzx eax, word [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart+2] + mov ecx, [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart] + and eax, (1 shl 11) - 1 + shr ecx, 30 + imul eax, ecx + movzx ecx, byte [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart] + add edx, ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart +; update bandwidth +.dec_bandwidth: + shr ecx, 1 + jnc @f + sub [edx], ax +@@: + add edx, 2 + test ecx, ecx + jnz .dec_bandwidth +; return + ret +endp + +uglobal +ehci_last_fs_alloc dd ? +endg + +; This needs to be rewritten. Seriously. +; It schedules everything to the first microframe of some frame, +; frame is spinned out of thin air. +; This works while you have one keyboard and one mouse... +; maybe even ten keyboards and ten mice... but give any serious stress, +; and this would break. +proc ehci_select_fs_interrupt_list +virtual at ebp-12 +.targetsmask dd ? +.bandwidth dd ? +.target dd ? + dd ? + dd ? +.config_pipe dd ? +.endpoint dd ? +.maxpacket dd ? +.type dd ? +.interval dd ? +end virtual + cmp [.interval], 1 + adc [.interval], 0 + mov ecx, 64 + mov eax, ecx +@@: + shr ecx, 1 + cmp [.interval], ecx + jb @b + sub eax, ecx + sub eax, ecx + dec ecx + and ecx, [ehci_last_fs_alloc] + inc [ehci_last_fs_alloc] + add eax, ecx + imul eax, sizeof.ehci_static_ep + lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+eax-sizeof.ehci_controller] + mov ax, 1C01h + ret +endp + +proc ehci_fs_interrupt_list_unlink + ret +endp diff --git a/kernel/trunk/bus/usb/uhci.inc b/kernel/trunk/bus/usb/uhci.inc new file mode 100644 index 0000000000..36ccc36d2f --- /dev/null +++ b/kernel/trunk/bus/usb/uhci.inc @@ -0,0 +1,1817 @@ +; Code for UHCI controllers. +; Note: it should be moved to an external driver, +; it was convenient to have this code compiled into the kernel during initial +; development, but there are no reasons to keep it here. + +; ============================================================================= +; ================================= Constants ================================= +; ============================================================================= +; UHCI register declarations +UhciCommandReg = 0 +UhciStatusReg = 2 +UhciInterruptReg = 4 +UhciFrameNumberReg = 6 +UhciBaseAddressReg = 8 +UhciSOFModifyReg = 0Ch +UhciPort1StatusReg = 10h +; possible PIDs for USB data transfers +USB_PID_SETUP = 2Dh +USB_PID_IN = 69h +USB_PID_OUT = 0E1h +; UHCI does not support an interrupt on root hub status change. We must poll +; the controller periodically. This is the period in timer ticks (10ms). +; We use the value 100 ms: it is valid value for USB hub poll rate (1-255 ms), +; small enough to be responsible to connect events and large enough to not +; load CPU too often. +UHCI_POLL_INTERVAL = 100 +; the following constant is an invalid encoding for length fields in +; uhci_gtd; it is used to check whether an inactive TD has been +; completed (actual length of the transfer is valid) or not processed at all +; (actual length of the transfer is UHCI_INVALID_LENGTH). +; Valid values are 0-4FFh and 7FFh. We use 700h as an invalid value. +UHCI_INVALID_LENGTH = 700h + +; ============================================================================= +; ================================ Structures ================================= +; ============================================================================= + +; UHCI-specific part of a pipe descriptor. +; * The structure corresponds to the Queue Head aka QH from the UHCI +; specification with some additional fields. +; * The hardware uses first two fields (8 bytes). Next two fields are used for +; software book-keeping. +; * The hardware requires 16-bytes alignment of the hardware part. +; Since the allocator (usb_allocate_common) allocates memory sequentially +; from page start (aligned on 0x1000 bytes), size of the structure must be +; divisible by 16. +struct uhci_pipe +NextQH dd ? +; 1. First bit (bit 0) is Terminate bit. 1 = there is no next QH. +; 2. Next bit (bit 1) is QH/TD select bit. 1 = NextQH points to QH. +; 3. Next two bits (bits 2-3) are reserved. +; 4. With masked 4 lower bits, this is the physical address of the next QH in +; the QH list. +; See also the description before NextVirt field of the usb_pipe +; structure. Additionally to that description, the following is specific for +; the UHCI controller: +; * n=10, N=1024. However, this number is quite large. +; * 1024 lists are used only for individual transfer descriptors for +; Isochronous endpoints. This means that the software can sleep up to 1024 ms +; before initiating the next portion of a large isochronous transfer, which +; is a sufficiently large value. +; * We use the 32ms upper limit for interrupt endpoint polling interval. +; This seems to be a reasonable value. +; * The "next" list for last Periodic list is the Control list. +; * The "next" list for Control list is Bulk list and the "next" +; list for Bulk list is Control list. This loop is used for bandwidth +; reclamation: the hardware traverses lists until end-of-frame. +HeadTD dd ? +; 1. First bit (bit 0) is Terminate bit. 1 = there is no TDs in this QH. +; 2. Next bit (bit 1) is QH/TD select bit. 1 = HeadTD points to QH. +; 3. Next two bits (bits 2-3) are reserved. +; 4. With masked 4 lower bits, this is the physical address of the first TD in +; the TD queue for this QH. +Token dd ? +; This field is a template for uhci_gtd.Token field in transfer +; descriptors. The meaning of individual bits is the same as for +; uhci_gtd.Token, except that PID bitfield is always +; USB_PID_SETUP/IN/OUT for control/in/out pipes, +; the MaximumLength bitfield encodes maximum packet size, +; the Reserved bit 20 is LowSpeedDevice bit. +ErrorTD dd ? +; Usually NULL. If nonzero, it is a pointer to descriptor which was error'd +; and should be freed sometime in the future (the hardware could still use it). +SoftwarePart rd sizeof.usb_pipe/4 +; Common part for all controllers, described by usb_pipe structure. +ends + +if sizeof.uhci_pipe mod 16 +.err uhci_pipe must be 16-bytes aligned +end if + +; This structure describes the static head of every list of pipes. +; The hardware requires 16-bytes alignment of this structure. +; All instances of this structure are located sequentially in uhci_controller, +; uhci_controller is page-aligned, so it is sufficient to make this structure +; 16-bytes aligned and verify that the first instance is 16-bytes aligned +; inside uhci_controller. +struct uhci_static_ep +NextQH dd ? +; Same as uhci_pipe.NextQH. +HeadTD dd ? +; Same as uhci_pipe.HeadTD. +NextList dd ? +; Virtual address of the next list. + dd ? +; Not used. +SoftwarePart rd sizeof.usb_static_ep/4 +; Common part for all controllers, described by usb_static_ep structure. + dd ? +; Padding for 16-byte alignment. +ends + +if sizeof.uhci_static_ep mod 16 +.err uhci_static_ep must be 16-bytes aligned +end if + +; UHCI-specific part of controller data. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 4096 bytes and corresponds to +; the Frame List from UHCI specification. +; * The hardware requires page-alignment of the hardware part, so +; the entire descriptor must be page-aligned. +; This structure is allocated with kernel_alloc (see usb_init_controller), +; this gives page-aligned data. +struct uhci_controller +; ------------------------------ hardware fields ------------------------------ +FrameList rd 1024 +; Entry n corresponds to the head of the frame list to be executed in +; the frames n,n+1024,n+2048,n+3096,... +; The first bit of each entry is Terminate bit, 1 = the frame is empty. +; The second bit of each entry is QH/TD select bit, 1 = the entry points to +; QH, 0 = to TD. +; With masked 2 lower bits, the entry is a physical address of the first QH/TD +; to be executed. +; ------------------------------ software fields ------------------------------ +; Every list has the static head, which is an always empty QH. +; The following fields are static heads, one per list: +; 32+16+8+4+2+1 = 63 for Periodic lists, 1 for Control list and 1 for Bulk list. +IntEDs uhci_static_ep + rb 62 * sizeof.uhci_static_ep +ControlED uhci_static_ep +BulkED uhci_static_ep +IOBase dd ? +; Base port in I/O space for UHCI controller. +; UHCI register UhciXxx is addressed as in/out to IOBase + UhciXxx, +; see declarations in the beginning of this source. +DeferredActions dd ? +; Bitmask of bits from UhciStatusReg which need to be processed +; by uhci_process_deferred. Bit 0 = a transaction with IOC bit +; has completed. Bit 1 = a transaction has failed. Set by uhci_irq, +; cleared by uhci_process_deferred. +LastPollTime dd ? +; See the comment before UHCI_POLL_INTERVAL. This variable keeps the +; last time, in timer ticks, when the polling was done. +ends + +if uhci_controller.IntEDs mod 16 +.err Static endpoint descriptors must be 16-bytes aligned inside uhci_controller +end if + +; UHCI general transfer descriptor. +; * The structure describes non-Isochronous data transfers +; for the UHCI controller. +; * The structure includes two parts, the hardware part and the software part. +; * The hardware part consists of first 16 bytes and corresponds to the +; Transfer Descriptor aka TD from UHCI specification. +; * The hardware requires 16-bytes alignment of the hardware part, so +; the entire descriptor must be 16-bytes aligned. Since the allocator +; (uhci_allocate_common) allocates memory sequentially from page start +; (aligned on 0x1000 bytes), size of the structure must be divisible by 16. +struct uhci_gtd +NextTD dd ? +; 1. First bit (bit 0) is Terminate bit. 1 = there is no next TD. +; 2. Next bit (bit 1) is QH/TD select bit. 1 = NextTD points to QH. +; This bit is always set to 0 in the implementation. +; 3. Next bit (bit 2) is Depth/Breadth select bit. 1 = the controller should +; proceed to the NextTD after this TD is complete. 0 = the controller +; should proceed to the next endpoint after this TD is complete. +; The implementation sets this bit to 0 for final stages of all transactions +; and to 1 for other stages. +; 4. Next bit (bit 3) is reserved and must be zero. +; 5. With masked 4 lower bits, this is the physical address of the next TD +; in the TD list. +ControlStatus dd ? +; 1. Lower 11 bits (bits 0-10) are ActLen. This is written by the controller +; at the conclusion of a USB transaction to indicate the actual number of +; bytes that were transferred minus 1. +; 2. Next 6 bits (bits 11-16) are reserved. +; 3. Next bit (bit 17) signals Bitstuff error. +; 4. Next bit (bit 18) signals CRC/Timeout error. +; 5. Next bit (bit 19) signals NAK receive. +; 6. Next bit (bit 20) signals Babble error. +; 7. Next bit (bit 21) signals Data Buffer error. +; 8. Next bit (bit 22) signals Stall error. +; 9. Next bit (bit 23) is Active field. 1 = this TD should be processed. +; 10. Next bit (bit 24) is InterruptOnComplete bit. 1 = the controller should +; issue an interrupt on completion of the frame in which this TD is +; executed. +; 11. Next bit (bit 25) is IsochronousSelect bit. 1 = this TD is isochronous. +; 12. Next bit (bit 26) is LowSpeedDevice bit. 1 = this TD is for low-speed. +; 13. Next two bits (bits 27-28) are ErrorCounter field. This field is +; decremented by the controller on every non-fatal error with this TD. +; Babble and Stall are considered fatal errors and immediately deactivate +; the TD without decrementing this field. 0 = no error limit, +; n = deactivate the TD after n errors. +; 14. Next bit (bit 29) is ShortPacketDetect bit. 1 = short packet is an error. +; Note: the specification defines this bit as input for the controller, +; but does not specify the value written by controller. +; Some controllers (e.g. Intel) keep the value, some controllers (e.g. VIA) +; set the value to whether a short packet was actually detected +; (or something like that). +; Thus, we duplicate this bit as bit 0 of OrigBufferInfo. +; 15. Upper two bits (bits 30-31) are reserved. +Token dd ? +; 1. Lower 8 bits (bits 0-7) are PID, one of USB_PID_*. +; 2. Next 7 bits (bits 8-14) are DeviceAddress field. This is the address of +; the target device on the USB bus. +; 3. Next 4 bits (bits 15-18) are Endpoint field. This is the target endpoint +; number. +; 4. Next bit (bit 19) is DataToggle bit. n = issue/expect DATAn token. +; 5. Next bit (bit 20) is reserved. +; 6. Upper 11 bits (bits 21-31) are MaximumLength field. This field specifies +; the maximum number of data bytes for the transfer minus 1 byte. Null data +; packet is encoded as 0x7FF, maximum possible non-null data packet is 1280 +; bytes, encoded as 0x4FF. +Buffer dd ? +; Physical address of the data buffer for this TD. +OrigBufferInfo dd ? +; Usually NULL. If the original buffer crosses a page boundary, this is a +; pointer to the structure uhci_original_buffer for this request. +; bit 0: 1 = short packet is NOT allowed +; (before the TD is processed, it is the copy of bit 29 of ControlStatus; +; some controllers modify that bit, so we need a copy in a safe place) +SoftwarePart rd sizeof.usb_gtd/4 +; Software part, common for all controllers. +ends + +if sizeof.uhci_gtd mod 16 +.err uhci_gtd must be 16-bytes aligned +end if + +; UHCI requires that the entire transfer buffer should be on one page. +; If the actual buffer crosses page boundary, uhci_alloc_packet +; allocates additional memory for buffer for hardware. +; This structure describes correspondence between two buffers. +struct uhci_original_buffer +OrigBuffer dd ? +UsedBuffer dd ? +ends + +; Description of UHCI-specific data and functions for +; controller-independent code. +; Implements the structure usb_hardware_func from hccommon.inc for UHCI. +iglobal +align 4 +uhci_hardware_func: + dd 'UHCI' + dd sizeof.uhci_controller + dd uhci_init + dd uhci_process_deferred + dd uhci_set_device_address + dd uhci_get_device_address + dd uhci_port_disable + dd uhci_new_port.reset + dd uhci_set_endpoint_packet_size + dd usb1_allocate_endpoint + dd uhci_free_pipe + dd uhci_init_pipe + dd uhci_unlink_pipe + dd usb1_allocate_general_td + dd uhci_free_td + dd uhci_alloc_transfer + dd uhci_insert_transfer + dd uhci_new_device +endg + +; ============================================================================= +; =================================== Code ==================================== +; ============================================================================= + +; Controller-specific initialization function. +; Called from usb_init_controller. Initializes the hardware and +; UHCI-specific parts of software structures. +; eax = pointer to uhci_controller to be initialized +; [ebp-4] = pcidevice +proc uhci_init +; inherit some variables from the parent (usb_init_controller) +.devfn equ ebp - 4 +.bus equ ebp - 3 +; 1. Store pointer to uhci_controller for further use. + push eax + mov edi, eax + mov esi, eax +; 2. Initialize uhci_controller.FrameList. +; Note that FrameList is located in the beginning of uhci_controller, +; so esi and edi now point to uhci_controller.FrameList. +; First 32 entries of FrameList contain physical addresses +; of first 32 Periodic static heads, further entries duplicate these. +; See the description of structures for full info. +; Note that all static heads fit in one page, so one call to +; get_phys_addr is sufficient. +if (uhci_controller.IntEDs / 0x1000) <> (uhci_controller.BulkED / 0x1000) +.err assertion failed +end if +; 2a. Get physical address of first static head. +; Note that 1) it is located in the beginning of a page +; and 2) all other static heads fit in the same page, +; so one call to get_phys_addr without correction of lower 12 bits +; is sufficient. +if (uhci_controller.IntEDs mod 0x1000) <> 0 +.err assertion failed +end if + add eax, uhci_controller.IntEDs + call get_phys_addr +; 2b. Fill first 32 entries. + inc eax + inc eax ; set QH bit for uhci_pipe.NextQH + push 32 + pop ecx + mov edx, ecx +@@: + stosd + add eax, sizeof.uhci_static_ep + loop @b +; 2c. Fill the rest entries. + mov ecx, 1024 - 32 + rep movsd +; 3. Initialize static heads uhci_controller.*ED. +; Use the loop over groups: first group consists of first 32 Periodic +; descriptors, next group consists of next 16 Periodic descriptors, +; ..., last group consists of the last Periodic descriptor. +; 3a. Prepare for the loop. +; make esi point to the second group, other registers are already set. + add esi, 32*4 + 32*sizeof.uhci_static_ep +; 3b. Loop over groups. On every iteration: +; edx = size of group, edi = pointer to the current group, +; esi = pointer to the next group, eax = physical address of the next group. +.init_static_eds: +; 3c. Get the size of next group. + shr edx, 1 +; 3d. Exit the loop if there is no next group. + jz .init_static_eds_done +; 3e. Initialize the first half of the current group. +; Advance edi to the second half. + push eax esi + call uhci_init_static_ep_group + pop esi eax +; 3f. Initialize the second half of the current group +; with the same values. +; Advance edi to the next group, esi/eax to the next of the next group. + call uhci_init_static_ep_group + jmp .init_static_eds +.init_static_eds_done: +; 3g. Initialize the last static head. + xor esi, esi + call uhci_init_static_endpoint +; 3i. Initialize the head of Control list. + add eax, sizeof.uhci_static_ep + call uhci_init_static_endpoint +; 3j. Initialize the head of Bulk list. + sub eax, sizeof.uhci_static_ep + call uhci_init_static_endpoint +; 4. Get I/O base address and size from PCI bus. +; 4a. Read&save PCI command state. + mov bh, [.devfn] + mov ch, [.bus] + mov cl, 1 + mov eax, ecx + mov bl, 4 + call pci_read_reg + push eax +; 4b. Disable IO access. + and al, not 1 + push ecx + xchg eax, ecx + call pci_write_reg + pop ecx +; 4c. Read&save IO base address. + mov eax, ecx + mov bl, 20h + call pci_read_reg + and al, not 3 + xchg eax, edi +; now edi = IO base +; 4d. Write 0xffff to IO base address. + push ecx + xchg eax, ecx + or ecx, -1 + call pci_write_reg + pop ecx +; 4e. Read IO base address. + mov eax, ecx + call pci_read_reg + and al, not 3 + cwde + not eax + inc eax + xchg eax, esi +; now esi = IO size +; 4f. Restore IO base address. + xchg eax, ecx + mov ecx, edi + push eax + call pci_write_reg + pop eax +; 4g. Restore PCI command state and enable io & bus master access. + pop ecx + or ecx, 5 + mov bl, 4 + push eax + call pci_write_reg + pop eax +; 5. Reset the controller. +; 5e. Host reset. + mov edx, edi + mov ax, 2 + out dx, ax +; 5f. Wait up to 10ms. + push 10 + pop ecx +@@: + push esi + push 1 + pop esi + call delay_ms + pop esi + in ax, dx + test al, 2 + loopnz @b + jz @f + dbgstr 'UHCI controller reset timeout' + jmp .fail +@@: +if 0 +; emergency variant for tests - always wait 10 ms +; wait 10 ms + push esi + push 10 + pop esi + call delay_ms + pop esi +; clear reset signal + xor eax, eax + out dx, ax +end if +.resetok: +; 6. Get number of ports & disable all ports. + add esi, edi + lea edx, [edi+UhciPort1StatusReg] +.scanports: + cmp edx, esi + jae .doneports + in ax, dx + cmp ax, 0xFFFF + jz .doneports + test al, al + jns .doneports + xor eax, eax + out dx, ax + inc edx + inc edx + jmp .scanports +.doneports: + lea esi, [edx-UhciPort1StatusReg] + sub esi, edi + shr esi, 1 ; esi = number of ports + jnz @f + dbgstr 'error: no ports on UHCI controller' + jmp .fail +@@: +; 7. Setup the rest of uhci_controller. + xchg esi, [esp] ; restore the pointer to uhci_controller from the step 1 + add esi, sizeof.uhci_controller + pop [esi+usb_controller.NumPorts] + DEBUGF 1,'K : UHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] + mov [esi+uhci_controller.IOBase-sizeof.uhci_controller], edi + mov eax, [timer_ticks] + mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax +; 8. Hook interrupt. + mov ah, [.bus] + mov al, 0 + mov bh, [.devfn] + mov bl, 3Ch + call pci_read_reg +; al = IRQ +; DEBUGF 1,'K : UHCI %x: io=%x, irq=%x\n',esi,edi,al + movzx eax, al + stdcall attach_int_handler, eax, uhci_irq, esi +; 9. Setup controller registers. + xor eax, eax + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] +; 9a. UhciStatusReg := 3Fh: clear all status bits +; (for this register 1 clears the corresponding bit, 0 does not change it). + inc edx + inc edx ; UhciStatusReg == 2 + mov al, 3Fh + out dx, ax +; 9b. UhciInterruptReg := 0Dh. + inc edx + inc edx ; UhciInterruptReg == 4 + mov al, 0Dh + out dx, ax +; 9c. UhciFrameNumberReg := 0. + inc edx + inc edx ; UhciFrameNumberReg == 6 + mov al, 0 + out dx, ax +; 9d. UhciBaseAddressReg := physical address of uhci_controller. + inc edx + inc edx ; UhciBaseAddressReg == 8 + lea eax, [esi-sizeof.uhci_controller] + call get_phys_addr + out dx, eax +; 9e. UhciCommandReg := Run + Configured + (MaxPacket is 64 bytes) + sub edx, UhciBaseAddressReg ; UhciCommandReg == 0 + mov ax, 0C1h ; Run, Configured, MaxPacket = 64b + out dx, ax +; 10. Do initial scan of existing devices. + call uhci_poll_roothub +; 11. Return pointer to usb_controller. + xchg eax, esi + ret +.fail: +; On error, pop the pointer saved at step 1 and return zero. +; Note that the main code branch restores the stack at step 7 and never fails +; after step 7. + pop ecx + xor eax, eax + ret +endp + +; Controller-specific pre-initialization function: take ownership from BIOS. +; UHCI has no mechanism to ask the owner politely to release ownership, +; so do it in inpolite way, preventing controller from any SMI activity. +proc uhci_kickoff_bios +; 1. Get the I/O address. + mov ah, [esi+PCIDEV.bus] + mov al, 1 + mov bh, [esi+PCIDEV.devfn] + mov bl, 20h + call pci_read_reg + and eax, 0xFFFC + xchg eax, edx +; 2. Stop the controller and disable all interrupts. + in ax, dx + and al, not 1 + out dx, ax + add edx, UhciInterruptReg + xor eax, eax + out dx, ax +; 3. Disable all bits for SMI routing, clear SMI routing status, +; enable master interrupt bit. + mov ah, [esi+PCIDEV.bus] + mov al, 1 + mov bl, 0xC0 + mov ecx, 0AF00h + call pci_write_reg + ret +endp + +; Helper procedure for step 3 of uhci_init. +; Initializes the static head of one list. +; eax = physical address of the "next" list, esi = pointer to the "next" list, +; edi = pointer to head to initialize. +; Advances edi to the next head, keeps eax/esi. +proc uhci_init_static_endpoint + mov [edi+uhci_static_ep.NextQH], eax + mov byte [edi+uhci_static_ep.HeadTD], 1 + mov [edi+uhci_static_ep.NextList], esi + add edi, uhci_static_ep.SoftwarePart + call usb_init_static_endpoint + add edi, sizeof.uhci_static_ep - uhci_static_ep.SoftwarePart + ret +endp + +; Helper procedure for step 3 of uhci_init, see comments there. +; Initializes one half of group of static heads. +; edx = size of the next group = half of size of the group, +; edi = pointer to the group, eax = physical address of the next group, +; esi = pointer to the next group. +; Advances eax, esi, edi to next group, keeps edx. +proc uhci_init_static_ep_group + push edx +@@: + call uhci_init_static_endpoint + add eax, sizeof.uhci_static_ep + add esi, sizeof.uhci_static_ep + dec edx + jnz @b + pop edx + ret +endp + +; IRQ handler for UHCI controllers. +uhci_irq.noint: +; Not our interrupt: restore esi and return zero. + pop esi + xor eax, eax + ret +proc uhci_irq + push esi ; save used register to be cdecl +virtual at esp + dd ? ; saved esi + dd ? ; return address +.controller dd ? +end virtual + mov esi, [.controller] +; 1. Read UhciStatusReg. + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + inc edx + inc edx ; UhciStatusReg == 2 + in ax, dx +; 2. Test whether it is our interrupt; if so, at least one status bit is set. + test al, 0x1F + jz .noint +; 3. Clear all status bits. + out dx, ax +; 4. Sanity check. + test al, 0x3C + jz @f + DEBUGF 1,'K : something terrible happened with UHCI (%x)\n',al +@@: +; 5. We can't do too much from an interrupt handler, e.g. we can't take +; any mutex locks since our code could be called when another code holds the +; lock and has no chance to release it. Thus, only inform the processing thread +; that it should scan the queue and wake it if needed. + lock or byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], al + push ebx + xor ebx, ebx + inc ebx + call usb_wakeup_if_needed + pop ebx +; 6. This is our interrupt; return 1. + mov al, 1 + pop esi ; restore used register to be stdcall + ret +endp + +; This procedure is called in the USB thread from usb_thread_proc, +; processes regular actions and those actions which can't be safely done +; from interrupt handler. +; Returns maximal time delta before the next call. +proc uhci_process_deferred + push ebx edi ; save used registers to be stdcall +; 1. Initialize the return value. + push -1 +; 2. Poll the root hub every UHCI_POLL_INTERVAL ticks. +; Also force polling if some transaction has completed with errors; +; the error can be caused by disconnect, try to detect it. + test byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], 2 + jnz .force_poll + mov eax, [timer_ticks] + sub eax, [esi+uhci_controller.LastPollTime-sizeof.uhci_controller] + sub eax, UHCI_POLL_INTERVAL + jl .nopoll +.force_poll: + mov eax, [timer_ticks] + mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax + call uhci_poll_roothub + mov eax, -UHCI_POLL_INTERVAL +.nopoll: + neg eax + cmp [esp], eax + jb @f + mov [esp], eax +@@: +; 3. Process wait lists. +; 3a. Test whether there is a wait request. + mov eax, [esi+usb_controller.WaitPipeRequestAsync] + cmp eax, [esi+usb_controller.ReadyPipeHeadAsync] + jnz .check_removed + mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] + cmp eax, [esi+usb_controller.ReadyPipeHeadPeriodic] + jz @f +.check_removed: +; 3b. Yep. Find frame and compare it with the saved one. + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + add edx, UhciFrameNumberReg + in ax, dx + cmp word [esi+usb_controller.StartWaitFrame], ax + jnz .removed +; 3c. The same frame; wake up in 0.01 sec. + mov dword [esp], 1 + jmp @f +.removed: +; 3d. The frame is changed, old contents is guaranteed to be forgotten. + mov eax, [esi+usb_controller.WaitPipeRequestAsync] + mov [esi+usb_controller.ReadyPipeHeadAsync], eax + mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] + mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax +@@: +; 4. Process disconnect events. This should be done after step 2 +; (which includes the first stage of disconnect processing). + call usb_disconnect_stage2 +; 5. Test whether USB_CONNECT_DELAY for a connected device is over. +; Call uhci_new_port for all such devices. + xor ecx, ecx + cmp [esi+usb_controller.NewConnected], ecx + jz .skip_newconnected +.portloop: + bt [esi+usb_controller.NewConnected], ecx + jnc .noconnect + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ConnectedTime+ecx*4] + sub eax, USB_CONNECT_DELAY + jge .connected + neg eax + cmp [esp], eax + jb .nextport + mov [esp], eax + jmp .nextport +.connected: + btr [esi+usb_controller.NewConnected], ecx + call uhci_new_port +.noconnect: +.nextport: + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .portloop +.skip_newconnected: +; 6. Test for processed packets. +; This should be done after step 4, so transfers which were failed due +; to disconnect are marked with the exact reason, not just +; 'device not responding'. + xor eax, eax + xchg byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], al + test al, 3 + jz .noioc + call uhci_process_updated_schedule +.noioc: +; 7. Test whether reset signalling has been started. If so, +; either should be stopped now (if time is over) or schedule wakeup (otherwise). +; This should be done after step 6, because a completed SET_ADDRESS command +; could result in reset of a new port. +.test_reset: +; 7a. Test whether reset signalling is active. + cmp [esi+usb_controller.ResettingStatus], 1 + jnz .no_reset_in_progress +; 7b. Yep. Test whether it should be stopped. + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ResetTime] + sub eax, USB_RESET_TIME + jge .reset_done +; 7c. Not yet, but initiate wakeup in -eax ticks and exit this step. + neg eax + cmp [esp], eax + jb .skip_reset + mov [esp], eax + jmp .skip_reset +.reset_done: +; 7d. Yep, call the worker function and proceed to 7e. + call uhci_port_reset_done +.no_reset_in_progress: +; 7e. Test whether reset process is done, either successful or failed. + cmp [esi+usb_controller.ResettingStatus], 0 + jz .skip_reset +; 7f. Yep. Test whether it should be stopped. + mov eax, [timer_ticks] + sub eax, [esi+usb_controller.ResetTime] + sub eax, USB_RESET_RECOVERY_TIME + jge .reset_recovery_done +; 7g. Not yet, but initiate wakeup in -eax ticks and exit this step. + neg eax + cmp [esp], eax + jb .skip_reset + mov [esp], eax + jmp .skip_reset +.reset_recovery_done: +; 7h. Yep, call the worker function. This could initiate another reset, +; so return to the beginning of this step. + call uhci_port_init + jmp .test_reset +.skip_reset: +; 8. Process wait-done notifications, test for new wait requests. +; Note: that must be done after steps 4 and 6 which could create new requests. +; 8a. Call the worker function. + call usb_process_wait_lists +; 8b. If no new requests, skip the rest of this step. + test eax, eax + jz @f +; 8c. UHCI is not allowed to cache anything; we don't know what is +; processed right now, but we can be sure that the controller will not +; use any removed structure starting from the next frame. +; Request removal of everything disconnected until now, +; schedule wakeup in 0.01 sec. + mov eax, [esi+usb_controller.WaitPipeListAsync] + mov [esi+usb_controller.WaitPipeRequestAsync], eax + mov eax, [esi+usb_controller.WaitPipeListPeriodic] + mov [esi+usb_controller.WaitPipeRequestPeriodic], eax + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + add edx, UhciFrameNumberReg + in ax, dx + mov word [esi+usb_controller.StartWaitFrame], ax + mov dword [esp], 1 +@@: +; 9. Return the value from the top of stack. + pop eax + pop edi ebx ; restore used registers to be stdcall. + ret +endp + +; This procedure is called in the USB thread from uhci_process_deferred +; when UHCI IRQ handler has signalled that new IOC-packet was processed. +; It scans all lists for completed packets and calls uhci_process_finalized_td +; for those packets. +; in: esi -> usb_controller +proc uhci_process_updated_schedule +; Important note: we cannot hold the list lock during callbacks, +; because callbacks sometimes open and/or close pipes and thus acquire/release +; the corresponding lock itself. +; Fortunately, pipes can be finally freed only by another step of +; uhci_process_deferred, so all pipes existing at the start of this function +; will be valid while this function is running. Some pipes can be removed +; from the corresponding list, some pipes can be inserted; insert/remove +; functions guarantee that traversing one list yields all pipes that were in +; that list at the beginning of the traversing (possibly with some new pipes, +; possibly without some new pipes, that doesn't matter). +; 1. Process all Periodic lists. + lea edi, [esi+uhci_controller.IntEDs.SoftwarePart-sizeof.uhci_controller] + lea ebx, [esi+uhci_controller.IntEDs.SoftwarePart+63*sizeof.uhci_static_ep-sizeof.uhci_controller] +@@: + call uhci_process_updated_list + cmp edi, ebx + jnz @b +; 2. Process the Control list. + call uhci_process_updated_list +; 3. Process the Bulk list. + call uhci_process_updated_list +; 4. Return. + ret +endp + +; This procedure is called from uhci_process_updated_schedule, +; see comments there. +; It processes one list, esi -> usb_controller, edi -> usb_static_ep, +; and advances edi to the next head. +proc uhci_process_updated_list + push ebx ; save used register to be stdcall +; 1. Perform the external loop over all pipes. + mov ebx, [edi+usb_static_ep.NextVirt] +.loop: + cmp ebx, edi + jz .done +; store pointer to the next pipe in the stack + push [ebx+usb_static_ep.NextVirt] +; 2. For every pipe, perform the internal loop over all descriptors. +; All descriptors are organized in the queue; we process items from the start +; of the queue until a) the last descriptor (not the part of the queue itself) +; or b) an active (not yet processed by the hardware) descriptor is reached. + lea ecx, [ebx+usb_pipe.Lock] + call mutex_lock + mov ebx, [ebx+usb_pipe.LastTD] + push ebx + mov ebx, [ebx+usb_gtd.NextVirt] +.tdloop: +; 3. For every descriptor, test active flag and check for end-of-queue; +; if either of conditions holds, exit from the internal loop. + cmp ebx, [esp] + jz .tddone + mov eax, [ebx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart] + test eax, 1 shl 23 ; active? + jnz .tddone +; Release the queue lock while processing one descriptor: +; callback function could (and often would) schedule another transfer. + push ecx + call mutex_unlock + call uhci_process_finalized_td + pop ecx + call mutex_lock + jmp .tdloop +.tddone: + call mutex_unlock + pop ebx +; End of internal loop, restore pointer to the next pipe +; and continue the external loop. + pop ebx + jmp .loop +.done: + pop ebx ; restore used register to be stdcall + add edi, sizeof.uhci_static_ep + ret +endp + +; This procedure is called from uhci_process_updated_list, which is itself +; called from uhci_process_updated_schedule, see comments there. +; It processes one completed descriptor. +; in: esi -> usb_controller, ebx -> usb_gtd, out: ebx -> next usb_gtd. +proc uhci_process_finalized_td +; 1. Remove this descriptor from the list of descriptors for this pipe. + call usb_unlink_td +; DEBUGF 1,'K : finalized TD:\n' +; DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8] +; DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8] +; 2. If this is IN transfer into special buffer, copy the data +; to target location. + mov edx, [ebx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] + and edx, not 1 ; clear lsb (used for another goal) + jz .nocopy + cmp byte [ebx+uhci_gtd.Token-uhci_gtd.SoftwarePart], USB_PID_IN + jnz .nocopy +; Note: we assume that pointer to buffer is valid in the memory space of +; the USB thread. This means that buffer must reside in kernel memory +; (shared by all processes). + push esi edi + mov esi, [edx+uhci_original_buffer.UsedBuffer] + mov edi, [edx+uhci_original_buffer.OrigBuffer] + mov ecx, [ebx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart] + inc ecx + and ecx, 7FFh + mov edx, ecx + shr ecx, 2 + and edx, 3 + rep movsd + mov ecx, edx + rep movsb + pop edi esi +.nocopy: +; 3. Calculate actual number of bytes transferred. +; 3a. Read the state. + mov eax, [ebx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart] + mov ecx, [ebx+uhci_gtd.Token-uhci_gtd.SoftwarePart] +; 3b. Get number of bytes processed. + lea edx, [eax+1] + and edx, 7FFh +; 3c. Subtract number of bytes in this packet. + add ecx, 1 shl 21 + shr ecx, 21 + sub edx, ecx +; 3d. Add total length transferred so far. + add edx, [ebx+usb_gtd.Length] +; Actions on error and on success are slightly different. +; 4. Test for error. On error, proceed to step 5, otherwise go to step 6 +; with ecx = 0 (no error). +; USB transaction error is always considered as such. +; If short packets are not allowed, UHCI controllers do not set an error bit, +; but stop (clear Active bit and do not advance) the queue. +; Short packet is considered as an error if the packet is actually short +; (actual length is less than maximal one) and the code creating the packet +; requested that behaviour (so bit 0 of OrigBufferInfo is set; this could be +; because the caller disallowed short packets or because the packet is not +; the last one in the corresponding transfer). + xor ecx, ecx + test eax, 1 shl 22 + jnz .error + test byte [ebx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], 1 + jz .notify + cmp edx, [ebx+usb_gtd.Length] + jz .notify +.error: +; 5. There was an error while processing this packet. +; The hardware has stopped processing the queue. + DEBUGF 1,'K : TD failed:\n' +if uhci_gtd.SoftwarePart <> 20 +.err modify offsets for debug output +end if + DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8] + DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8] +; 5a. Save the status and length. + push edx + push eax + mov eax, [ebx+usb_gtd.Pipe] + DEBUGF 1,'K : pipe: %x %x\n',[eax+0-uhci_pipe.SoftwarePart],[eax+4-uhci_pipe.SoftwarePart] +; 5b. Store the current TD as an error packet. +; If an error packet is already stored for this pipe, +; it is definitely not used already, so free the old packet. + mov eax, [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] + test eax, eax + jz @f + stdcall uhci_free_td, eax +@@: + mov eax, [ebx+usb_gtd.Pipe] + mov [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart], ebx +; 5c. Traverse the list of descriptors looking for the final packet +; for this transfer. +; Free and unlink non-final descriptors, except the current one. +; Final descriptor will be freed in step 7. + call usb_is_final_packet + jnc .found_final + mov ebx, [ebx+usb_gtd.NextVirt] +.look_final: + call usb_unlink_td + call usb_is_final_packet + jnc .found_final + push [ebx+usb_gtd.NextVirt] + stdcall uhci_free_td, ebx + pop ebx + jmp .look_final +.found_final: +; 5d. Restore the status saved in 5a and transform it to the error code. + pop eax ; error code + shr eax, 16 +; Notes: +; * any USB transaction error results in Stalled bit; if it is not set, +; but we are here, it must be due to short packet; +; * babble is considered a fatal USB transaction error, +; other errors just lead to retrying the transaction; +; if babble is detected, return the corresponding error; +; * if several non-fatal errors have occured during transaction retries, +; all corresponding bits are set. In this case, return some error code, +; the order is quite arbitrary. + push USB_STATUS_UNDERRUN + pop ecx + test al, 1 shl (22-16) ; not Stalled? + jz .know_error + mov cl, USB_STATUS_OVERRUN + test al, 1 shl (20-16) ; Babble detected? + jnz .know_error + mov cl, USB_STATUS_BITSTUFF + test al, 1 shl (17-16) ; Bitstuff error? + jnz .know_error + mov cl, USB_STATUS_NORESPONSE + test al, 1 shl (18-16) ; CRC/TimeOut error? + jnz .know_error + mov cl, USB_STATUS_BUFOVERRUN + test al, 1 shl (21-16) ; Data Buffer error? + jnz .know_error + mov cl, USB_STATUS_STALL +.know_error: +; 5e. If error code is USB_STATUS_UNDERRUN +; and the last TD allows short packets, it is not an error. +; Note: all TDs except the last one in any transfer stage are marked +; as short-packet-is-error to stop controller from further processing +; of that stage; we need to restart processing from a TD following the last. +; After that, go to step 6 with ecx = 0 (no error). + cmp ecx, USB_STATUS_UNDERRUN + jnz @f + test byte [ebx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], 1 + jnz @f +; The controller has stopped this queue on the error packet. +; Update uhci_pipe.HeadTD to point to the next packet in the queue. + call uhci_fix_toggle + xor ecx, ecx +.control: + mov eax, [ebx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart] + and al, not 0xF + mov edx, [ebx+usb_gtd.Pipe] + mov [edx+uhci_pipe.HeadTD-uhci_pipe.SoftwarePart], eax + pop edx ; length + jmp .notify +@@: +; 5f. Abort the entire transfer. +; There are two cases: either there is only one transfer stage +; (everything except control transfers), then ebx points to the last TD and +; all previous TD were unlinked and dismissed (if possible), +; or there are several stages (a control transfer) and ebx points to the last +; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, +; because Setup stage can not produce short packets); for Data stage, we need +; to unlink and free (if possible) one more TD and advance ebx to the next one. + cmp [ebx+usb_gtd.Callback], 0 + jnz .normal +; We cannot free ErrorTD yet, it could still be used by the hardware. + push ecx + mov eax, [ebx+usb_gtd.Pipe] + push [ebx+usb_gtd.NextVirt] + cmp ebx, [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] + jz @f + stdcall uhci_free_td, ebx +@@: + pop ebx + call usb_unlink_td + pop ecx +.normal: +; 5g. For bulk/interrupt transfers we have no choice but halt the queue, +; the driver should intercede (through some API which is not written yet). +; Control pipes normally recover at the next SETUP transaction (first stage +; of any control transfer), so we hope on the best and just advance the queue +; to the next transfer. (According to the standard, "A control pipe may also +; support functional stall as well, but this is not recommended."). + mov edx, [ebx+usb_gtd.Pipe] + cmp [edx+usb_pipe.Type], CONTROL_PIPE + jz .control +; Bulk/interrupt transfer; halt the queue. + mov eax, [ebx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart] + and al, not 0xF + inc eax ; set Halted bit + mov [edx+uhci_pipe.HeadTD-uhci_pipe.SoftwarePart], eax + pop edx ; restore length saved in step 5a +.notify: +; 6. Either the descriptor in ebx was processed without errors, +; or all necessary error actions were taken and ebx points to the last +; related descriptor. +; 6a. Test whether it is the last packet in the transfer +; <=> it has an associated callback. + mov eax, [ebx+usb_gtd.Callback] + test eax, eax + jz .nocallback +; 6b. It has an associated callback; call it with corresponding parameters. + stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \ + [ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] + jmp .callback +.nocallback: +; 6c. It is an intermediate packet. Add its length to the length +; in the following packet. + mov eax, [ebx+usb_gtd.NextVirt] + add [eax+usb_gtd.Length], edx +.callback: +; 7. Free the current descriptor (if allowed) and return the next one. +; 7a. Save pointer to the next descriptor. + push [ebx+usb_gtd.NextVirt] +; 7b. Free the descriptor, unless it is saved as ErrorTD. + mov eax, [ebx+usb_gtd.Pipe] + cmp [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart], ebx + jz @f + stdcall uhci_free_td, ebx +@@: +; 7c. Restore pointer to the next descriptor and return. + pop ebx + ret +endp + +; Helper procedure for restarting transfer queue. +; When transfers are queued, their toggle bit is filled assuming that +; everything will go without errors. On error, some packets needs to be +; skipped, so toggle bits may become incorrect. +; This procedure fixes toggle bits. +; in: ebx -> last packet to be skipped, ErrorTD -> last processed packet +proc uhci_fix_toggle +; 1. Nothing to do for control pipes: in that case, +; toggle bits for different transfer stages are independent. + mov ecx, [ebx+usb_gtd.Pipe] + cmp [ecx+usb_pipe.Type], CONTROL_PIPE + jz .nothing +; 2. The hardware expects next packet with toggle = (ErrorTD.toggle xor 1), +; the current value in next packet is (ebx.toggle xor 1). +; Nothing to do if ErrorTD.toggle == ebx.toggle. + mov eax, [ecx+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] + mov eax, [eax+uhci_gtd.Token-uhci_gtd.SoftwarePart] + xor eax, [ebx+uhci_gtd.Token-uhci_gtd.SoftwarePart] + test eax, 1 shl 19 + jz .nothing +; 3. Lock the transfer queue. + add ecx, usb_pipe.Lock + call mutex_lock +; 4. Flip the toggle bit in all packets from ebx.NextVirt to ecx.LastTD +; (inclusive). + mov eax, [ebx+usb_gtd.NextVirt] +.loop: + xor byte [eax+uhci_gtd.Token-uhci_gtd.SoftwarePart+2], 1 shl (19-16) + cmp eax, [ecx+usb_pipe.LastTD-usb_pipe.Lock] + mov eax, [eax+usb_gtd.NextVirt] + jnz .loop +; 5. Flip the toggle bit in uhci_pipe structure. + xor byte [ecx+uhci_pipe.Token-uhci_pipe.SoftwarePart-usb_pipe.Lock+2], 1 shl (19-16) + or dword [ecx+uhci_pipe.Token-uhci_pipe.SoftwarePart-usb_pipe.Lock], eax +; 6. Unlock the transfer queue. + call mutex_unlock +.nothing: + ret +endp + +; This procedure is called in the USB thread from uhci_process_deferred +; every UHCI_POLL_INTERVAL ticks. It polls the controller for +; connect/disconnect events. +; in: esi -> usb_controller +proc uhci_poll_roothub + push ebx ; save used register to be stdcall +; 1. Prepare for the loop for every port. + xor ecx, ecx +.portloop: +; 2. Some implementations of UHCI set ConnectStatusChange bit in a response to +; PortReset. Thus, we must ignore this change for port which is resetting. + cmp cl, [esi+usb_controller.ResettingPort] + jz .nextport +; 3. Read port status. + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + lea edx, [edx+ecx*2+UhciPort1StatusReg] + in ax, dx +; 4. If no change bits are set, continue to the next port. + test al, 0Ah + jz .nextport +; 5. Clear change bits and read the status again. +; (It is possible, although quite unlikely, that some event occurs between +; the first read and the clearing, invalidating the old status. If an event +; occurs after the clearing, we will not miss it, looking in the next scan. + out dx, ax + mov ebx, eax + in ax, dx +; 6. Process connect change notifications. +; Note: if connect status has changed, ignore enable status change; +; it is normal to disable a port at disconnect event. +; Some controllers set enable status change bit, some don't. + test bl, 2 + jz .noconnectchange + DEBUGF 1,'K : [%d] UHCI %x connect status changed, %x/%x\n',[timer_ticks],esi,bx,ax +; yep. Regardless of the current status, note disconnect event; +; if there is something connected, store the connect time and note connect event. +; In any way, do not process + bts [esi+usb_controller.NewDisconnected], ecx + test al, 1 + jz .disconnect + mov eax, [timer_ticks] + mov [esi+usb_controller.ConnectedTime+ecx*4], eax + bts [esi+usb_controller.NewConnected], ecx + jmp .nextport +.disconnect: + btr [esi+usb_controller.NewConnected], ecx + jmp .nextport +.noconnectchange: +; 7. Process enable change notifications. +; Note: that needs work. + test bl, 8 + jz .nextport + test al, 4 + jnz .nextport + dbgstr 'Port disabled' +.nextport: +; 8. Continue the loop for every port. + inc ecx + cmp ecx, [esi+usb_controller.NumPorts] + jb .portloop + pop ebx ; restore used register to be stdcall + ret +endp + +; This procedure is called from uhci_process_deferred when +; a new device was connected at least USB_CONNECT_DELAY ticks +; and therefore is ready to be configured. +; in: esi -> usb_controller, ecx = port (zero-based) +proc uhci_new_port +; test whether we are configuring another port +; if so, postpone configuring and return + bts [esi+usb_controller.PendingPorts], ecx + cmp [esi+usb_controller.ResettingPort], -1 + jnz .nothing + btr [esi+usb_controller.PendingPorts], ecx +; fall through to uhci_new_port.reset + +; This function is called from uhci_new_port and uhci_test_pending_port. +; It starts reset signalling for the port. Note that in USB first stages +; of configuration can not be done for several ports in parallel. +.reset: +; 1. Store information about resetting hub (roothub) and port. + and [esi+usb_controller.ResettingHub], 0 + mov [esi+usb_controller.ResettingPort], cl +; 2. Initiate reset signalling. + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + lea edx, [edx+ecx*2+UhciPort1StatusReg] + in ax, dx + or ah, 2 + out dx, ax +; 3. Store the current time and set status to 1 = reset signalling active. + mov eax, [timer_ticks] + mov [esi+usb_controller.ResetTime], eax + mov [esi+usb_controller.ResettingStatus], 1 +.nothing: + ret +endp + +; This procedure is called from uhci_process_deferred when +; reset signalling for a port needs to be finished. +proc uhci_port_reset_done +; 1. Stop reset signalling. + movzx ecx, [esi+usb_controller.ResettingPort] + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + lea edx, [edx+ecx*2+UhciPort1StatusReg] + in ax, dx + DEBUGF 1,'K : [%d] UHCI %x status %x/',[timer_ticks],esi,ax + and ah, not 2 + out dx, ax +; 2. Status bits in UHCI are invalid during reset signalling. +; Wait a millisecond while status bits become valid again. + push esi + push 1 + pop esi + call delay_ms + pop esi +; 3. ConnectStatus bit is zero during reset and becomes 1 during step 2; +; some controllers interpret this as a (fake) connect event. +; Enable port and clear status change notification. + in ax, dx + DEBUGF 1,'%x\n',ax + or al, 6 ; enable port, clear status change + out dx, ax +; 4. Store the current time and set status to 2 = reset recovery active. + mov eax, [timer_ticks] + DEBUGF 1,'K : reset done at %d\n',[timer_ticks] + mov [esi+usb_controller.ResetTime], eax + mov [esi+usb_controller.ResettingStatus], 2 + ret +endp + +; This procedure is called from uhci_process_deferred when +; a new device has been reset, recovered after reset and +; needs to be configured. +; in: esi -> usb_controller +proc uhci_port_init +; 1. Read port status. + mov [esi+usb_controller.ResettingStatus], 0 + movzx ecx, [esi+usb_controller.ResettingPort] + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + lea edx, [edx+ecx*2+UhciPort1StatusReg] + in ax, dx + DEBUGF 1,'K : [%d] UHCI %x status %x\n',[timer_ticks],esi,ax +; 2. If the device has been disconnected, stop the initialization. + test al, 1 + jnz @f + dbgstr 'USB port disabled after reset' + jmp usb_test_pending_port +@@: +; 3. Copy LowSpeed bit to bit 0 of eax and call the worker procedure +; to notify the protocol layer about new UHCI device. + push edx + mov al, ah + call uhci_new_device + pop edx + test eax, eax + jnz .nothing +; 4. If something at the protocol layer has failed +; (no memory, no bus address), disable the port and stop the initialization. +.disable_exit: + in ax, dx + and al, not 4 + out dx, ax ; disable the port + jmp usb_test_pending_port +.nothing: + ret +endp + +; This procedure is called from uhci_port_init and from hub support code +; when a new device is connected and has been reset. +; It calls usb_new_device at the protocol layer with correct parameters. +; in: esi -> usb_controller, eax = speed; +; UHCI is USB1 device, so only low bit of eax (LowSpeed) is used. +proc uhci_new_device +; 1. Clear all bits of speed except bit 0. + and eax, 1 +; 2. Store the speed for the protocol layer. + mov [esi+usb_controller.ResettingSpeed], al +; 3. Create pseudo-pipe in the stack. +; See uhci_init_pipe: only .Controller and .Token fields are used. + push esi ; fill .Controller field + mov ecx, esp + shl eax, 20 ; bit 20 = LowSpeedDevice + push eax ; ignored (ErrorTD) + push eax ; .Token field: DeviceAddress is zero, bit 20 = LowSpeedDevice +; 4. Notify the protocol layer. + call usb_new_device +; 5. Cleanup the stack after step 3 and return. + add esp, 12 + ret +endp + +; This procedure is called from usb_set_address_callback +; and stores USB device address in the uhci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe, cl = address +proc uhci_set_device_address + mov byte [ebx+uhci_pipe.Token+1-uhci_pipe.SoftwarePart], cl + call usb_subscription_done + ret +endp + +; This procedure returns USB device address from the uhci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe +; out: eax = endpoint address +proc uhci_get_device_address + mov al, byte [ebx+uhci_pipe.Token+1-uhci_pipe.SoftwarePart] + and eax, 7Fh + ret +endp + +; This procedure is called from usb_set_address_callback +; if the device does not accept SET_ADDRESS command and needs +; to be disabled at the port level. +; in: esi -> usb_controller, ecx = port (zero-based) +proc uhci_port_disable + mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] + lea edx, [edx+UhciPort1StatusReg+ecx*2] + in ax, dx + and al, not 4 + out dx, ax + ret +endp + +; This procedure is called from usb_get_descr8_callback when +; the packet size for zero endpoint becomes known and +; stores the packet size in uhci_pipe structure. +; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size +proc uhci_set_endpoint_packet_size + dec ecx + shl ecx, 21 + and [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart], (1 shl 21) - 1 + or [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart], ecx +; uhci_pipe.Token field is purely for software bookkeeping and does not affect +; the hardware; thus, we can continue initialization immediately. + call usb_subscription_done + ret +endp + +; This procedure is called from API usb_open_pipe and processes +; the controller-specific part of this API. See docs. +; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, +; esi -> usb_controller, eax -> usb_gtd for the first TD, +; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type +proc uhci_init_pipe +; inherit some variables from the parent usb_open_pipe +virtual at ebp+8 +.config_pipe dd ? +.endpoint dd ? +.maxpacket dd ? +.type dd ? +.interval dd ? +end virtual +; 1. Initialize ErrorTD to zero. + and [edi+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart], 0 +; 2. Initialize HeadTD to the physical address of the first TD. + push eax ; store pointer to the first TD for step ? + sub eax, uhci_gtd.SoftwarePart + call get_phys_addr + mov [edi+uhci_pipe.HeadTD-uhci_pipe.SoftwarePart], eax +; 3. Initialize Token field: +; take DeviceAddress and LowSpeedDevice from the parent pipe, +; take Endpoint and MaximumLength fields from API arguments, +; set PID depending on pipe type and provided pipe direction, +; set DataToggle to zero. + mov eax, [ecx+uhci_pipe.Token-uhci_pipe.SoftwarePart] + and eax, 0x107F00 ; keep DeviceAddress and LowSpeedDevice + mov edx, [.endpoint] + and edx, 15 + shl edx, 15 + or eax, edx + mov edx, [.maxpacket] + dec edx + shl edx, 21 + or eax, edx + mov al, USB_PID_SETUP + cmp [.type], CONTROL_PIPE + jz @f + mov al, USB_PID_OUT + test byte [.endpoint], 80h + jz @f + mov al, USB_PID_IN +@@: + mov [edi+uhci_pipe.Token-uhci_pipe.SoftwarePart], eax +; 4. Initialize the first TD: +; copy Token from uhci_pipe.Token zeroing reserved bit 20, +; set ControlStatus for future transfers, bit make it inactive, +; set bit 0 in NextTD = "no next TD". + pop edx ; restore pointer saved in step 2 + mov [edx+uhci_gtd.Token-uhci_gtd.SoftwarePart], eax + and byte [edx+uhci_gtd.Token+2-uhci_gtd.SoftwarePart], not (1 shl (20-16)) + and eax, 1 shl 20 + shl eax, 6 + or eax, UHCI_INVALID_LENGTH + (3 shl 27) + ; not processed, inactive, allow 3 errors + mov [edx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart], eax + mov [edx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], 1 +; 5. Select the corresponding list and insert to the list. +; 5a. Use Control list for control pipes, Bulk list for bulk pipes. + lea edx, [esi+uhci_controller.ControlED.SoftwarePart-sizeof.uhci_controller] + cmp [.type], BULK_PIPE + jb .insert ; control pipe + lea edx, [esi+uhci_controller.BulkED.SoftwarePart-sizeof.uhci_controller] + jz .insert ; bulk pipe +.interrupt_pipe: +; 5b. For interrupt pipes, let the scheduler select the appropriate list +; based on the current bandwidth distribution and the requested bandwidth. +; This could fail if the requested bandwidth is not available; +; if so, return an error. + lea edx, [esi + uhci_controller.IntEDs - sizeof.uhci_controller] + lea eax, [esi + uhci_controller.IntEDs + 32*sizeof.uhci_static_ep - sizeof.uhci_controller] + push 64 + pop ecx + call usb1_select_interrupt_list + test edx, edx + jz .return0 +.insert: +; Insert to the head of the corresponding list. +; Note: inserting to the head guarantees that the list traverse in +; uhci_process_updated_schedule, once started, will not interact with new pipes. +; However, we still need to ensure that links in the new pipe (edi.NextVirt) +; are initialized before links to the new pipe (edx.NextVirt). +; 5c. Insert in the list of virtual addresses. + mov ecx, [edx+usb_pipe.NextVirt] + mov [edi+usb_pipe.NextVirt], ecx + mov [edi+usb_pipe.PrevVirt], edx + mov [ecx+usb_pipe.PrevVirt], edi + mov [edx+usb_pipe.NextVirt], edi +; 5d. Insert in the hardware list: copy previous NextQH to the new pipe, +; store the physical address of the new pipe to previous NextQH. + mov ecx, [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart] + mov [edi+uhci_pipe.NextQH-uhci_pipe.SoftwarePart], ecx + lea eax, [edi-uhci_pipe.SoftwarePart] + call get_phys_addr + inc eax + inc eax + mov [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart], eax +; 6. Return with nonzero eax. + ret +.return0: + xor eax, eax + ret +endp + +; This procedure is called when a pipe is closing (either due to API call +; or due to disconnect); it unlinks a pipe from the corresponding list. +if uhci_static_ep.SoftwarePart <> uhci_pipe.SoftwarePart +.err uhci_unlink_pipe assumes that uhci_static_ep.SoftwarePart == uhci_pipe.SoftwarePart +end if +proc uhci_unlink_pipe + cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE + jnz @f + mov eax, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] + cmp al, USB_PID_IN + setz ch + bt eax, 20 + setc cl + add eax, 1 shl 21 + shr eax, 21 + stdcall usb1_interrupt_list_unlink, eax, ecx +@@: +; Note: we need to ensure that NextVirt field of the pipe is not modified; +; this procedure can be called while uhci_process_updated_schedule processes +; the same pipe, and it needs a correct NextVirt field to continue. + mov edx, [ebx+usb_pipe.NextVirt] + mov eax, [ebx+usb_pipe.PrevVirt] + mov [edx+usb_pipe.PrevVirt], eax + mov [eax+usb_pipe.NextVirt], edx +; Note: eax could be either usb_pipe or usb_static_ep; +; fortunately, NextQH and SoftwarePart have same offsets in both. + mov edx, [ebx+uhci_pipe.NextQH-uhci_pipe.SoftwarePart] + mov [eax+uhci_pipe.NextQH-uhci_pipe.SoftwarePart], edx + ret +endp + +; Free memory associated with pipe. +; For UHCI, this includes usb_pipe structure and ErrorTD, if present. +proc uhci_free_pipe + mov eax, [esp+4] + mov eax, [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] + test eax, eax + jz @f + stdcall uhci_free_td, eax +@@: + jmp usb1_free_endpoint +endp + +; This procedure is called from the several places in main USB code +; and allocates required packets for the given transfer stage. +; ebx = pipe, other parameters are passed through the stack +proc uhci_alloc_transfer stdcall uses edi, buffer:dword, size:dword, flags:dword, td:dword, direction:dword +locals +token dd ? +origTD dd ? +packetSize dd ? ; must be the last variable, see usb_init_transfer +endl +; 1. [td] will be the first packet in the transfer. +; Save it to allow unrolling if something will fail. + mov eax, [td] + mov [origTD], eax +; In UHCI one TD describes one packet, transfers should be split into parts +; with size <= endpoint max packet size. +; 2. Get the maximum packet size for endpoint from uhci_pipe.Token +; and generate Token field for TDs. + mov edi, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] + mov eax, edi + shr edi, 21 + inc edi +; zero packet size (it will be set for every packet individually), +; zero reserved bit 20, + and eax, (1 shl 20) - 1 + mov [packetSize], edi +; set the correct PID if it is different from the pipe-wide PID +; (Data and Status stages of control transfers), + mov ecx, [direction] + and ecx, 3 + jz @f + mov al, USB_PID_OUT + dec ecx + jz @f + mov al, USB_PID_IN +@@: +; set the toggle bit for control transfers, + mov ecx, [direction] + test cl, 1 shl 3 + jz @f + and ecx, 1 shl 2 + and eax, not (1 shl 19) + shl ecx, 19-2 + or eax, ecx +@@: +; store the resulting Token in the stack variable. + mov [token], eax +; 3. While the remaining data cannot fit in one packet, +; allocate full packets (of maximal possible size). +.fullpackets: + cmp [size], edi + jbe .lastpacket + call uhci_alloc_packet + test eax, eax + jz .fail + mov [td], eax + add [buffer], edi + sub [size], edi + jmp .fullpackets +.lastpacket: +; 4. The remaining data can fit in one packet; +; allocate the last packet with size = size of remaining data. + mov eax, [size] + mov [packetSize], eax + call uhci_alloc_packet + test eax, eax + jz .fail +; 5. Clear 'short packets are not allowed' bit for the last packet, +; if the caller requested this. +; Note: even if the caller says that short transfers are ok, +; all packets except the last one are marked as 'must be complete': +; if one of them will be short, the software intervention is needed +; to skip remaining packets; uhci_process_finalized_td will handle this +; transparently to the caller. + test [flags], 1 + jz @f + and byte [ecx+uhci_gtd.ControlStatus+3-uhci_gtd.SoftwarePart], not (1 shl (29-24)) + and byte [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], not 1 +@@: +; 6. Update toggle bit in uhci_pipe structure from current value of [token]. + mov edx, [token] + xor edx, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] + and edx, 1 shl 19 + xor [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart], edx +.nothing: + ret +.fail: + mov edi, uhci_hardware_func + mov eax, [td] + stdcall usb_undo_tds, [origTD] + xor eax, eax + jmp .nothing +endp + +; Helper procedure for uhci_alloc_transfer. Allocates one packet. +proc uhci_alloc_packet +; inherit some variables from the parent uhci_alloc_transfer +virtual at ebp-12 +.token dd ? +.origTD dd ? +.packetSize dd ? + rd 2 +.buffer dd ? +.transferSize dd ? +.Flags dd ? +.td dd ? +.direction dd ? +end virtual +; 1. In UHCI all data for one packet must be on the same page. +; Thus, if the given buffer splits page boundary, we need a temporary buffer +; and code that transfers data between the given buffer and the temporary one. +; 1a. There is no buffer for zero-length packets. + xor eax, eax + cmp [.packetSize], eax + jz .notempbuf +; 1b. A temporary buffer is not required if the first and the last bytes +; of the given buffer are the same except lower 12 bits. + mov edx, [.buffer] + add edx, [.packetSize] + dec edx + xor edx, [.buffer] + test edx, -0x1000 + jz .notempbuf +; 1c. We need a temporary buffer. Allocate [packetSize]*2 bytes, so that +; there must be [packetSize] bytes on one page, +; plus space for a header uhci_original_buffer. + push ebx + mov eax, [.packetSize] + add eax, eax + add eax, sizeof.uhci_original_buffer + call malloc + pop ebx +; 1d. If failed, return zero. + test eax, eax + jz .nothing +; 1e. Test whether [.packetSize] bytes starting from +; eax + sizeof.uhci_original_buffer are in the same page. +; If so, use eax + sizeof.uhci_original_buffer as a temporary buffer. +; Otherwise, use the beginning of the next page as a temporary buffer +; (since we have overallocated, sufficient space must remain). + lea ecx, [eax+sizeof.uhci_original_buffer] + mov edx, ecx + add edx, [.packetSize] + dec edx + xor edx, ecx + test edx, -0x1000 + jz @f + mov ecx, eax + or ecx, 0xFFF + inc ecx +@@: + mov [eax+uhci_original_buffer.UsedBuffer], ecx + mov ecx, [.buffer] + mov [eax+uhci_original_buffer.OrigBuffer], ecx +; 1f. For SETUP and OUT packets, copy data from the given buffer +; to the temporary buffer now. For IN packets, data go in other direction +; when the transaction completes. + cmp byte [.token], USB_PID_IN + jz .nocopy + push esi edi + mov esi, ecx + mov edi, [eax+uhci_original_buffer.UsedBuffer] + mov ecx, [.packetSize] + mov edx, ecx + shr ecx, 2 + and edx, 3 + rep movsd + mov ecx, edx + rep movsb + pop edi esi +.nocopy: +.notempbuf: +; 2. Allocate the next TD. + push eax + call usb1_allocate_general_td + pop edx +; If failed, free the temporary buffer (if it was allocated) and return zero. + test eax, eax + jz .fail +; 3. Initialize controller-independent parts of both TDs. + push edx + call usb_init_transfer +; 4. Initialize the next TD: +; mark it as last one (this will be changed when further packets will be +; allocated), copy Token field from uhci_pipe.Token zeroing bit 20, +; generate ControlStatus field, mark as Active +; (for last descriptor, this will be changed by uhci_insert_transfer). + mov [eax+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], 1 ; no next TD + mov edx, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] + mov [eax+uhci_gtd.Token-uhci_gtd.SoftwarePart], edx + and byte [eax+uhci_gtd.Token+2-uhci_gtd.SoftwarePart], not (1 shl (20-16)) + and edx, 1 shl 20 + shl edx, 6 + or edx, UHCI_INVALID_LENGTH + (1 shl 23) + (3 shl 27) + ; not processed, active, allow 3 errors + mov [eax+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart], edx +; 5. Initialize remaining fields of the current TD. +; 5a. Store pointer to the buffer allocated in step 1 (or zero). + pop [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] +; 5b. Store physical address of the next TD. + push eax + sub eax, uhci_gtd.SoftwarePart + call get_phys_addr +; use Depth traversal unless this is the first TD in the transfer stage; +; uhci_insert_transfer will set Depth traversal for the first TD and clear +; it in the last TD + cmp ecx, [ebx+usb_pipe.LastTD] + jz @f + or eax, 4 +@@: + mov [ecx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], eax +; 5c. Store physical address of the buffer: zero if no data present, +; the temporary buffer if it was allocated, the given buffer otherwise. + xor eax, eax + cmp [.packetSize], eax + jz .hasphysbuf + mov eax, [.buffer] + mov edx, [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] + test edx, edx + jz @f + mov eax, [edx+uhci_original_buffer.UsedBuffer] +@@: + call get_phys_addr +.hasphysbuf: + mov [ecx+uhci_gtd.Buffer-uhci_gtd.SoftwarePart], eax +; 5d. For IN transfers, disallow short packets. +; This will be overridden, if needed, by uhci_alloc_transfer. + mov eax, [.token] + mov edx, [.packetSize] + dec edx + cmp al, USB_PID_IN + jnz @f + or byte [ecx+uhci_gtd.ControlStatus+3-uhci_gtd.SoftwarePart], 1 shl (29-24) ; disallow short packets + or byte [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], 1 +@@: +; 5e. Get Token field: combine [.token] with [.packetSize]. + shl edx, 21 + or edx, eax + mov [ecx+uhci_gtd.Token-uhci_gtd.SoftwarePart], edx +; 6. Flip toggle bit in [.token]. + xor eax, 1 shl 19 + mov [.token], eax +; 7. Return pointer to the next TD. + pop eax +.nothing: + ret +.fail: + xchg eax, edx + call free + xor eax, eax + ret +endp + +; This procedure is called from the several places in main USB code +; and activates the transfer which was previously allocated by +; uhci_alloc_transfer. +; ecx -> last descriptor for the transfer, ebx -> usb_pipe +proc uhci_insert_transfer +; DEBUGF 1,'K : uhci_insert_transfer: eax=%x, ecx=%x, [esp+4]=%x\n',eax,ecx,[esp+4] + and byte [eax+uhci_gtd.ControlStatus+2-uhci_gtd.SoftwarePart], not (1 shl (23-16)) ; clear Active bit + or byte [ecx+uhci_gtd.ControlStatus+3-uhci_gtd.SoftwarePart], 1 shl (24-24) ; set InterruptOnComplete bit + mov eax, [esp+4] + or byte [eax+uhci_gtd.ControlStatus+2-uhci_gtd.SoftwarePart], 1 shl (23-16) ; set Active bit + or byte [eax+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], 4 ; set Depth bit + ret +endp + +; Free all memory associated with one TD. +; For UHCI, this includes memory for uhci_gtd itself +; and the temporary buffer, if present. +proc uhci_free_td + mov eax, [esp+4] + mov eax, [eax+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] + and eax, not 1 + jz .nobuf + push ebx + call free + pop ebx +.nobuf: + sub dword [esp+4], uhci_gtd.SoftwarePart + jmp usb_free_common +endp diff --git a/kernel/trunk/const.inc b/kernel/trunk/const.inc index e666b32f23..414e171595 100644 --- a/kernel/trunk/const.inc +++ b/kernel/trunk/const.inc @@ -634,6 +634,17 @@ struct SRV srv_proc_ex dd ? ;+0x2C ;kernel mode service handler ends +struct USBSRV + srv SRV + usb_func dd ? +ends + +struct USBFUNC + strucsize dd ? + add_device dd ? + device_disconnect dd ? +ends + DRV_ENTRY equ 1 DRV_EXIT equ -1 diff --git a/kernel/trunk/core/dll.inc b/kernel/trunk/core/dll.inc index c9772e42af..3a9ee042f8 100644 --- a/kernel/trunk/core/dll.inc +++ b/kernel/trunk/core/dll.inc @@ -142,7 +142,11 @@ proc srv_handler stdcall, ioctl:dword cmp [edi+SRV.size], sizeof.SRV jne .fail - stdcall [edi+SRV.srv_proc], esi +; stdcall [edi+SRV.srv_proc], esi + mov eax, [edi+SRV.srv_proc] + test eax, eax + jz .fail + stdcall eax, esi ret .fail: xor eax, eax @@ -174,7 +178,11 @@ srv_handlerEx: cmp [eax+SRV.size], sizeof.SRV jne .fail - stdcall [eax+SRV.srv_proc], ecx +; stdcall [eax+SRV.srv_proc], ecx + mov eax, [eax+SRV.srv_proc] + test eax, eax + jz .fail + stdcall eax, ecx ret .fail: or eax, -1 @@ -213,8 +221,30 @@ proc get_service stdcall, sz_name:dword ret endp -align 4 -proc reg_service stdcall, name:dword, handler:dword +reg_service: + xor eax, eax + mov ecx, [esp+8] + jecxz .nothing + push sizeof.SRV + push ecx + pushd [esp+12] + call reg_service_ex +.nothing: + ret 8 + +reg_usb_driver: + push sizeof.USBSRV + pushd [esp+12] + pushd [esp+12] + call reg_service_ex + test eax, eax + jz .nothing + mov ecx, [esp+12] + mov [eax+USBSRV.usb_func], ecx +.nothing: + ret 12 + +proc reg_service_ex stdcall, name:dword, handler:dword, srvsize:dword push ebx @@ -223,10 +253,10 @@ proc reg_service stdcall, name:dword, handler:dword cmp [name], eax je .fail - cmp [handler], eax - je .fail +; cmp [handler], eax +; je .fail - mov eax, sizeof.SRV + mov eax, [srvsize] call malloc test eax, eax jz .fail diff --git a/kernel/trunk/core/taskman.inc b/kernel/trunk/core/taskman.inc index bda77e4e52..4ade26374a 100644 --- a/kernel/trunk/core/taskman.inc +++ b/kernel/trunk/core/taskman.inc @@ -550,7 +550,7 @@ proc destroy_app_space stdcall, pg_dir:dword, dlls_list:dword xor edx, edx push edx - mov eax, 0x2 + mov eax, 0x1 mov ebx, [pg_dir] .loop: ;eax = current slot of process diff --git a/kernel/trunk/detect/biosdisk.inc b/kernel/trunk/detect/biosdisk.inc index 2c6a3ac4bc..82286b9b54 100644 --- a/kernel/trunk/detect/biosdisk.inc +++ b/kernel/trunk/detect/biosdisk.inc @@ -7,6 +7,7 @@ ; Detect all BIOS hard drives. ; diamond, 2008 +; Do not include USB mass storages. CleverMouse, 2013 xor cx, cx mov es, cx @@ -24,21 +25,40 @@ bdds: test ah, ah jz bddc inc cx +; We are going to call int 13h/func 48h, Extended get drive parameters. +; The latest version of the EDD specification is 3.0. +; There are two slightly incompatible variants for version 3.0; +; original one from Phoenix in 1998, see e.g. +; http://www.t10.org/t13/technical/d98120r0.pdf, and T13 draft, +; http://www.t13.org/documents/UploadedDocuments/docs2004/d1572r3-EDD3.pdf +; T13 draft addresses more possible buses, so it gives additional 8 bytes +; for device path. +; Most BIOSes follow Phoenix, but T13 version is also known to be used +; (e.g. systems based on AMD Geode). +; Fortunately, there is an in/out length field, so +; it is easy to tell what variant was selected by the BIOS: +; Phoenix-3.0 has 42h bytes, T13-3.0 has 4Ah bytes. +; Note that 2.0 has 1Eh bytes, 1.1 has 1Ah bytes; both variants of 3.0 have +; the same structure for first 1Eh bytes, compatible with previous versions. +; Note also that difference between Phoenix-3.0 and T13-3.0 starts near the +; end of the structure, so the current code doesn't even need to distinguish. +; It needs, however, give at least 4Ah bytes as input and expect that BIOS +; could return 42h bytes as output while still giving all the information. mov ah, 48h push ds push es pop ds mov si, 0xA000 - mov word [si], 1Eh + mov word [si], 4Ah mov ah, 48h int 13h pop ds jc bddc2 - inc byte [es:0x907F] cmp word [es:si], 1Eh - jb bddl + jb .noide cmp word [es:si+1Ah], 0xFFFF - jz bddl + jz .noide + inc byte [es:0x907F] mov al, dl stosb push ds @@ -61,7 +81,15 @@ bdds: stosw pop ds jmp bddc2 -bddl: +.noide: + cmp word [es:si], 42h + jb .nousb + cmp word [es:si+28h], 'US' + jnz .nousb + cmp byte [es:si+2Ah], 'B' + jz bddc2 +.nousb: + inc byte [es:0x907F] mov al, dl stosb xor ax, ax diff --git a/kernel/trunk/docs/usbapi.txt b/kernel/trunk/docs/usbapi.txt new file mode 100644 index 0000000000..4ae11f140e --- /dev/null +++ b/kernel/trunk/docs/usbapi.txt @@ -0,0 +1,183 @@ +When the kernel detects a connected USB device, it configures the device in +terms of USB protocol - SET_ADDRESS + SET_CONFIGURATION, the first +configuration is always selected. The kernel also reads device descriptor to +print some information, reads and parses configuration descriptor. For every +interface the kernel looks for class code of this interface and loads the +corresponding COFF driver. Currently the correspondence is hardcoded into +the kernel code and looks as follows: 3 = usbhid.obj, 8 = usbstor.obj, +9 is handled by the kernel itself, other = usbother.obj. + +The driver must be standard driver in COFF format, exporting procedure +named "START" and a variable named "version". Loader calls "START" procedure +as stdcall with one parameter DRV_ENTRY = 1; if initialization is successful, +the "START" procedure is also called by shutdown code with one parameter +DRV_EXIT = -1. + +The driver must register itself as a USB driver in "START" procedure. +This is done by call to exported function RegUSBDriver and passing the returned +value as result of "START" procedure. + +void* __stdcall RegUSBDriver( + const char* name, + void* handler, + const USBFUNC* usbfunc +); + +The parameter 'name' should match the name of driver, "usbhid" for usbhid.obj. +The parameter 'handler' is optional; if it is non-NULL, it should point to +the standard handler for IOCTL interface as in non-USB drivers. +The parameter 'usbfunc' is a pointer to the following structure: + +struc USBFUNC +{ + .strucsize dd ? ; size of the structure, including this field + .add_device dd ? ; pointer to AddDevice function in the driver + ; required + .device_disconnect dd ? ; pointer to DeviceDisconnected function in the driver + ; optional, may be NULL +; other functions may be added in the future +} + +The driver should implement the function + +void* __stdcall AddDevice( + void* pipe0, + void* configdescr, + void* interfacedescr +); + +The parameter 'controlpipe' is a handle of the control pipe for endpoint zero +of the device. It can be used as the argument of USBControlTransferAsync. +The parameter 'configdescr' points to USB configuration descriptor +and all associated data, as returned by GET_DESCRIPTOR request. +The total length of all associated data is contained in the configuration +descriptor. +The parameter 'interfacedescr' points to USB interface descriptor corresponding +to the interface which is initializing. This is a pointer inside data +associated with the configuration descriptor. +Note that one device can implement many interfaces, so AddDevice may be +called several times with the same 'configdescr' and different 'interfacedescr'. +The returned value NULL means that the initialization has failed. +Any other value means that configuration was successful; the kernel does not +try to interpret the value. It can be, for example, pointer to the internal +data allocated with Kmalloc, or index in some internal table. Remember that +Kmalloc() is NOT stdcall, it destroys ebx. + +The driver can implement the function + +void __stdcall DeviceDisconnected( + void* devicedata +); + +If this function is implemented, the kernel calls it when the device is +disconnected, passing the returned value of AddDevice as 'devicedata'. + +The driver can use the following functions exported by the kernel. + +void* __stdcall USBOpenPipe( + void* pipe0, + int endpoint, + int maxpacketsize, + int type, + int interval +); + +The parameter 'pipe0' is a handle of the pipe for endpoint zero for +the device, as passed to AddDevice. It is used to identify the device. +The parameter 'endpoint' is endpoint number as defined by USB. Lower +4 bits form the number itself, bit 7 - highest bit of low byte - +is 0/1 for OUT/IN endpoints, other bits should be zero. +The parameter 'maxpacketsize' sets the maximum packet size for this pipe. +The parameter 'type' selects the type of the endpoint as defined by USB: +0 = control, 1 = isochronous (not supported yet), 2 = bulk, 3 = interrupt. +The parameter 'interval' is ignored for control and bulk endpoints. +For interrupt endpoints, it sets the polling interval in milliseconds. +The function returns a handle to the pipe or NULL on failure. + +void* __stdcall USBNormalTransferAsync( + void* pipe, + void* buffer, + int size, + void* callback, + void* calldata, + int flags +); +void* __stdcall USBControlTransferAsync( + void* pipe, + void* config, + void* buffer, + int size, + void* callback, + void* calldata, + int flags +); + +The first function inserts a bulk or interrupt transfer to the transfer queue +for given pipe. Type and direction of transfer are fixed for bulk and interrupt +endpoints and are set in USBOpenPipe. The second function inserts a control +transfer to the transfer queue for given pipe. Direction of a control transfer +is concluded from 'config' packet, bit 7 of byte 0 is set for IN transfers +and cleared for OUT transfers. These function return immediately; when data +are transferred, the callback function will be called. + +The parameter 'pipe' is a handle returned by USBOpenPipe. +The parameter 'config' of USBControlTransferAsync points to 8-byte +configuration packet as defined by USB. +The parameter 'buffer' is a pointer to buffer. For IN transfers, it will be +filled with the data. For OUT transfers, it should contain data to be +transferred. It can be NULL for an empty transfer or if no additional data are +required for a control transfer. +The parameter 'size' is size of data to transfer. It can be 0 for an empty +transfer or if no additional data are required for a control transfer. +The parameter 'callback' is a pointer to a function which will be called +when the transfer will be done. +The parameter 'calldata' will be passed as is to the callback function. +For example, it can be NULL, it can be a pointer to device data or it can be +a pointer to data used to pass additional parameters between caller and +callback. The transfer-specific data can also be associated with 'buffer', +preceding (negative offsets from 'buffer') or following (offsets more than +or equal to 'size') the buffer itself. +The parameter 'flags' is the bitmask. +The bit 0 is ignored for OUT transfers, for IN transfers it controls whether +the device can transfer less data than 'size' bytes. If the bit is 0, a small +transfer is an error; if the bit is 1, a small transfer is OK. +All other bits are reserved and should be zero. +The returned value is NULL if an error occured and non-NULL if the transfer +was successfully queued. If an error will occur later, the callback function +will be notified. + +void __stdcall CallbackFunction( + void* pipe, + int status, + void* buffer, + int length, + void* calldata +); + +The parameters 'pipe', 'buffer', 'calldata' are the same as for the +corresponding USB*TransferAsync. +The parameter 'length' is the number of bytes transferred. For +control transfers, this includes 8 bytes from SETUP stage, so +0 means that SETUP stage failed and 'size'+8 means full transfer. +The parameter 'status' is nonzero if an error occured. +USB_STATUS_OK = 0 ; no error +USB_STATUS_CRC = 1 ; CRC error +USB_STATUS_BITSTUFF = 2 ; bit stuffing violation +USB_STATUS_TOGGLE = 3 ; data toggle mismatch +USB_STATUS_STALL = 4 ; device returned STALL +USB_STATUS_NORESPONSE = 5 ; device not responding +USB_STATUS_PIDCHECK = 6 ; invalid PID check bits +USB_STATUS_WRONGPID = 7 ; unexpected PID value +USB_STATUS_OVERRUN = 8 ; too many data from endpoint +USB_STATUS_UNDERRUN = 9 ; too few data from endpoint +USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer + ; possible only for isochronous transfers +USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer + ; possible only for isochronous transfers +USB_STATUS_DISCONNECTED = 16 ; device disconnected + +If several transfers are queued for the same pipe, their callback functions +are called in the same order as they were queued. +When the device is disconnected, all callback functions are called +with USB_STATUS_DISCONNECTED. The call to DeviceDisconnected() occurs after +all callbacks. diff --git a/kernel/trunk/drivers/fdo.inc b/kernel/trunk/drivers/fdo.inc new file mode 100644 index 0000000000..d7dff6ff5b --- /dev/null +++ b/kernel/trunk/drivers/fdo.inc @@ -0,0 +1,439 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; +;; Copyright (C) KolibriOS team 2004-2007. All rights reserved. ;; +;; Distributed under terms of the GNU General Public License ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +; +; Formatted Debug Output (FDO) +; Copyright (c) 2005-2006, mike.dld +; Created: 2005-01-29, Changed: 2006-11-10 +; +; For questions and bug reports, mail to mike.dld@gmail.com +; +; Available format specifiers are: %s, %d, %u, %x (with partial width support) +; + +; to be defined: +; __DEBUG__ equ 1 +; __DEBUG_LEVEL__ equ 5 + +macro debug_func name { + if used name + name@of@func equ name +} + +macro debug_beginf { + align 4 + name@of@func: +} + +debug_endf fix end if + +macro DEBUGS _sign,[_str] { + common + local tp + tp equ 0 + match _arg:_num,_str \{ + DEBUGS_N _sign,_num,_arg + tp equ 1 + \} + match =0 _arg,tp _str \{ + DEBUGS_N _sign,,_arg + \} +} + +macro DEBUGS_N _sign,_num,[_str] { + common + pushf + pushad + local ..str,..label,is_str + is_str = 0 + forward + if _str eqtype '' + is_str = 1 + end if + common + if is_str = 1 + jmp ..label + ..str db _str,0 + ..label: + add esp, 4*8+4 + mov edx, ..str + sub esp, 4*8+4 + else + mov edx, _str + end if + if ~_num eq + if _num eqtype eax + if _num in + mov esi, _num + else if ~_num eq esi + movzx esi, _num + end if + else if _num eqtype 0 + mov esi, _num + else + local tp + tp equ 0 + match [_arg],_num \{ + mov esi, dword[_arg] + tp equ 1 + \} + match =0 =dword[_arg],tp _num \{ + mov esi, dword[_arg] + tp equ 1 + \} + match =0 =word[_arg],tp _num \{ + movzx esi, word[_arg] + tp equ 1 + \} + match =0 =byte[_arg],tp _num \{ + movzx esi, byte[_arg] + tp equ 1 + \} + match =0,tp \{ + 'Error: specified string width is incorrect' + \} + end if + else + mov esi, 0x7FFFFFFF + end if + call fdo_debug_outstr + popad + popf +} + +macro DEBUGD _sign,_dec { + local tp + tp equ 0 + match _arg:_num,_dec \{ + DEBUGD_N _sign,_num,_arg + tp equ 1 + \} + match =0 _arg,tp _dec \{ + DEBUGD_N _sign,,_arg + \} +} + +macro DEBUGD_N _sign,_num,_dec { + pushf + pushad + if (~_num eq) + if (_dec eqtype eax | _dec eqtype 0) + 'Error: precision allowed only for in-memory variables' + end if + if (~_num in <1,2,4>) + if _sign + 'Error: 1, 2 and 4 are only allowed for precision in %d' + else + 'Error: 1, 2 and 4 are only allowed for precision in %u' + end if + end if + end if + if _dec eqtype eax + if _dec in + mov eax, _dec + else if ~_dec eq eax + if _sign = 1 + movsx eax, _dec + else + movzx eax, _dec + end if + end if + else if _dec eqtype 0 + mov eax, _dec + else + add esp, 4*8+4 + if _num eq + mov eax, dword _dec + else if _num = 1 + if _sign = 1 + movsx eax, byte _dec + else + movzx eax, byte _dec + end if + else if _num = 2 + if _sign = 1 + movsx eax, word _dec + else + movzx eax, word _dec + end if + else + mov eax, dword _dec + end if + sub esp, 4*8+4 + end if + mov cl, _sign + call fdo_debug_outdec + popad + popf +} + +macro DEBUGH _sign,_hex { + local tp + tp equ 0 + match _arg:_num,_hex \{ + DEBUGH_N _sign,_num,_arg + tp equ 1 + \} + match =0 _arg,tp _hex \{ + DEBUGH_N _sign,,_arg + \} +} + +macro DEBUGH_N _sign,_num,_hex { + pushf + pushad + if (~_num eq) & (~_num in <1,2,3,4,5,6,7,8>) + 'Error: 1..8 are only allowed for precision in %x' + end if + if _hex eqtype eax + if _hex in + if ~_hex eq eax + mov eax, _hex + end if + mov edx, 8 + else if _hex in + if ~_hex eq ax + movzx eax, _hex + end if + if (_num eq) + mov edx, 4 + end if + else if _hex in + if ~_hex eq al + movzx eax, _hex + end if + if (_num eq) + mov edx, 2 + end if + end if + else if _hex eqtype 0 + mov eax, _hex + else + add esp, 4*8+4 + mov eax, dword _hex + sub esp, 4*8+4 + end if + if ~_num eq + mov edx, _num + else + if ~_hex eqtype eax + mov edx, 8 + end if + end if + call fdo_debug_outhex + popad + popf +} + +;----------------------------------------------------------------------------- + +debug_func fdo_debug_outchar +debug_beginf + pushad + movzx ebx, al + mov eax, 1 +; mov ecx,sys_msg_board +; call ecx ; sys_msg_board + stdcall SysMsgBoardChar + popad + ret +debug_endf + +debug_func fdo_debug_outstr +debug_beginf + mov eax, 1 + .l1: + dec esi + js .l2 + movzx ebx, byte[edx] + or bl, bl + jz .l2 +; mov ecx,sys_msg_board +; call ecx ; sys_msg_board + stdcall SysMsgBoardChar + inc edx + jmp .l1 + .l2: + ret +debug_endf + +debug_func fdo_debug_outdec +debug_beginf + or cl, cl + jz @f + or eax, eax + jns @f + neg eax + push eax + mov al, '-' + call fdo_debug_outchar + pop eax + @@: + push 10 + pop ecx + push -'0' + .l1: + xor edx, edx + div ecx + push edx + test eax, eax + jnz .l1 + .l2: + pop eax + add al, '0' + jz .l3 + call fdo_debug_outchar + jmp .l2 + .l3: + ret +debug_endf + +debug_func fdo_debug_outhex + __fdo_hexdigits db '0123456789ABCDEF' +debug_beginf + mov cl, dl + neg cl + add cl, 8 + shl cl, 2 + rol eax, cl + .l1: + rol eax, 4 + push eax + and eax, 0x0000000F + mov al, [__fdo_hexdigits+eax] + call fdo_debug_outchar + pop eax + dec edx + jnz .l1 + ret +debug_endf + +;----------------------------------------------------------------------------- + +macro DEBUGF _level,_format,[_arg] { + common + if __DEBUG__ = 1 & _level >= __DEBUG_LEVEL__ + local ..f1,f2,a1,a2,c1,c2,c3,..lbl + _debug_str_ equ __debug_str_ # a1 + a1 = 0 + c2 = 0 + c3 = 0 + f2 = 0 + repeat ..lbl-..f1 + virtual at 0 + db _format,0,0 + load c1 word from %-1 + end virtual + if c1 = '%s' + virtual at 0 + db _format,0,0 + store word 0 at %-1 + load c1 from f2-c2 + end virtual + if c1 <> 0 + DEBUGS 0,_debug_str_+f2-c2 + end if + c2 = c2 + 1 + f2 = %+1 + DEBUGF_HELPER S,a1,0,_arg + else if c1 = '%x' + virtual at 0 + db _format,0,0 + store word 0 at %-1 + load c1 from f2-c2 + end virtual + if c1 <> 0 + DEBUGS 0,_debug_str_+f2-c2 + end if + c2 = c2 + 1 + f2 = %+1 + DEBUGF_HELPER H,a1,0,_arg + else if c1 = '%d' | c1 = '%u' + local c4 + if c1 = '%d' + c4 = 1 + else + c4 = 0 + end if + virtual at 0 + db _format,0,0 + store word 0 at %-1 + load c1 from f2-c2 + end virtual + if c1 <> 0 + DEBUGS 0,_debug_str_+f2-c2 + end if + c2 = c2 + 1 + f2 = %+1 + DEBUGF_HELPER D,a1,c4,_arg + else if c1 = '\n' + c3 = c3 + 1 + end if + end repeat + virtual at 0 + db _format,0,0 + load c1 from f2-c2 + end virtual + if (c1<>0)&(f2<>..lbl-..f1-1) + DEBUGS 0,_debug_str_+f2-c2 + end if + virtual at 0 + ..f1 db _format,0 + ..lbl: + __debug_strings equ __debug_strings,_debug_str_,<_format>,..lbl-..f1-1-c2-c3 + end virtual + end if +} + +macro __include_debug_strings dummy,[_id,_fmt,_len] { + common + local c1,a1,a2 + forward + if defined _len & ~_len eq + _id: + a1 = 0 + a2 = 0 + repeat _len + virtual at 0 + db _fmt,0,0 + load c1 word from %+a2-1 + end virtual + if (c1='%s')|(c1='%x')|(c1='%d')|(c1='%u') + db 0 + a2 = a2 + 1 + else if (c1='\n') + dw $0A0D + a1 = a1 + 1 + a2 = a2 + 1 + else + db c1 and 0x0FF + end if + end repeat + db 0 + end if +} + +macro DEBUGF_HELPER _letter,_num,_sign,[_arg] { + common + local num + num = 0 + forward + if num = _num + DEBUG#_letter _sign,_arg + end if + num = num+1 + common + _num = _num+1 +} + +macro include_debug_strings { + if __DEBUG__ = 1 + match dbg_str,__debug_strings \{ + __include_debug_strings dbg_str + \} + end if +} diff --git a/kernel/trunk/drivers/imports.inc b/kernel/trunk/drivers/imports.inc index f511dc4668..592b533387 100644 --- a/kernel/trunk/drivers/imports.inc +++ b/kernel/trunk/drivers/imports.inc @@ -96,7 +96,15 @@ kernel_export \ LFBAddress,\ GetDisplay,\ SetScreen,\ +\ + RegUSBDriver,\ + USBOpenPipe,\ + USBNormalTransferAsync,\ + USBControlTransferAsync,\ \ DiskAdd,\ DiskMediaChanged,\ - DiskDel + DiskDel,\ +\ + TimerHS,\ + CancelTimerHS diff --git a/kernel/trunk/drivers/usbhid.asm b/kernel/trunk/drivers/usbhid.asm new file mode 100644 index 0000000000..cc997a1cd0 --- /dev/null +++ b/kernel/trunk/drivers/usbhid.asm @@ -0,0 +1,696 @@ +; standard driver stuff +format MS COFF + +DEBUG = 1 + +; this is for DEBUGF macro from 'fdo.inc' +__DEBUG__ = 1 +__DEBUG_LEVEL__ = 1 + +include 'proc32.inc' +include 'imports.inc' +include 'fdo.inc' + +public START +public version + +; USB constants +DEVICE_DESCR_TYPE = 1 +CONFIG_DESCR_TYPE = 2 +STRING_DESCR_TYPE = 3 +INTERFACE_DESCR_TYPE = 4 +ENDPOINT_DESCR_TYPE = 5 +DEVICE_QUALIFIER_DESCR_TYPE = 6 + +CONTROL_PIPE = 0 +ISOCHRONOUS_PIPE = 1 +BULK_PIPE = 2 +INTERRUPT_PIPE = 3 + +; USB structures +virtual at 0 +config_descr: +.bLength db ? +.bDescriptorType db ? +.wTotalLength dw ? +.bNumInterfaces db ? +.bConfigurationValue db ? +.iConfiguration db ? +.bmAttributes db ? +.bMaxPower db ? +.sizeof: +end virtual + +virtual at 0 +interface_descr: +.bLength db ? +.bDescriptorType db ? +.bInterfaceNumber db ? +.bAlternateSetting db ? +.bNumEndpoints db ? +.bInterfaceClass db ? +.bInterfaceSubClass db ? +.bInterfaceProtocol db ? +.iInterface db ? +.sizeof: +end virtual + +virtual at 0 +endpoint_descr: +.bLength db ? +.bDescriptorType db ? +.bEndpointAddress db ? +.bmAttributes db ? +.wMaxPacketSize dw ? +.bInterval db ? +.sizeof: +end virtual + +; Driver data for all devices +virtual at 0 +device_data: +.type dd ? ; 1 = keyboard, 2 = mouse +.intpipe dd ? ; interrupt pipe handle +.packetsize dd ? +.packet rb 8 ; packet with data from device +.control rb 8 ; control packet to device +.sizeof: +end virtual + +; Driver data for mouse +virtual at device_data.sizeof +mouse_data: +; no additional data +.sizeof: +end virtual + +; Driver data for keyboard +virtual at device_data.sizeof +keyboard_data: +.handle dd ? ; keyboard handle from RegKeyboard +.configpipe dd ? ; config pipe handle +.prevpacket rb 8 ; previous packet with data from device +.timer dd ? ; auto-repeat timer handle +.repeatkey db ? ; auto-repeat key code +.ledstate db ? ; state of LEDs + align 4 +.sizeof: +end virtual + +section '.flat' code readable align 16 +; The start procedure. +START: +; 1. Test whether the procedure is called with the argument DRV_ENTRY. +; If not, return 0. + xor eax, eax ; initialize return value + cmp dword [esp+4], 1 ; compare the argument + jnz .nothing +; 2. Register self as a USB driver. +; The name is my_driver = 'usbhid'; IOCTL interface is not supported; +; usb_functions is an offset of a structure with callback functions. + stdcall RegUSBDriver, my_driver, eax, usb_functions +; 3. Return the returned value of RegUSBDriver. +.nothing: + ret 4 + +; This procedure is called when new HID device is detected. +; It initializes the device. +AddDevice: +; Arguments are addressed through esp. In this point of the function, +; [esp+4] = a handle of the config pipe, [esp+8] points to config_descr +; structure, [esp+12] points to interface_descr structure. +; 1. Check device type. Currently only mice and keyboards with +; boot protocol are supported. +; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and +; bInterfaceProtocol are subsequent in interface_descr, just one +; memory reference is used for both. + mov edx, [esp+12] + push ebx ; save used register to be stdcall + mov cx, word [edx+interface_descr.bInterfaceSubClass] +; 1b. For boot protocol, subclass must be 1 and protocol must be either 1 for +; a keyboard or 2 for a mouse. Check. + cmp cx, 0x0101 + jz .keyboard + cmp cx, 0x0201 + jz .mouse +; 1c. If the device is neither a keyboard nor a mouse, print a message and +; go to 6c. + DEBUGF 1,'K : unknown HID device\n' + jmp .nothing +; 1d. If the device is a keyboard or a mouse, print a message and continue +; configuring. +.keyboard: + DEBUGF 1,'K : USB keyboard detected\n' + push keyboard_data.sizeof + jmp .common +.mouse: + DEBUGF 1,'K : USB mouse detected\n' + push mouse_data.sizeof +.common: +; 2. Allocate memory for device data. + pop eax ; get size of device data +; 2a. Call the kernel, saving and restoring register edx. + push edx + call Kmalloc + pop edx +; 2b. Check result. If failed, say a message and go to 6c. + test eax, eax + jnz @f + DEBUGF 1,'K : no memory\n' + jmp .nothing +@@: + xchg eax, ebx +; HID devices use one IN interrupt endpoint for polling the device +; and an optional OUT interrupt endpoint. We do not use the later, +; but must locate the first. Look for the IN interrupt endpoint. +; 3. Get the upper bound of all descriptors' data. + mov eax, [esp+8+4] ; configuration descriptor + movzx ecx, [eax+config_descr.wTotalLength] + add eax, ecx +; 4. Loop over all descriptors until +; either end-of-data reached - this is fail +; or interface descriptor found - this is fail, all further data +; correspond to that interface +; or endpoint descriptor found. +; 4a. Loop start: eax points to the interface descriptor. +.lookep: +; 4b. Get next descriptor. + movzx ecx, byte [edx] ; the first byte of all descriptors is length + add edx, ecx +; 4c. Check that at least two bytes are readable. The opposite is an error. + inc edx + cmp edx, eax + jae .errorep + dec edx +; 4d. Check that this descriptor is not interface descriptor. The opposite is +; an error. + cmp byte [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE + jz .errorep +; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue +; the loop. + cmp byte [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE + jnz .lookep +; 5. Check that the descriptor contains all required data and all data are +; readable. If so, proceed to 7. + cmp byte [edx+endpoint_descr.bLength], endpoint_descr.sizeof + jb .errorep + sub eax, endpoint_descr.sizeof + cmp edx, eax + jbe @f +; 6. An error occured during processing endpoint descriptor. +.errorep: +; 6a. Print a message. + DEBUGF 1,'K : error: invalid endpoint descriptor\n' +; 6b. Free memory allocated for device data. +.free: + xchg eax, ebx + call Kfree +.nothing: +; 6c. Return an error. + xor eax, eax + pop ebx + ret 12 +@@: +; 7. Check that the endpoint is IN interrupt endpoint. If not, go to 6. + test [edx+endpoint_descr.bEndpointAddress], 80h + jz .errorep + mov cl, [edx+endpoint_descr.bmAttributes] + and cl, 3 + cmp cl, INTERRUPT_PIPE + jnz .errorep +; 8. Open pipe for the endpoint. +; 8a. Load parameters from the descriptor. + movzx ecx, [edx+endpoint_descr.bEndpointAddress] + movzx eax, [edx+endpoint_descr.bInterval] + movzx edx, [edx+endpoint_descr.wMaxPacketSize] +; 8b. Call the kernel, saving and restoring edx. + push edx + stdcall USBOpenPipe, [esp+4+24], ecx, edx, INTERRUPT_PIPE, eax + pop edx +; 8c. Check result. If failed, go to 6b. + test eax, eax + jz .free +; We use 12 bytes for device type, interrupt pipe and interrupt packet size, +; 8 bytes for a packet and 8 bytes for previous packet, used by a keyboard. +; 9. Initialize device data. + mov [ebx+device_data.intpipe], eax + push 8 + pop ecx + cmp edx, ecx + jb @f + mov edx, ecx +@@: + xor eax, eax + mov [ebx+device_data.packetsize], edx + mov dword [ebx+device_data.packet], eax + mov dword [ebx+device_data.packet+4], eax + mov edx, [esp+12+4] ; interface descriptor + movzx ecx, [edx+interface_descr.bInterfaceProtocol] + mov [ebx+device_data.type], ecx + cmp ecx, 1 + jnz @f + mov [ebx+keyboard_data.handle], eax + mov [ebx+keyboard_data.timer], eax + mov [ebx+keyboard_data.repeatkey], al + mov dword [ebx+keyboard_data.prevpacket], eax + mov dword [ebx+keyboard_data.prevpacket+4], eax + mov eax, [esp+4+4] + mov [ebx+keyboard_data.configpipe], eax +@@: +; 10. Send the control packet SET_PROTOCOL(Boot Protocol) to the interface. + lea eax, [ebx+device_data.control] + mov dword [eax], 21h + (0Bh shl 8) + (0 shl 16) ; class request to interface + SET_PROTOCOL + Boot protocol + and dword [eax+4], 0 + mov dl, [edx+interface_descr.bInterfaceNumber] + mov [eax+4], dl +; Callback function is mouse_configured for mice and keyboard_configured1 for keyboards. + mov edx, keyboard_configured1 + cmp ecx, 1 + jz @f + mov edx, mouse_configured +@@: + stdcall USBControlTransferAsync, [esp+4+28], eax, 0, 0, edx, ebx, 0 +; 11. Return with pointer to device data as returned value. + xchg eax, ebx + pop ebx + ret 12 + +; This function is called when SET_PROTOCOL command for keyboard is done, +; either successful or unsuccessful. +keyboard_configured1: + xor edx, edx +; 1. Check the status of the transfer. +; If the transfer was failed, go to the common error handler. + cmp dword [esp+8], edx ; status is zero? + jnz keyboard_data_ready.error +; 2. Send the control packet SET_IDLE(infinity). HID auto-repeat is not useful. + mov eax, [esp+20] + push edx ; flags for USBControlTransferAsync + push eax ; userdata for USBControlTransferAsync + add eax, device_data.control + mov dword [eax], 21h + (0Ah shl 8) + (0 shl 24) ; class request to interface + SET_IDLE + no autorepeat + stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \ + eax, edx, edx, keyboard_configured2; , , +; 3. Return. + ret 20 + +; This function is called when SET_IDLE command for keyboard is done, +; either successful or unsuccessful. +keyboard_configured2: +; Check the status of the transfer and go to the corresponding label +; in the main handler. + cmp dword [esp+8], 0 + jnz keyboard_data_ready.error + mov edx, [esp+20] + push edx + stdcall RegKeyboard, usbkbd_functions, edx + pop edx + mov [edx+keyboard_data.handle], eax + jmp keyboard_data_ready.next + +; This function is called when another interrupt packet arrives, +; processed either successfully or unsuccessfully. +; It should parse the packet and initiate another transfer with +; the same callback function. +keyboard_data_ready: +; 1. Check the status of the transfer. + mov eax, [esp+8] + test eax, eax + jnz .error +; Parse the packet, comparing with the previous packet. +; For boot protocol, USB keyboard packet consists of the first byte +; with status keys that are currently pressed. The second byte should +; be ignored, and other 5 bytes denote keys that are currently pressed. + push esi ebx ; save used registers to be stdcall +; 2. Process control keys. +; 2a. Initialize before loop for control keys. edx = mask for control bits +; that were changed. + mov ebx, [esp+20+8] + movzx edx, byte [ebx+device_data.packet] ; get state of control keys + xor dl, byte [ebx+keyboard_data.prevpacket] ; compare with previous state +; 2b. If state of control keys has not changed, advance to 3. + jz .nocontrol +; 2c. Otherwise, loop over control keys; esi = bit number. + xor esi, esi +.controlloop: +; 2d. Skip bits that have not changed. + bt edx, esi + jnc .controlnext + push edx ; save register which is possibly modified by API +; The state of the current control key has changed. +; 2e. For extended control keys, send the prefix 0xE0. + mov al, [control_keys+esi] + test al, al + jns @f + push eax + mov ecx, 0xE0 + call SetKeyboardData + pop eax + and al, 0x7F +@@: +; 2f. If the current state of the control key is "pressed", send normal +; scancode. Otherwise, the key is released, so set the high bit in scancode. + movzx ecx, al + bt dword [ebx+device_data.packet], esi + jc @f + or cl, 0x80 +@@: + call SetKeyboardData + pop edx ; restore register which was possibly modified by API +.controlnext: +; 2g. We have 8 control keys. + inc esi + cmp esi, 8 + jb .controlloop +.nocontrol: +; 3. Initialize before loop for normal keys. esi = index. + push 2 + pop esi +.normalloop: +; 4. Process one key which was pressed in the previous packet. +; 4a. Get the next pressed key from the previous packet. + movzx eax, byte [ebx+esi+keyboard_data.prevpacket] +; 4b. Ignore special codes. + cmp al, 3 + jbe .normalnext1 +; 4c. Ignore keys that are still pressed in the current packet. + lea ecx, [ebx+device_data.packet] + call haskey + jz .normalnext1 +; 4d. Say warning about keys with strange codes. + cmp eax, normal_keys_number + jae .badkey1 + movzx ecx, [normal_keys+eax] + jecxz .badkey1 +; 4e. For extended keys, send the prefix 0xE0. + push ecx ; save keycode + test cl, cl + jns @f + push ecx + mov ecx, 0xE0 + call SetKeyboardData + pop ecx +@@: +; 4f. Send the release event. + or cl, 0x80 + call SetKeyboardData +; 4g. If this key is autorepeating, stop the timer. + pop ecx ; restore keycode + cmp cl, [ebx+keyboard_data.repeatkey] + jnz .normalnext1 + mov eax, [ebx+keyboard_data.timer] + test eax, eax + jz .normalnext1 + stdcall CancelTimerHS, eax + and [ebx+keyboard_data.timer], 0 + jmp .normalnext1 +.badkey1: + DEBUGF 1,'K : unknown keycode: %x\n',al +.normalnext1: +; 5. Process one key which is pressed in the current packet. +; 5a. Get the next pressed key from the current packet. + movzx eax, byte [ebx+esi+device_data.packet] +; 5b. Ignore special codes. + cmp al, 3 + jbe .normalnext2 +; 5c. Ignore keys that were already pressed in the previous packet. + lea ecx, [ebx+keyboard_data.prevpacket] + call haskey + jz .normalnext2 +; 5d. Say warning about keys with strange codes. + cmp eax, normal_keys_number + jae .badkey2 + movzx ecx, [normal_keys+eax] + jecxz .badkey2 +; 5e. For extended keys, send the prefix 0xE0. + push ecx ; save keycode + test cl, cl + jns @f + push ecx + mov ecx, 0xE0 + call SetKeyboardData + pop ecx +@@: +; 5f. Send the press event. + and cl, not 0x80 + call SetKeyboardData +; 5g. Stop the current auto-repeat timer, if present. + mov eax, [ebx+keyboard_data.timer] + test eax, eax + jz @f + stdcall CancelTimerHS, eax +@@: +; 5h. Start the auto-repeat timer. + pop ecx ; restore keycode + mov [ebx+keyboard_data.repeatkey], cl + stdcall TimerHS, 25, 5, autorepeat_timer, ebx + mov [ebx+keyboard_data.timer], eax + jmp .normalnext2 +.badkey2: + DEBUGF 1,'K : unknown keycode: %x\n',al +.normalnext2: +; 6. Advance to next key. + inc esi + cmp esi, 8 + jb .normalloop +; 7. Save the packet data for future reference. + mov eax, dword [ebx+device_data.packet] + mov dword [ebx+keyboard_data.prevpacket], eax + mov eax, dword [ebx+device_data.packet+4] + mov dword [ebx+keyboard_data.prevpacket+4], eax + pop ebx esi ; restore registers to be stdcall +.next: +; 8. Initiate transfer on the interrupt pipe. + mov eax, [esp+20] + push 1 ; flags for USBNormalTransferAsync + push eax ; userdata for USBNormalTransferAsync + add eax, device_data.packet + stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \ + eax, dword [eax+device_data.packetsize-device_data.packet], \ + keyboard_data_ready;, , +; 9. Return. +.nothing: + ret 20 +.error: +; An error has occured. +; 10. If an error is caused by the disconnect, do nothing, it is handled +; in DeviceDisconnected. Otherwise, say a message. + cmp eax, 16 + jz @f + push esi + mov esi, errormsgkbd + call SysMsgBoardStr + pop esi +@@: + ret 20 + +; Auxiliary procedure for keyboard_data_ready. +haskey: + push 2 + pop edx +@@: + cmp byte [ecx+edx], al + jz @f + inc edx + cmp edx, 7 + jbe @b +@@: + ret + +; Timer function for auto-repeat. +autorepeat_timer: + mov eax, [esp+4] + movzx ecx, [eax+keyboard_data.repeatkey] + test cl, cl + jns @f + push ecx + mov ecx, 0xE0 + call SetKeyboardData + pop ecx + and cl, not 0x80 +@@: + call SetKeyboardData + ret 4 + +; This function is called to update LED state on the keyboard. +SetKeyboardLights: + mov eax, [esp+4] + add eax, device_data.control + mov dword [eax], 21h + (9 shl 8) + (2 shl 24) + ; class request to interface + SET_REPORT + Output zero report + mov byte [eax+6], 1 + mov edx, [esp+8] + shr dl, 1 + jnc @f + or dl, 4 +@@: + lea ecx, [eax+keyboard_data.ledstate-device_data.control] + mov [ecx], dl + stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \ + eax, ecx, 1, keyboard_data_ready.nothing, 0, 0 + ret 8 + +; This function is called when it is safe to free keyboard data. +CloseKeyboard: + mov eax, [esp+4] + push ebx + call Kfree + pop ebx + ret 4 + +; This function is called when SET_PROTOCOL command for mouse is done, +; either successful or unsuccessful. +mouse_configured: +; Check the status of the transfer and go to the corresponding label +; in the main handler. + cmp dword [esp+8], 0 + jnz mouse_data_ready.error + mov eax, [esp+20] + add eax, device_data.packet + jmp mouse_data_ready.next + +; This function is called when another interrupt packet arrives, +; processed either successfully or unsuccessfully. +; It should parse the packet and initiate another transfer with +; the same callback function. +mouse_data_ready: +; 1. Check the status of the transfer. + mov eax, [esp+8] + test eax, eax + jnz .error + mov edx, [esp+16] +; 2. Parse the packet. +; For boot protocol, USB mouse packet consists of at least 3 bytes. +; The first byte is state of mouse buttons, the next two bytes are +; x and y movements. +; Normal mice do not distinguish between boot protocol and report protocol; +; in this case, scroll data are also present. Advanced mice, however, +; support two different protocols, boot protocol is used for compatibility +; and does not contain extended buttons or scroll data. + mov eax, [esp+12] ; buffer + push eax + xor ecx, ecx + cmp edx, 4 + jbe @f + movsx ecx, byte [eax+4] +@@: + push ecx + xor ecx, ecx + cmp edx, 3 + jbe @f + movsx ecx, byte [eax+3] + neg ecx +@@: + push ecx + xor ecx, ecx + cmp edx, 2 + jbe @f + movsx ecx, byte [eax+2] + neg ecx +@@: + push ecx + movsx ecx, byte [eax+1] + push ecx + movzx ecx, byte [eax] + push ecx + call SetMouseData + pop eax +.next: +; 3. Initiate transfer on the interrupt pipe. + stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \ + eax, dword [eax+device_data.packetsize-device_data.packet], mouse_data_ready, eax, 1 +; 4. Return. + ret 20 +.error: +; An error has occured. +; 5. If an error is caused by the disconnect, do nothing, it is handled +; in DeviceDisconnected. Otherwise, say a message. + cmp eax, 16 + jz @f + push esi + mov esi, errormsgmouse + call SysMsgBoardStr + pop esi +@@: + ret 20 + +; This function is called when the device is disconnected. +DeviceDisconnected: + push ebx ; save used register to be stdcall +; 1. Say a message. Use different messages for keyboards and mice. + mov ebx, [esp+4+4] + push esi + mov esi, disconnectmsgk + cmp byte [ebx+device_data.type], 1 + jz @f + mov esi, disconnectmsgm +@@: + stdcall SysMsgBoardStr + pop esi +; 2. If device is keyboard, then we must unregister it as a keyboard and +; possibly stop the auto-repeat timer. + cmp byte [ebx+device_data.type], 1 + jnz .nokbd + mov eax, [ebx+keyboard_data.timer] + test eax, eax + jz @f + stdcall CancelTimerHS, eax +@@: + mov ecx, [ebx+keyboard_data.handle] + jecxz .nokbd + stdcall DelKeyboard, ecx +; If keyboard is registered, then we should free data in CloseKeyboard, not here. + jmp .nothing +.nokbd: +; 3. Free the device data. + xchg eax, ebx + call Kfree +; 4. Return. +.nothing: + pop ebx ; restore used register to be stdcall + ret 4 ; purge one dword argument to be stdcall + +; strings +my_driver db 'usbhid',0 +errormsgmouse db 'K : USB transfer error, disabling mouse',10,0 +errormsgkbd db 'K : USB transfer error, disabling keyboard',10,0 +disconnectmsgm db 'K : USB mouse disconnected',10,0 +disconnectmsgk db 'K : USB keyboard disconnected',10,0 + +; data for keyboard: correspondence between HID usage keys and PS/2 scancodes. +EX = 80h +label control_keys byte + db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX +label normal_keys byte + db 00h, 00h, 00h, 00h, 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h ; 0x + db 32h, 31h, 18h, 19h, 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h ; 1x + db 04h, 05h, 06h, 07h, 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah ; 2x + db 1Bh, 2Bh, 2Bh, 27h, 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h ; 3x + db 41h, 42h, 43h, 44h, 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX ; 4x + db 4Bh+EX,50h+EX,48h+EX,45h,35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h,51h,4Bh,4Ch,4Dh,47h ; 5x + db 48h, 49h, 52h, 53h, 56h,5Dh+EX,5Eh+EX,59h,64h,65h,66h, 67h, 68h, 69h, 6Ah, 6Bh ; 6x + db 6Ch, 6Dh, 6Eh, 76h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h ; 7x + db 00h, 00h, 00h, 00h, 00h, 7Eh, 00h, 73h, 70h, 7Dh, 79h, 7Bh, 5Ch, 00h, 00h, 00h ; 8x + db 0F2h,0F1h,78h, 77h, 76h +normal_keys_number = $ - normal_keys + +; Exported variable: kernel API version. +align 4 +version dd 50005h +; Structure with callback functions. +usb_functions: + dd 12 + dd AddDevice + dd DeviceDisconnected + +; Structure with callback functions for keyboards. +usbkbd_functions: + dd 12 + dd CloseKeyboard + dd SetKeyboardLights + +; for DEBUGF macro +include_debug_strings + +; for uninitialized data +section '.data' data readable writable align 16 diff --git a/kernel/trunk/drivers/usbstor.asm b/kernel/trunk/drivers/usbstor.asm new file mode 100644 index 0000000000..4ecdfcde46 --- /dev/null +++ b/kernel/trunk/drivers/usbstor.asm @@ -0,0 +1,1609 @@ +; standard driver stuff +format MS COFF + +DEBUG = 1 +DUMP_PACKETS = 0 + +; this is for DEBUGF macro from 'fdo.inc' +__DEBUG__ = 1 +__DEBUG_LEVEL__ = 1 + +include 'proc32.inc' +include 'imports.inc' +include 'fdo.inc' + +public START +public version + +; USB constants +DEVICE_DESCR_TYPE = 1 +CONFIG_DESCR_TYPE = 2 +STRING_DESCR_TYPE = 3 +INTERFACE_DESCR_TYPE = 4 +ENDPOINT_DESCR_TYPE = 5 +DEVICE_QUALIFIER_DESCR_TYPE = 6 + +CONTROL_PIPE = 0 +ISOCHRONOUS_PIPE = 1 +BULK_PIPE = 2 +INTERRUPT_PIPE = 3 + +; USB structures +virtual at 0 +config_descr: +.bLength db ? +.bDescriptorType db ? +.wTotalLength dw ? +.bNumInterfaces db ? +.bConfigurationValue db ? +.iConfiguration db ? +.bmAttributes db ? +.bMaxPower db ? +.sizeof: +end virtual + +virtual at 0 +interface_descr: +.bLength db ? +.bDescriptorType db ? +.bInterfaceNumber db ? +.bAlternateSetting db ? +.bNumEndpoints db ? +.bInterfaceClass db ? +.bInterfaceSubClass db ? +.bInterfaceProtocol db ? +.iInterface db ? +.sizeof: +end virtual + +virtual at 0 +endpoint_descr: +.bLength db ? +.bDescriptorType db ? +.bEndpointAddress db ? +.bmAttributes db ? +.wMaxPacketSize dw ? +.bInterval db ? +.sizeof: +end virtual + +; Mass storage protocol constants, USB layer +REQUEST_GETMAXLUN = 0xFE ; get max lun +REQUEST_BORESET = 0xFF ; bulk-only reset + +; Mass storage protocol structures, USB layer +; Sent from host to device in the first stage of an operation. +struc command_block_wrapper +{ +.Signature dd ? ; the constant 'USBC' +.Tag dd ? ; identifies response with request +.Length dd ? ; length of data-transport phase +.Flags db ? ; one of CBW_FLAG_* +CBW_FLAG_OUT = 0 +CBW_FLAG_IN = 80h +.LUN db ? ; addressed unit +.CommandLength db ? ; the length of the following field +.Command rb 16 +.sizeof: +} +virtual at 0 +command_block_wrapper command_block_wrapper +end virtual + +; Sent from device to host in the last stage of an operation. +struc command_status_wrapper +{ +.Signature dd ? ; the constant 'USBS' +.Tag dd ? ; identifies response with request +.LengthRest dd ? ; .Length - (size of data which were transferred) +.Status db ? ; one of CSW_STATUS_* +CSW_STATUS_OK = 0 +CSW_STATUS_FAIL = 1 +CSW_STATUS_FATAL = 2 +.sizeof: +} +virtual at 0 +command_status_wrapper command_status_wrapper +end virtual + +; Constants of SCSI layer +SCSI_REQUEST_SENSE = 3 +SCSI_INQUIRY = 12h +SCSI_READ_CAPACITY = 25h +SCSI_READ10 = 28h +SCSI_WRITE10 = 2Ah + +; Result of SCSI REQUEST SENSE command. +SENSE_UNKNOWN = 0 +SENSE_RECOVERED_ERROR = 1 +SENSE_NOT_READY = 2 +SENSE_MEDIUM_ERROR = 3 +SENSE_HARDWARE_ERROR = 4 +SENSE_ILLEGAL_REQUEST = 5 +SENSE_UNIT_ATTENTION = 6 +SENSE_DATA_PROTECT = 7 +SENSE_BLANK_CHECK = 8 +; 9 is vendor-specific +SENSE_COPY_ABORTED = 10 +SENSE_ABORTED_COMMAND = 11 +SENSE_EQUAL = 12 +SENSE_VOLUME_OVERFLOW = 13 +SENSE_MISCOMPARE = 14 +; 15 is reserved + +; Structures of SCSI layer +; Result of SCSI INQUIRY request. +struc inquiry_data +{ +.PeripheralDevice db ? ; lower 5 bits are PeripheralDeviceType + ; upper 3 bits are PeripheralQualifier +.RemovableMedium db ? ; upper bit is RemovableMedium + ; other bits are for compatibility +.Version db ? ; lower 3 bits are ANSI-Approved version + ; next 3 bits are ECMA version + ; upper 2 bits are ISO version +.ResponseDataFormat db ? ; lower 4 bits are ResponseDataFormat + ; bit 6 is TrmIOP + ; bit 7 is AENC +.AdditionalLength db ? + dw ? ; reserved +.Flags db ? +.VendorID rb 8 ; vendor ID, big-endian +.ProductID rb 16 ; product ID, big-endian +.ProductRevBE dd ? ; product revision, big-endian +.sizeof: +} +virtual at 0 +inquiry_data inquiry_data +end virtual + +struc sense_data +{ +.ErrorCode db ? ; lower 7 bits are error code: + ; 70h = current error, + ; 71h = deferred error + ; upper bit is InformationValid +.SegmentNumber db ? ; number of segment descriptor + ; for commands COPY [+VERIFY], COMPARE +.SenseKey db ? ; bits 0-3 are one of SENSE_* + ; bit 4 is reserved + ; bit 5 is IncorrectLengthIndicator + ; bits 6 and 7 are used by + ; sequential-access devices +.Information dd ? ; command-specific +.AdditionalLength db ? ; length of data starting here +.CommandInformation dd ? ; command-specific +.AdditionalSenseCode db ? ; \ more detailed error code +.AdditionalSenseQual db ? ; / standard has a large table of them +.FRUCode db ? ; which part of device has failed + ; (device-specific, not regulated) +.SenseKeySpecific rb 3 ; depends on SenseKey +.sizeof: +} +virtual at 0 +sense_data sense_data +end virtual + +; Device data +; USB Mass storage device has one or more logical units, identified by LUN, +; logical unit number. The highest value of LUN, that is, number of units +; minus 1, can be obtained via control request Get Max LUN. +virtual at 0 +usb_device_data: +.ConfigPipe dd ? ; configuration pipe +.OutPipe dd ? ; pipe for OUT bulk endpoint +.InPipe dd ? ; pipe for IN bulk endpoint +.MaxLUN dd ? ; maximum Logical Unit Number +.LogicalDevices dd ? ; pointer to array of usb_unit_data +; 1 for a connected USB device, 1 for each disk device +; the structure can be freed when .NumReferences decreases to zero +.NumReferences dd ? ; number of references +.ConfigRequest rb 8 ; buffer for configuration requests +.LengthRest dd ? ; Length - (size of data which were transferred) +; All requests to a given device are serialized, +; only one request to a given device can be processed at a time. +; The current request and all pending requests are organized in the following +; queue, the head being the current request. +; NB: the queue must be device-wide due to the protocol: +; data stage is not tagged (unlike command_*_wrapper), so the only way to know +; what request the data are associated with is to guarantee that only one +; request is processing at the time. +.RequestsQueue rd 2 +.QueueLock rd 3 ; protects .RequestsQueue +.InquiryData inquiry_data ; information about device +; data for the current request +.Command command_block_wrapper +.DeviceDisconnected db ? +.Status command_status_wrapper +.Sense sense_data +.sizeof: +end virtual + +; Information about one logical device. +virtual at 0 +usb_unit_data: +.Parent dd ? ; pointer to parent usb_device_data +.LUN db ? ; index in usb_device_data.LogicalDevices array +.DiskIndex db ? ; for name "usbhd" +.MediaPresent db ? + db ? ; alignment +.DiskDevice dd ? ; handle of disk device or NULL +.SectorSize dd ? ; sector size +; For some devices, the first request to the medium fails with 'unit not ready'. +; When the code sees this status, it retries the command several times. +; Two following variables track the retry count and total time for those; +; total time is currently used only for debug output. +.UnitReadyAttempts dd ? +.TimerTicks dd ? +.sizeof: +end virtual + +; This is the structure for items in the queue usb_device_data.RequestsQueue. +virtual at 0 +request_queue_item: +.Next dd ? ; next item in the queue +.Prev dd ? ; prev item in the queue +.ReqBuilder dd ? ; procedure to fill command_block_wrapper +.Buffer dd ? ; input or output data + ; (length is command_block_wrapper.Length) +.Callback dd ? ; procedure to call in the end of transfer +.UserData dd ? ; passed as-is to .Callback +; There are 3 possible stages of any request, one of them optional: +; command stage (host sends command_block_wrapper to device), +; optional data stage, +; status stage (device sends command_status_wrapper to host). +; Also, if a request fails, the code queues additional request +; SCSI_REQUEST_SENSE; sense_data from SCSI_REQUEST_SENSE +; contains some information about the error. +.Stage db ? +.sizeof: +end virtual + +section '.flat' code readable align 16 +; The start procedure. +proc START +virtual at esp + dd ? ; return address +.reason dd ? ; DRV_ENTRY or DRV_EXIT +end virtual +; 1. Test whether the procedure is called with the argument DRV_ENTRY. +; If not, return 0. + xor eax, eax ; initialize return value + cmp [.reason], 1 ; compare the argument + jnz .nothing +; 2. Initialize: we have one global mutex. + mov ecx, free_numbers_lock + call MutexInit +; 3. Register self as a USB driver. +; The name is my_driver = 'usbstor'; IOCTL interface is not supported; +; usb_functions is an offset of a structure with callback functions. + stdcall RegUSBDriver, my_driver, 0, usb_functions +; 4. Return the returned value of RegUSBDriver. +.nothing: + ret 4 +endp + +; Helper procedures to work with requests queue. + +; Add a request to the queue. Stdcall with 5 arguments. +proc queue_request + push ebx esi +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.device dd ? ; pointer to usb_device_data +.ReqBuilder dd ? ; request_queue_item.ReqBuilder +.Buffer dd ? ; request_queue_item.Buffer +.Callback dd ? ; request_queue_item.Callback +.UserData dd ? ; request_queue_item.UserData +end virtual +; 1. Allocate the memory for the request description. + push request_queue_item.sizeof + pop eax + call Kmalloc + test eax, eax + jnz @f + mov esi, nomemory + call SysMsgBoardStr + pop esi ebx + ret 20 +@@: +; 2. Fill user-provided parts of the request description. + push edi + xchg eax, ebx + lea esi, [.ReqBuilder+4] + lea edi, [ebx+request_queue_item.ReqBuilder] + movsd ; ReqBuilder + movsd ; Buffer + movsd ; Callback + movsd ; UserData + pop edi +; 3. Set stage to zero: not started. + mov [ebx+request_queue_item.Stage], 0 +; 4. Lock the queue. + mov esi, [.device] + lea ecx, [esi+usb_device_data.QueueLock] + call MutexLock +; 5. Insert the request to the tail of the queue. + add esi, usb_device_data.RequestsQueue + mov edx, [esi+request_queue_item.Prev] + mov [ebx+request_queue_item.Next], esi + mov [ebx+request_queue_item.Prev], edx + mov [edx+request_queue_item.Next], ebx + mov [esi+request_queue_item.Prev], ebx +; 6. Test whether the queue was empty +; and the request should be started immediately. + cmp [esi+request_queue_item.Next], ebx + jnz .unlock +; 8. If the step 6 shows that the request is the first in the queue, +; start it. + sub esi, usb_device_data.RequestsQueue + call setup_request + jmp .nothing +.unlock: + call MutexUnlock +; 9. Return. +.nothing: + pop esi ebx + ret 20 +endp + +; The current request is completed. Call the callback, +; remove the request from the queue, start the next +; request if there is one. +; esi points to usb_device_data +proc complete_request +; 1. Print common debug messages on fails. +if DEBUG + cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL + jb .normal + jz .fail + DEBUGF 1, 'K : Fatal error during execution of command %x\n', [esi+usb_device_data.Command.Command]:2 + jmp .normal +.fail: + DEBUGF 1, 'K : Command %x failed\n', [esi+usb_device_data.Command.Command]:2 +.normal: +end if +; 2. Get the current request. + mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] +; 3. Call the callback. + stdcall [ebx+request_queue_item.Callback], esi, [ebx+request_queue_item.UserData] +; 4. Lock the queue. + lea ecx, [esi+usb_device_data.QueueLock] + call MutexLock +; 5. Remove the request. + lea edx, [esi+usb_device_data.RequestsQueue] + mov eax, [ebx+request_queue_item.Next] + mov [eax+request_queue_item.Prev], edx + mov [edx+request_queue_item.Next], eax +; 6. Free the request memory. + push eax edx + xchg eax, ebx + call Kfree + pop edx ebx +; 7. If there is a next request, start processing. + cmp ebx, edx + jnz setup_request +; 8. Unlock the queue and return. + lea ecx, [esi+usb_device_data.QueueLock] + call MutexUnlock + ret +endp + +; Start processing the request. Called either by queue_request +; or when the previous request has been processed. +; Do not call directly, use queue_request. +; Must be called when queue is locked; unlocks the queue when returns. +proc setup_request + xor eax, eax +; 1. If DeviceDisconnected has been run, then all handles of pipes +; are invalid, so we must fail immediately. +; (That is why this function needs the locked queue: this +; guarantee that either DeviceDisconnected has been already run, or +; DeviceDisconnected will not return before the queue is unlocked.) + cmp [esi+usb_device_data.DeviceDisconnected], al + jnz .fatal +; 2. If the previous command has encountered a fatal error, +; perform reset recovery. + cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL + jb .norecovery +; 2a. Send Bulk-Only Mass Storage Reset command to config pipe. + lea edx, [esi+usb_device_data.ConfigRequest] + mov word [edx], (REQUEST_BORESET shl 8) + 21h ; class request + mov word [edx+6], ax ; length = 0 + stdcall USBControlTransferAsync, [esi+usb_device_data.ConfigPipe], edx, eax, eax, recovery_callback1, esi, eax +; 2b. Fail here = fatal error. + test eax, eax + jz .fatal +; 2c. Otherwise, unlock the queue and return. recovery_callback1 will continue processing. +.unlock_return: + lea ecx, [esi+usb_device_data.QueueLock] + call MutexUnlock + ret +.norecovery: +; 3. Send the command. Fail (no memory or device disconnected) = fatal error. +; Otherwise, go to 2c. + call request_stage1 + test eax, eax + jnz .unlock_return +.fatal: +; 4. Fatal error. Set status = FATAL, unlock the queue, complete the request. + mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL + lea ecx, [esi+usb_device_data.QueueLock] + call MutexUnlock + jmp complete_request +endp + +; Initiate USB transfer for the first stage of a request (send command). +proc request_stage1 + mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] +; 1. Set the stage to 1 = command stage. + inc [ebx+request_queue_item.Stage] +; 2. Generate the command. Zero-initialize and use the caller-provided proc. + lea edx, [esi+usb_device_data.Command] + xor eax, eax + mov [edx+command_block_wrapper.CommandLength], 12 + mov dword [edx+command_block_wrapper.Command], eax + mov dword [edx+command_block_wrapper.Command+4], eax + mov dword [edx+command_block_wrapper.Command+8], eax + mov dword [edx+command_block_wrapper.Command+12], eax + inc [edx+command_block_wrapper.Tag] + stdcall [ebx+request_queue_item.ReqBuilder], edx, [ebx+request_queue_item.UserData] +; 4. Initiate USB transfer. + lea edx, [esi+usb_device_data.Command] +if DUMP_PACKETS + DEBUGF 1,'K : USBSTOR out:' + mov eax, edx + mov ecx, command_block_wrapper.sizeof + call debug_dump + DEBUGF 1,'\n' +end if + stdcall USBNormalTransferAsync, [esi+usb_device_data.OutPipe], edx, command_block_wrapper.sizeof, request_callback1, esi, 0 + ret +endp + +if DUMP_PACKETS +proc debug_dump + test ecx, ecx + jz .done +.loop: + test ecx, 0Fh + jnz @f + DEBUGF 1,'\nK :' +@@: + DEBUGF 1,' %x',[eax]:2 + inc eax + dec ecx + jnz .loop +.done: + ret +endp +end if + +; Called when the Reset command is completed, +; either successfully or not. +proc recovery_callback1 +virtual at esp + dd ? ; return address +.pipe dd ? +.status dd ? +.buffer dd ? +.length dd ? +.calldata dd ? +end virtual + cmp [.status], 0 + jnz .error +; todo: reset pipes + push ebx esi + mov esi, [.calldata+8] + call request_stage1 + pop esi ebx + test eax, eax + jz .error + ret 20 +.error: + DEBUGF 1, 'K : error %d while resetting', [.status] + jmp request_callback1.common_error +endp + +; Called when the first stage of request is completed, +; either successfully or not. +proc request_callback1 +virtual at esp + dd ? ; return address +.pipe dd ? +.status dd ? +.buffer dd ? +.length dd ? +.calldata dd ? +end virtual +; 1. Initialize. + mov ecx, [.calldata] + mov eax, [.status] +; 2. Test for error. + test eax, eax + jnz .error +; No error. +; 3. Increment the stage. + mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next] + inc [edx+request_queue_item.Stage] +; 4. If there is no data, skip this stage. + cmp [ecx+usb_device_data.Command.Length], 0 + jz ..request_get_status +; 5. Initiate USB transfer. If this fails, go to the error handler. + mov eax, [ecx+usb_device_data.InPipe] + cmp [ecx+usb_device_data.Command.Flags], 0 + js @f + mov eax, [ecx+usb_device_data.OutPipe] +if DUMP_PACKETS + DEBUGF 1,'K : USBSTOR out:' + push eax ecx + mov eax, [edx+request_queue_item.Buffer] + mov ecx, [ecx+usb_device_data.Command.Length] + call debug_dump + pop ecx eax + DEBUGF 1,'\n' +end if +@@: + stdcall USBNormalTransferAsync, eax, [edx+request_queue_item.Buffer], [ecx+usb_device_data.Command.Length], request_callback2, ecx, 0 + test eax, eax + jz .error +; 6. Return. + ret 20 +.error: +; Error. +; 7. Print debug message and complete the request as failed. + DEBUGF 1,'K : error %d after %d bytes in request stage\n',eax,[.length] +.common_error: +; TODO: add recovery after STALL + mov ecx, [.calldata] + mov [ecx+usb_device_data.Status.Status], CSW_STATUS_FATAL + push ebx esi + mov esi, ecx + call complete_request + pop esi ebx + ret 20 +endp + +; Called when the second stage of request is completed, +; either successfully or not. +proc request_callback2 +virtual at esp + dd ? ; return address +.pipe dd ? +.status dd ? +.buffer dd ? +.length dd ? +.calldata dd ? +end virtual +if DUMP_PACKETS + mov eax, [.calldata] + mov eax, [eax+usb_device_data.InPipe] + cmp [.pipe], eax + jnz @f + DEBUGF 1,'K : USBSTOR in:' + push eax ecx + mov eax, [.buffer+8] + mov ecx, [.length+8] + call debug_dump + pop ecx eax + DEBUGF 1,'\n' +@@: +end if +; 1. Initialize. + mov ecx, [.calldata] + mov eax, [.status] +; 2. Test for error. + test eax, eax + jnz .error +; No error. +..request_get_status: +; 3. Increment the stage. + mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next] + inc [edx+request_queue_item.Stage] +; 4. Initiate USB transfer. If this fails, go to the error handler. + lea edx, [ecx+usb_device_data.Status] + stdcall USBNormalTransferAsync, [ecx+usb_device_data.InPipe], edx, command_status_wrapper.sizeof, request_callback3, ecx, 0 + test eax, eax + jz .error + ret 20 +.error: +; Error. +; 7. Print debug message and complete the request as failed. + DEBUGF 1,'K : error %d after %d bytes in data stage\n',eax,[.length] + jmp request_callback1.common_error +endp + +; Called when the third stage of request is completed, +; either successfully or not. +proc request_callback3 +virtual at esp + dd ? ; return address +.pipe dd ? +.status dd ? +.buffer dd ? +.length dd ? +.calldata dd ? +end virtual +if DUMP_PACKETS + DEBUGF 1,'K : USBSTOR in:' + mov eax, [.buffer] + mov ecx, [.length] + call debug_dump + DEBUGF 1,'\n' +end if +; 1. Initialize. + mov eax, [.status] +; 2. Test for error. + test eax, eax + jnz .transfer_error +; Transfer is OK. +; 3. Validate the status. Invalid status = fatal error. + push ebx esi + mov esi, [.calldata+8] + mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] + cmp [esi+usb_device_data.Status.Signature], 'USBS' + jnz .invalid + mov eax, [esi+usb_device_data.Command.Tag] + cmp [esi+usb_device_data.Status.Tag], eax + jnz .invalid + cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL + ja .invalid +; 4. The status block is valid. Check the status code. + jz .complete +; 5. If this command was not REQUEST_SENSE, copy status data to safe place. +; Otherwise, the original command has failed, so restore the fail status. + cmp byte [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE + jz .request_sense + mov eax, [esi+usb_device_data.Status.LengthRest] + mov [esi+usb_device_data.LengthRest], eax + cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL + jz .fail +.complete: + call complete_request +.nothing: + pop esi ebx + ret 20 +.request_sense: + mov [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL + jmp .complete +.invalid: +; 6. Invalid status block. Say error, set status to fatal and complete request. + push esi + mov esi, invresponse + call SysMsgBoardStr + pop esi + mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL + jmp .complete +.fail: +; 7. The command has failed. +; If this command was not REQUEST_SENSE, schedule the REQUEST_SENSE command +; to determine the reason of fail. Otherwise, assume that there is no error data. + cmp [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE + jz .fail_request_sense + mov [ebx+request_queue_item.ReqBuilder], request_sense_req + lea eax, [esi+usb_device_data.Sense] + mov [ebx+request_queue_item.Buffer], eax + call request_stage1 + test eax, eax + jnz .nothing +.fail_request_sense: + DEBUGF 1,'K : fail during REQUEST SENSE\n' + mov byte [esi+usb_device_data.Sense], 0 + jmp .complete +.transfer_error: +; TODO: add recovery after STALL + DEBUGF 1,'K : error %d after %d bytes in status stage\n',eax,[.length] + jmp request_callback1.common_error +endp + +; Builder for SCSI_REQUEST_SENSE request. +; edx = first argument = pointer to usb_device_data.Command, +; second argument = custom data given to queue_request (ignored). +proc request_sense_req + mov [edx+command_block_wrapper.Length], sense_data.sizeof + mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN + mov byte [edx+command_block_wrapper.Command+0], SCSI_REQUEST_SENSE + mov byte [edx+command_block_wrapper.Command+4], sense_data.sizeof + ret 8 +endp + +; This procedure is called when new mass-storage device is detected. +; It initializes the device. +; Technically, initialization implies sending several USB queries, +; so it is split in several procedures. The first is AddDevice, +; other are callbacks which will be called at some time in the future, +; when the device will respond. +; The general scheme: +; * AddDevice parses descriptors, opens pipes; if everything is ok, +; AddDevice sends REQUEST_GETMAXLUN with callback known_lun_callback; +; * known_lun_callback allocates memory for LogicalDevices and sends +; SCSI_TEST_UNIT_READY to all logical devices with test_unit_ready_callback; +; * test_unit_ready_callback checks whether the unit is ready; +; if not, it repeats the same request several times; +; if ok or there were too many attempts, it sends SCSI_INQUIRY with +; callback inquiry_callback; +; * inquiry_callback checks that a logical device is a block device +; and the unit was ready; if so, it notifies the kernel about new disk device. +proc AddDevice + push ebx esi +virtual at esp + rd 2 ; saved registers ebx, esi + dd ? ; return address +.pipe0 dd ? ; handle of the config pipe +.config dd ? ; pointer to config_descr +.interface dd ? ; pointer to interface_descr +end virtual +; 1. Check device type. Currently only SCSI-command-set Bulk-only devices +; are supported. +; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and +; bInterfaceProtocol are subsequent in interface_descr, just one +; memory reference is used for both. + mov esi, [.interface] + xor ebx, ebx + mov cx, word [esi+interface_descr.bInterfaceSubClass] +; 1b. For Mass-storage SCSI-command-set Bulk-only devices subclass must be 6 +; and protocol must be 50h. Check. + cmp cx, 0x5006 + jz .known +; There are devices with subclass 5 which use the same protocol 50h. +; The difference is not important for the code except for this test, +; so allow them to proceed also. + cmp cx, 0x5005 + jz .known +; 1c. If the device is unknown, print a message and go to 11c. + mov esi, unkdevice + call SysMsgBoardStr + jmp .nothing +; 1d. If the device uses known command set, print a message and continue +; configuring. +.known: + push esi + mov esi, okdevice + call SysMsgBoardStr + pop esi +; 2. Allocate memory for internal device data. +; 2a. Call the kernel. + mov eax, usb_device_data.sizeof + call Kmalloc +; 2b. Check return value. + test eax, eax + jnz @f +; 2c. If failed, say a message and go to 11c. + mov esi, nomemory + call SysMsgBoardStr + jmp .nothing +@@: +; 2d. If succeeded, zero the contents and continue configuring. + xchg ebx, eax ; ebx will point to usb_device_data + xor eax, eax + mov [ebx+usb_device_data.OutPipe], eax + mov [ebx+usb_device_data.InPipe], eax + mov [ebx+usb_device_data.MaxLUN], eax + mov [ebx+usb_device_data.LogicalDevices], eax + mov dword [ebx+usb_device_data.ConfigRequest], eax + mov dword [ebx+usb_device_data.ConfigRequest+4], eax + mov [ebx+usb_device_data.Status.Status], al + mov [ebx+usb_device_data.DeviceDisconnected], al +; 2e. There is one reference: a connected USB device. + inc eax + mov [ebx+usb_device_data.NumReferences], eax +; 2f. Save handle of configuration pipe for reset recovery. + mov eax, [.pipe0] + mov [ebx+usb_device_data.ConfigPipe], eax +; 2g. Save the interface number for configuration requests. + mov al, [esi+interface_descr.bInterfaceNumber] + mov [ebx+usb_device_data.ConfigRequest+4], al +; 2h. Initialize common fields in command wrapper. + mov [ebx+usb_device_data.Command.Signature], 'USBC' + mov [ebx+usb_device_data.Command.Tag], 'xxxx' +; 2i. Initialize requests queue. + lea eax, [ebx+usb_device_data.RequestsQueue] + mov [eax+request_queue_item.Next], eax + mov [eax+request_queue_item.Prev], eax + lea ecx, [ebx+usb_device_data.QueueLock] + call MutexInit +; Bulk-only mass storage devices use one OUT bulk endpoint for sending +; command/data and one IN bulk endpoint for receiving data/status. +; Look for those endpoints. +; 3. Get the upper bound of all descriptors' data. + mov edx, [.config] ; configuration descriptor + movzx ecx, [edx+config_descr.wTotalLength] + add edx, ecx +; 4. Loop over all descriptors until +; either end-of-data reached - this is fail +; or interface descriptor found - this is fail, all further data +; correspond to that interface +; or both endpoint descriptors found. +; 4a. Loop start: esi points to the interface descriptor, +.lookep: +; 4b. Get next descriptor. + movzx ecx, byte [esi] ; the first byte of all descriptors is length + add esi, ecx +; 4c. Check that at least two bytes are readable. The opposite is an error. + inc esi + cmp esi, edx + jae .errorep + dec esi +; 4d. Check that this descriptor is not interface descriptor. The opposite is +; an error. + cmp byte [esi+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE + jz .errorep +; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue +; the loop. + cmp byte [esi+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE + jnz .lookep +; 5. Check that the descriptor contains all required data and all data are +; readable. The opposite is an error. + cmp byte [esi+endpoint_descr.bLength], endpoint_descr.sizeof + jb .errorep + lea ecx, [esi+endpoint_descr.sizeof] + cmp ecx, edx + ja .errorep +; 6. Check that the endpoint is bulk endpoint. The opposite is an error. + mov cl, [esi+endpoint_descr.bmAttributes] + and cl, 3 + cmp cl, BULK_PIPE + jnz .errorep +; 7. Get the direction of this endpoint. + movzx ecx, [esi+endpoint_descr.bEndpointAddress] + shr ecx, 7 +; 8. Test whether a pipe for this direction is already opened. If so, continue +; the loop. + cmp [ebx+usb_device_data.OutPipe+ecx*4], 0 + jnz .lookep +; 9. Open pipe for this endpoint. +; 9a. Save registers. + push ecx edx +; 9b. Load parameters from the descriptor. + movzx ecx, [esi+endpoint_descr.bEndpointAddress] + movzx edx, [esi+endpoint_descr.wMaxPacketSize] + movzx eax, [esi+endpoint_descr.bInterval] ; not used for USB1, may be important for USB2 +; 9c. Call the kernel. + stdcall USBOpenPipe, [ebx+usb_device_data.ConfigPipe], ecx, edx, BULK_PIPE, eax +; 9d. Restore registers. + pop edx ecx +; 9e. Check result. If failed, go to 11b. + test eax, eax + jz .free +; 9f. Save result. + mov [ebx+usb_device_data.OutPipe+ecx*4], eax +; 10. Test whether the second pipe is already opened. If not, continue loop. + xor ecx, 1 + cmp [ebx+usb_device_data.OutPipe+ecx*4], 0 + jz .lookep + jmp .created +; 11. An error occured during processing endpoint descriptor. +.errorep: +; 11a. Print a message. + DEBUGF 1,'K : error: invalid endpoint descriptor\n' +.free: +; 11b. Free the allocated usb_device_data. + xchg eax, ebx + call Kfree +.nothing: +; 11c. Return an error. + xor eax, eax + jmp .return +.created: +; 12. Pipes are opened. Send GetMaxLUN control request. + lea eax, [ebx+usb_device_data.ConfigRequest] + mov byte [eax], 0A1h ; class request from interface + mov byte [eax+1], REQUEST_GETMAXLUN + mov byte [eax+6], 1 ; transfer 1 byte + lea ecx, [ebx+usb_device_data.MaxLUN] +if DUMP_PACKETS + DEBUGF 1,'K : GETMAXLUN: %x %x %x %x %x %x %x %x\n',[eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2 +end if + stdcall USBControlTransferAsync, [ebx+usb_device_data.ConfigPipe], eax, ecx, 1, known_lun_callback, ebx, 0 +; 13. Return with pointer to device data as returned value. + xchg eax, ebx +.return: + pop esi ebx + ret 12 +endp + +; This function is called when REQUEST_GETMAXLUN is done, +; either successful or unsuccessful. +proc known_lun_callback + push ebx esi +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.pipe dd ? +.status dd ? +.buffer dd ? +.length dd ? +.calldata dd ? +end virtual +; 1. Check the status. If the request failed, assume that MaxLUN is zero. + mov ebx, [.calldata] + mov eax, [.status] + test eax, eax + jz @f + DEBUGF 1, 'K : GETMAXLUN failed with status %d, assuming zero\n', eax + mov [ebx+usb_device_data.MaxLUN], 0 +@@: +; 2. Allocate the memory for logical devices. + mov eax, [ebx+usb_device_data.MaxLUN] + inc eax + DEBUGF 1,'K : %d logical unit(s)\n',eax + imul eax, usb_unit_data.sizeof + push ebx + call Kmalloc + pop ebx +; If failed, print a message and do nothing. + test eax, eax + jnz @f + mov esi, nomemory + call SysMsgBoardStr + pop esi ebx + ret 20 +@@: + mov [ebx+usb_device_data.LogicalDevices], eax +; 3. Initialize logical devices and initiate TEST_UNIT_READY request. + xchg esi, eax + xor ecx, ecx +.looplun: + mov [esi+usb_unit_data.Parent], ebx + mov [esi+usb_unit_data.LUN], cl + xor eax, eax + mov [esi+usb_unit_data.MediaPresent], al + mov [esi+usb_unit_data.DiskDevice], eax + mov [esi+usb_unit_data.SectorSize], eax + mov [esi+usb_unit_data.UnitReadyAttempts], eax + push ecx + call GetTimerTicks + mov [esi+usb_unit_data.TimerTicks], eax + stdcall queue_request, ebx, test_unit_ready_req, 0, test_unit_ready_callback, esi + pop ecx + inc ecx + add esi, usb_unit_data.sizeof + cmp ecx, [ebx+usb_device_data.MaxLUN] + jbe .looplun +; 4. Return. + pop esi ebx + ret 20 +endp + +; Builder for SCSI INQUIRY request. +; edx = first argument = pointer to usb_device_data.Command, +; second argument = custom data given to queue_request. +proc inquiry_req + mov eax, [esp+8] + mov al, [eax+usb_unit_data.LUN] + mov [edx+command_block_wrapper.Length], inquiry_data.sizeof + mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN + mov [edx+command_block_wrapper.LUN], al + mov byte [edx+command_block_wrapper.Command+0], SCSI_INQUIRY + mov byte [edx+command_block_wrapper.Command+4], inquiry_data.sizeof + ret 8 +endp + +; Called when SCSI INQUIRY request is completed. +proc inquiry_callback +; 1. Check the status. + mov ecx, [esp+4] + cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK + jnz .fail +; 2. The command has completed successfully. +; Print a message showing device type, ignore anything but block devices. + mov al, [ecx+usb_device_data.InquiryData.PeripheralDevice] + and al, 1Fh + DEBUGF 1,'K : peripheral device type is %x\n',al + test al, al + jnz .nothing + DEBUGF 1,'K : direct-access mass storage device detected\n' +; 3. We have found a new disk device. Increment number of references. + lock inc [ecx+usb_device_data.NumReferences] +; Unfortunately, we are now in the context of the USB thread, +; so we can't notify the kernel immediately: it would try to do something +; with a new disk, those actions would be synchronous and would require +; waiting for results of USB requests, but we need to exit this callback +; to allow the USB thread to continue working and handling those requests. +; 4. Thus, create a temporary kernel thread which would do it. + mov edx, [esp+8] + push ebx ecx + push 51 + pop eax + push 1 + pop ebx + mov ecx, new_disk_thread + ; edx = parameter + int 0x40 + pop ecx ebx + cmp eax, -1 + jnz .nothing +; on error, reverse step 3 + lock dec [ecx+usb_device_data.NumReferences] +.nothing: + ret 8 +.fail: +; 4. The command has failed. Print a message and do nothing. + push esi + mov esi, inquiry_fail + call SysMsgBoardStr + pop esi + ret 8 +endp + +; Builder for SCSI TEST_UNIT_READY request. +; edx = first argument = pointer to usb_device_data.Command, +; second argument = custom data given to queue_request. +proc test_unit_ready_req + mov eax, [esp+8] + mov al, [eax+usb_unit_data.LUN] + mov [edx+command_block_wrapper.Length], 0 + mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN + mov [edx+command_block_wrapper.LUN], al + ret 8 +endp + +; Called when SCSI TEST_UNIT_READY request is completed. +proc test_unit_ready_callback +virtual at esp + dd ? ; return address +.device dd ? +.calldata dd ? +end virtual +; 1. Check the status. + mov ecx, [.device] + mov edx, [.calldata] + cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK + jnz .fail +; 2. The command has completed successfully, +; possibly after some repetitions. Print a debug message showing +; number and time of those. Remember that media is ready and go to 4. + DEBUGF 1,'K : media is ready\n' + call GetTimerTicks + sub eax, [edx+usb_unit_data.TimerTicks] + DEBUGF 1,'K : %d attempts, %d ticks\n',[edx+usb_unit_data.UnitReadyAttempts],eax + inc [edx+usb_unit_data.MediaPresent] + jmp .inquiry +.fail: +; 3. The command has failed. +; Retry the same request up to 3 times with 10ms delay; +; if limit of retries is not reached, exit from the function. +; Otherwise, go to 4. + inc [edx+usb_unit_data.UnitReadyAttempts] + cmp [edx+usb_unit_data.UnitReadyAttempts], 3 + jz @f + push ecx edx esi + push 10 + pop esi + call Sleep + pop esi edx ecx + stdcall queue_request, ecx, test_unit_ready_req, 0, test_unit_ready_callback, edx + ret 8 +@@: + DEBUGF 1,'K : media not ready\n' +.inquiry: +; 4. initiate INQUIRY request. + lea eax, [ecx+usb_device_data.InquiryData] + stdcall queue_request, ecx, inquiry_req, eax, inquiry_callback, edx + ret 8 +endp + +; Temporary thread for initial actions with a new disk device. +proc new_disk_thread + sub esp, 32 +virtual at esp +.name rb 32 ; device name +.param dd ? ; contents of edx at the moment of int 0x40/eax=51 + dd ? ; stack segment +end virtual +; We are ready to notify the kernel about a new disk device. + mov esi, [.param] +; 1. Generate name. +; 1a. Find a free index. + mov ecx, free_numbers_lock + call MutexLock + xor eax, eax +@@: + bsf edx, [free_numbers+eax] + jnz @f + add eax, 4 + cmp eax, 4*4 + jnz @b + call MutexUnlock + push esi + mov esi, noindex + call SysMsgBoardStr + pop esi + jmp .drop_reference +@@: +; 1b. Mark the index as busy. + btr [free_numbers+eax], edx + lea eax, [eax*8+edx] + push eax + call MutexUnlock + pop eax +; 1c. Generate a name of the form "usbhd" in the stack. + mov dword [esp], 'usbh' + lea edi, [esp+5] + mov byte [edi-1], 'd' + push eax + push -'0' + push 10 + pop ecx +@@: + cdq + div ecx + push edx + test eax, eax + jnz @b +@@: + pop eax + add al, '0' + stosb + jnz @b + pop ecx + mov edx, esp +; 3d. Store the index in usb_unit_data to free it later. + mov [esi+usb_unit_data.DiskIndex], cl +; 4. Notify the kernel about a new disk. +; 4a. Add a disk. +; stdcall queue_request, ecx, read_capacity_req, eax, read_capacity_callback, eax + stdcall DiskAdd, disk_functions, edx, esi, 0 + mov ebx, eax +; 4b. If it failed, release the index and do nothing. + test eax, eax + jz .free_index +; 4c. Notify the kernel that a media is present. + stdcall DiskMediaChanged, eax, 1 +; 5. Lock the requests queue, check that device is not disconnected, +; store the disk handle, unlock the requests queue. + mov ecx, [esi+usb_unit_data.Parent] + add ecx, usb_device_data.QueueLock + call MutexLock + cmp byte [ecx+usb_device_data.DeviceDisconnected-usb_device_data.QueueLock], 0 + jnz .disconnected + mov [esi+usb_unit_data.DiskDevice], ebx + call MutexUnlock + jmp .exit +.disconnected: + call MutexUnlock + stdcall disk_close, ebx + jmp .exit +.free_index: + mov ecx, free_numbers_lock + call MutexLock + movzx eax, [esi+usb_unit_data.DiskIndex] + bts [free_numbers], eax + call MutexUnlock +.drop_reference: + mov esi, [esi+usb_unit_data.Parent] + lock dec [esi+usb_device_data.NumReferences] + jnz .exit + mov eax, [esi+usb_device_data.LogicalDevices] + call Kfree + xchg eax, esi + call Kfree +.exit: + or eax, -1 + int 0x40 +endp + +; This function is called when the device is disconnected. +proc DeviceDisconnected + push ebx esi +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.device dd ? +end virtual +; 1. Say a message. + mov esi, disconnectmsg + call SysMsgBoardStr +; 2. Lock the requests queue, set .DeviceDisconnected to 1, +; unlock the requests queue. +; Locking is required for synchronization with queue_request: +; all USB callbacks are executed in the same thread and are +; synchronized automatically, but queue_request can be running +; from any thread which wants to do something with a filesystem. +; Without locking, it would be possible that queue_request has +; been started, has checked that device is not yet disconnected, +; then DeviceDisconnected completes and all handles become invalid, +; then queue_request tries to use them. + mov esi, [.device] + lea ecx, [esi+usb_device_data.QueueLock] + call MutexLock + mov [esi+usb_device_data.DeviceDisconnected], 1 + call MutexUnlock +; 3. Drop one reference to the structure and check whether +; that was the last reference. + lock dec [esi+usb_device_data.NumReferences] + jz .free +; 4. If not, there are some additional references due to disk devices; +; notify the kernel that those disks are deleted. +; Note that new disks cannot be added while we are looping here, +; because new_disk_thread checks for .DeviceDisconnected. + mov ebx, [esi+usb_device_data.MaxLUN] + mov esi, [esi+usb_device_data.LogicalDevices] + inc ebx +.diskdel: + mov eax, [esi+usb_unit_data.DiskDevice] + test eax, eax + jz @f + stdcall DiskDel, eax +@@: + add esi, usb_unit_data.sizeof + dec ebx + jnz .diskdel +; In this case, some operations with those disks are still possible, +; so we can't do anything more now. disk_close will take care of the rest. +.return: + pop esi ebx + ret 4 +; 5. If there are no disk devices, free all resources which were allocated. +.free: + mov eax, [esi+usb_device_data.LogicalDevices] + test eax, eax + jz @f + call Kfree +@@: + xchg eax, esi + call Kfree + jmp .return +endp + +; Disk functions. +DISK_STATUS_OK = 0 ; success +DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable +DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters +DISK_STATUS_NO_MEDIA = 2 ; no media present +DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data + +; Called when all operations with the given disk are done. +proc disk_close + push ebx esi +virtual at esp + rd 2 ; saved registers + dd ? ; return address +.userdata dd ? +end virtual + mov esi, [.userdata] + mov ecx, free_numbers_lock + call MutexLock + movzx eax, [esi+usb_unit_data.DiskIndex] + bts [free_numbers], eax + call MutexUnlock + mov esi, [esi+usb_unit_data.Parent] + lock dec [esi+usb_device_data.NumReferences] + jnz .nothing + mov eax, [esi+usb_device_data.LogicalDevices] + call Kfree + xchg eax, esi + call Kfree +.nothing: + pop esi ebx + ret 4 +endp + +; Returns sector size, capacity and flags of the media. +proc disk_querymedia stdcall uses ebx esi edi, \ + userdata:dword, mediainfo:dword +; 1. Create event for waiting. + xor esi, esi + xor ecx, ecx + call CreateEvent + test eax, eax + jz .generic_fail + push eax + push edx + push ecx + push 0 + push 0 +virtual at ebp-.localsize +.locals: +; two following dwords are the output of READ_CAPACITY +.LastLBABE dd ? +.SectorSizeBE dd ? +.Status dd ? +; two following dwords identify an event +.event_code dd ? +.event dd ? + rd 3 ; saved registers +.localsize = $ - .locals + dd ? ; saved ebp + dd ? ; return address +.userdata dd ? +.mediainfo dd ? +end virtual +; 2. Initiate SCSI READ_CAPACITY request. + mov eax, [userdata] + mov ecx, [eax+usb_unit_data.Parent] + mov edx, esp + stdcall queue_request, ecx, read_capacity_req, edx, read_capacity_callback, edx +; 3. Wait for event. This destroys it. + mov eax, [.event] + mov ebx, [.event_code] + call WaitEvent +; 4. Get the status and results. + pop ecx + bswap ecx ; .LastLBA + pop edx + bswap edx ; .SectorSize + pop eax ; .Status +; 5. If the request has completed successfully, store results. + test eax, eax + jnz @f + DEBUGF 1,'K : sector size is %d, last sector is %d\n',edx,ecx + mov ebx, [mediainfo] + mov [ebx], eax ; flags = 0 + mov [ebx+4], edx ; sectorsize + add ecx, 1 + adc eax, 0 + mov [ebx+8], ecx + mov [ebx+12], eax ; capacity + mov eax, [userdata] + mov [eax+usb_unit_data.SectorSize], edx + xor eax, eax +@@: +; 6. Restore the stack and return. + pop ecx + pop ecx + ret +.generic_fail: + or eax, -1 + ret +endp + +; Builder for SCSI READ_CAPACITY request. +; edx = first argument = pointer to usb_device_data.Command, +; second argument = custom data given to queue_request, +; pointer to disk_querymedia.locals. +proc read_capacity_req + mov eax, [esp+8] + mov eax, [eax+disk_querymedia.userdata-disk_querymedia.locals] + mov al, [eax+usb_unit_data.LUN] + mov [edx+command_block_wrapper.Length], 8 + mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN + mov [edx+command_block_wrapper.LUN], al + mov byte [edx+command_block_wrapper.Command+0], SCSI_READ_CAPACITY + ret 8 +endp + +; Called when SCSI READ_CAPACITY request is completed. +proc read_capacity_callback +; Transform the status to return value of disk_querymedia +; and set the event. + mov ecx, [esp+4] + xor eax, eax + cmp [ecx+usb_device_data.Status.Status], al + jz @f + or eax, -1 +@@: + mov ecx, [esp+8] + mov [ecx+disk_querymedia.Status-disk_querymedia.locals], eax + push ebx esi edi + mov eax, [ecx+disk_querymedia.event-disk_querymedia.locals] + mov ebx, [ecx+disk_querymedia.event_code-disk_querymedia.locals] + xor edx, edx + xor esi, esi + call RaiseEvent + pop edi esi ebx + ret 8 +endp + +disk_write: + mov al, SCSI_WRITE10 + jmp disk_read_write + +disk_read: + mov al, SCSI_READ10 + +; Reads from the device or writes to the device. +proc disk_read_write stdcall uses ebx esi edi, \ + userdata:dword, buffer:dword, startsector:qword, numsectors:dword +; 1. Initialize. + push eax ; .command + mov eax, [userdata] + mov eax, [eax+usb_unit_data.SectorSize] + push eax ; .SectorSize + push 0 ; .processed + mov eax, [numsectors] + mov eax, [eax] +; 2. The transfer length for SCSI_{READ,WRITE}10 commands can not be greater +; than 0xFFFF, so split the request to slices with <= 0xFFFF sectors. +max_sectors_at_time = 0xFFFF +.split: + push eax ; .length_rest + cmp eax, max_sectors_at_time + jb @f + mov eax, max_sectors_at_time +@@: + sub [esp], eax + push eax ; .length_cur +; 3. startsector must fit in 32 bits, otherwise abort the request. + cmp dword [startsector+4], 0 + jnz .generic_fail +; 4. Create event for waiting. + xor esi, esi + xor ecx, ecx + call CreateEvent + test eax, eax + jz .generic_fail + push eax ; .event + push edx ; .event_code + push ecx ; .status +virtual at ebp-.localsize +.locals: +.status dd ? +.event_code dd ? +.event dd ? +.length_cur dd ? +.length_rest dd ? +.processed dd ? +.SectorSize dd ? +.command db ? + rb 3 + rd 3 ; saved registers +.localsize = $ - .locals + dd ? ; saved ebp + dd ? ; return address +.userdata dd ? +.buffer dd ? +.startsector dq ? +.numsectors dd ? +end virtual +; 5. Initiate SCSI READ10 or WRITE10 request. + mov eax, [userdata] + mov ecx, [eax+usb_unit_data.Parent] + stdcall queue_request, ecx, read_write_req, [buffer], read_write_callback, esp +; 6. Wait for event. This destroys it. + mov eax, [.event] + mov ebx, [.event_code] + call WaitEvent +; 7. Get the status. If the operation has failed, abort. + pop eax ; .status + pop ecx ecx ; cleanup .event_code, .event + pop ecx ; .length_cur + test eax, eax + jnz .return +; 8. Otherwise, continue the loop started at step 2. + add dword [startsector], ecx + adc dword [startsector+4], eax + imul ecx, [.SectorSize] + add [buffer], ecx + pop eax + test eax, eax + jnz .split + push eax +.return: +; 9. Restore the stack, store .processed to [numsectors], return. + pop ecx ; .length_rest + pop ecx ; .processed + mov edx, [numsectors] + mov [edx], ecx + pop ecx ; .SectorSize + pop ecx ; .command + ret +.generic_fail: + or eax, -1 + pop ecx ; .length_cur + jmp .return +endp + +; Builder for SCSI READ10 or WRITE10 request. +; edx = first argument = pointer to usb_device_data.Command, +; second argument = custom data given to queue_request, +; pointer to disk_read_write.locals. +proc read_write_req + mov eax, [esp+8] + mov ecx, [eax+disk_read_write.userdata-disk_read_write.locals] + mov cl, [ecx+usb_unit_data.LUN] + mov [edx+command_block_wrapper.LUN], cl + mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals] + imul ecx, [eax+disk_read_write.SectorSize-disk_read_write.locals] + mov [edx+command_block_wrapper.Length], ecx + mov cl, [eax+disk_read_write.command-disk_read_write.locals] + mov [edx+command_block_wrapper.Flags], CBW_FLAG_OUT + cmp cl, SCSI_READ10 + jnz @f + mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN +@@: + mov byte [edx+command_block_wrapper.Command], cl + mov ecx, dword [eax+disk_read_write.startsector-disk_read_write.locals] + bswap ecx + mov dword [edx+command_block_wrapper.Command+2], ecx + mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals] + xchg cl, ch + mov word [edx+command_block_wrapper.Command+7], cx + ret 8 +endp + +; Called when SCSI READ10 or WRITE10 request is completed. +proc read_write_callback +; 1. Initialize. + push ebx esi edi +virtual at esp + rd 3 ; saved registers + dd ? ; return address +.device dd ? +.calldata dd ? +end virtual + mov ecx, [.device] + mov esi, [.calldata] +; 2. Get the number of sectors which were read. +; If the status is OK or FAIL, the field .LengthRest is valid. +; Otherwise, it is invalid, so assume zero sectors. + xor eax, eax + cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_FAIL + ja .sectors_calculated + mov eax, [ecx+usb_device_data.LengthRest] + xor edx, edx + div [esi+disk_read_write.SectorSize-disk_read_write.locals] + test edx, edx + jz @f + inc eax +@@: + mov edx, eax + mov eax, [esi+disk_read_write.length_cur-disk_read_write.locals] + sub eax, edx + jae .sectors_calculated + xor eax, eax +.sectors_calculated: +; 3. Increase the total number of processed sectors. + add [esi+disk_read_write.processed-disk_read_write.locals], eax +; 4. Set status to OK if all sectors were read, to ERROR otherwise. + cmp eax, [esi+disk_read_write.length_cur-disk_read_write.locals] + setz al + movzx eax, al + dec eax + mov [esi+disk_read_write.status-disk_read_write.locals], eax +; 5. Set the event. + mov eax, [esi+disk_read_write.event-disk_read_write.locals] + mov ebx, [esi+disk_read_write.event_code-disk_read_write.locals] + xor edx, edx + xor esi, esi + call RaiseEvent +; 6. Return. + pop edi esi ebx + ret 8 +endp + +; strings +my_driver db 'usbstor',0 +disconnectmsg db 'K : USB mass storage device disconnected',13,10,0 +nomemory db 'K : no memory',13,10,0 +unkdevice db 'K : unknown mass storage device',13,10,0 +okdevice db 'K : USB mass storage device detected',13,10,0 +transfererror db 'K : USB transfer error, disabling mass storage',13,10,0 +invresponse db 'K : invalid response from mass storage device',13,10,0 +fatalerr db 'K : mass storage device reports fatal error',13,10,0 +inquiry_fail db 'K : INQUIRY command failed',13,10,0 +;read_capacity_fail db 'K : READ CAPACITY command failed',13,10,0 +;read_fail db 'K : READ command failed',13,10,0 +noindex db 'K : failed to generate disk name',13,10,0 + +; Exported variable: kernel API version. +align 4 +version dd 50005h +; Structure with callback functions. +usb_functions: + dd usb_functions_end - usb_functions + dd AddDevice + dd DeviceDisconnected +usb_functions_end: + +disk_functions: + dd disk_functions_end - disk_functions + dd disk_close + dd 0 ; closemedia + dd disk_querymedia + dd disk_read + dd disk_write + dd 0 ; flush + dd 0 ; adjust_cache_size: use default cache +disk_functions_end: + +free_numbers_lock rd 3 +; 128 devices should be enough for everybody +free_numbers dd -1, -1, -1, -1 + +; for DEBUGF macro +include_debug_strings + +; for uninitialized data +section '.data' data readable writable align 16 diff --git a/kernel/trunk/kernel.asm b/kernel/trunk/kernel.asm index facc438ec4..c1e331696e 100644 --- a/kernel/trunk/kernel.asm +++ b/kernel/trunk/kernel.asm @@ -805,6 +805,8 @@ end if stdcall load_driver, szVidintel + call usb_init + ; SET PRELIMINARY WINDOW STACK AND POSITIONS mov esi, boot_windefs diff --git a/kernel/trunk/kernel32.inc b/kernel/trunk/kernel32.inc index 7920a91769..e0efa79d24 100644 --- a/kernel/trunk/kernel32.inc +++ b/kernel/trunk/kernel32.inc @@ -220,6 +220,9 @@ include "gui/skincode.inc" include "bus/pci/pci32.inc" +; USB functions +include "bus/usb/init.inc" + ; Floppy drive controller include "blkdev/fdc.inc"