From 00f370ce61f903ba8697709d58dc459fd65579f5 Mon Sep 17 00:00:00 2001 From: YouSheng Date: Tue, 21 Jul 2009 14:56:03 +0800 Subject: [PATCH 2/4] Apply smartq patch --- .gitignore | 3 + Makefile | 6 +- arch/arm/Makefile | 4 + arch/arm/boot/.gitignore | 3 + arch/arm/configs/hhs3c6410_4_3_defconfig | 1592 ++++ arch/arm/configs/hhs3c6410_7_defconfig | 1592 ++++ arch/arm/mach-s3c6400/Makefile | 4 +- arch/arm/mach-s3c6400/leds-s3c6400.c | 2 +- arch/arm/mach-s3c6400/leds.c | 12 +- arch/arm/mach-s3c6400/mach-smdk6400.c | 68 +- arch/arm/mach-s3c6410/Makefile | 4 +- arch/arm/mach-s3c6410/clock.c | 22 +- arch/arm/mach-s3c6410/leds-s3c6410.c | 2 +- arch/arm/mach-s3c6410/leds.c | 12 +- arch/arm/mach-s3c6410/mach-smdk6410.c | 228 +- arch/arm/mach-s3c6410/pwm/pwm-s3c6410.c | 5 +- arch/arm/plat-s3c24xx/devs.c | 11 + arch/arm/plat-s3c24xx/gpio.c | 21 +- arch/arm/plat-s3c64xx/Makefile | 2 +- arch/arm/plat-s3c64xx/irq-pl192.c | 94 +- arch/arm/plat-s3c64xx/pm.c | 80 +- arch/arm/plat-s3c64xx/s3c64xx-cpufreq.c | 7 +- arch/arm/vfp/entry.S | 8 +- arch/arm/vfp/vfp.h | 6 +- arch/arm/vfp/vfphw.S | 96 +- arch/arm/vfp/vfpinstr.h | 6 +- arch/arm/vfp/vfpmodule.c | 164 +- drivers/Makefile | 2 +- drivers/bluetooth/Kconfig | 11 +- drivers/bluetooth/bcm203x.c | 9 +- drivers/bluetooth/bfusb.c | 13 +- drivers/bluetooth/bluecard_cs.c | 10 +- drivers/bluetooth/bpa10x.c | 17 +- drivers/bluetooth/bt3c_cs.c | 136 +- drivers/bluetooth/btsdio.c | 6 +- drivers/bluetooth/btuart_cs.c | 134 +- drivers/bluetooth/btusb.c | 482 ++- drivers/bluetooth/dtl1_cs.c | 74 +- drivers/bluetooth/hci_bcsp.c | 62 +- drivers/bluetooth/hci_ldisc.c | 24 +- drivers/bluetooth/hci_usb.c | 44 +- drivers/bluetooth/hci_usb.h | 26 +- drivers/bluetooth/hci_vhci.c | 16 +- drivers/char/Kconfig | 13 + drivers/char/Makefile | 4 +- drivers/char/apm-emulation.c | 8 + drivers/char/hhtech_gpio.c | 883 +++ drivers/input/keyboard/gpio_keys.c | 158 +- drivers/input/keyboard/s3c-keypad.c | 9 +- drivers/input/keyboard/s3c-keypad.h | 37 +- drivers/input/mousedev.c | 3 + drivers/input/touchscreen/s3c-ts.c | 90 +- drivers/misc/Kconfig | 5 + drivers/misc/Makefile | 1 + drivers/misc/smartq5_encrypt/Makefile | 3 + drivers/misc/smartq5_encrypt/lib_Crypto.h | 1426 ++++ drivers/misc/smartq5_encrypt/smartq5_encrypt.c | 117 + drivers/mmc/card/block.c | 13 + drivers/mmc/core/sdio_cis.c | 4 + drivers/mmc/host/s3c-hsmmc.c | 58 +- drivers/net/dm9000.c | 13 +- drivers/net/wireless/Kconfig | 22 + drivers/net/wireless/Makefile | 2 + drivers/net/wireless/libertas/Makefile | 2 +- drivers/net/wireless/libertas/cmd.c | 6 + drivers/net/wireless/libertas/cmdresp.c | 5 +- drivers/net/wireless/libertas/ioctl.c | 1046 +++ drivers/net/wireless/libertas/main.c | 8 +- drivers/net/wireless/libertas/scan.c | 4 +- drivers/net/wireless/libertas/wext.c | 151 +- drivers/net/wireless/libertas/wext.h | 41 + drivers/net/wireless/marvell8686/Makefile | 15 + drivers/net/wireless/marvell8686/README | 1587 ++++ drivers/net/wireless/marvell8686/host.h | 349 + drivers/net/wireless/marvell8686/hostcmd.h | 1052 +++ drivers/net/wireless/marvell8686/if_sdio.c | 1379 ++++ drivers/net/wireless/marvell8686/if_sdio.h | 97 + drivers/net/wireless/marvell8686/include.h | 42 + drivers/net/wireless/marvell8686/os_defs.h | 42 + drivers/net/wireless/marvell8686/os_headers.h | 69 + drivers/net/wireless/marvell8686/os_macros.h | 165 + drivers/net/wireless/marvell8686/os_timers.h | 83 + drivers/net/wireless/marvell8686/release_version.h | 5 + drivers/net/wireless/marvell8686/sbi.h | 111 + drivers/net/wireless/marvell8686/sdio.h | 112 + drivers/net/wireless/marvell8686/wlan_11d.c | 893 +++ drivers/net/wireless/marvell8686/wlan_11d.h | 110 + drivers/net/wireless/marvell8686/wlan_cmd.c | 2501 ++++++ drivers/net/wireless/marvell8686/wlan_cmdresp.c | 1595 ++++ drivers/net/wireless/marvell8686/wlan_debug.c | 281 + drivers/net/wireless/marvell8686/wlan_decl.h | 109 + drivers/net/wireless/marvell8686/wlan_defs.h | 623 ++ drivers/net/wireless/marvell8686/wlan_dev.h | 468 ++ drivers/net/wireless/marvell8686/wlan_fops.c | 217 + drivers/net/wireless/marvell8686/wlan_fw.c | 532 ++ drivers/net/wireless/marvell8686/wlan_join.c | 2031 +++++ drivers/net/wireless/marvell8686/wlan_join.h | 84 + drivers/net/wireless/marvell8686/wlan_main.c | 1359 ++++ drivers/net/wireless/marvell8686/wlan_proc.c | 213 + drivers/net/wireless/marvell8686/wlan_rx.c | 367 + drivers/net/wireless/marvell8686/wlan_scan.c | 3226 ++++++++ drivers/net/wireless/marvell8686/wlan_scan.h | 284 + drivers/net/wireless/marvell8686/wlan_thread.h | 58 + drivers/net/wireless/marvell8686/wlan_tx.c | 286 + drivers/net/wireless/marvell8686/wlan_types.h | 1050 +++ drivers/net/wireless/marvell8686/wlan_version.h | 18 + drivers/net/wireless/marvell8686/wlan_wext.c | 8045 ++++++++++++++++++++ drivers/net/wireless/marvell8686/wlan_wext.h | 406 + drivers/net/wireless/marvell8686/wlan_wmm.c | 1666 ++++ drivers/net/wireless/marvell8686/wlan_wmm.h | 111 + drivers/rtc/rtc-s3c.c | 18 +- drivers/serial/s3c6400.c | 10 +- drivers/usb/core/driver.c | 11 +- drivers/usb/gadget/Kconfig | 6 +- drivers/usb/gadget/s3c-udc-otg-hs.c | 2 +- drivers/usb/host/ohci-s3c2410.c | 61 +- drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c | 49 +- drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h | 2 +- drivers/usb/host/s3c-otg/s3c-otg-isr.c | 10 + drivers/usb/host/s3c-otg/s3c-otg-oci.c | 2 +- drivers/usb/host/s3c-otg/s3c-otg-roothub.c | 12 +- drivers/usb/misc/Kconfig | 9 + drivers/usb/misc/Makefile | 1 + drivers/usb/serial/option.c | 442 +- drivers/video/Kconfig | 10 + drivers/video/s3c/Makefile | 2 + drivers/video/s3c/colorbar.c | 423 + drivers/video/s3c/s3cfb.c | 49 +- drivers/video/s3c/s3cfb.h | 5 +- drivers/video/s3c/s3cfb_a070vw04.c | 263 + drivers/video/s3c/s3cfb_fimd4x.c | 27 +- drivers/video/s3c/s3cfb_td043mtex.c | 279 + drivers/watchdog/s3c2410_wdt.c | 15 + fs/Kconfig | 7 + fs/Makefile | 6 + fs/aufs/Kconfig | 283 + fs/aufs/Makefile | 70 + fs/aufs/aufs.h | 118 + fs/aufs/br_fuse.c | 80 + fs/aufs/br_nfs.c | 390 + fs/aufs/br_xfs.c | 74 + fs/aufs/branch.c | 1036 +++ fs/aufs/branch.h | 443 ++ fs/aufs/cpup.c | 1138 +++ fs/aufs/cpup.h | 95 + fs/aufs/dcsub.c | 250 + fs/aufs/dcsub.h | 54 + fs/aufs/debug.c | 539 ++ fs/aufs/debug.h | 337 + fs/aufs/dentry.c | 960 +++ fs/aufs/dentry.h | 398 + fs/aufs/dinfo.c | 436 ++ fs/aufs/dir.c | 558 ++ fs/aufs/dir.h | 154 + fs/aufs/dlgt.c | 112 + fs/aufs/export.c | 809 ++ fs/aufs/f_op.c | 813 ++ fs/aufs/file.c | 806 ++ fs/aufs/file.h | 262 + fs/aufs/finfo.c | 186 + fs/aufs/getattr.c | 133 + fs/aufs/hin_or_dlgt.c | 736 ++ fs/aufs/hinode.h | 184 + fs/aufs/hinotify.c | 1146 +++ fs/aufs/i_op.c | 954 +++ fs/aufs/i_op_add.c | 757 ++ fs/aufs/i_op_del.c | 566 ++ fs/aufs/i_op_ren.c | 1261 +++ fs/aufs/iinfo.c | 284 + fs/aufs/inode.c | 430 ++ fs/aufs/inode.h | 593 ++ fs/aufs/misc.c | 325 + fs/aufs/misc.h | 244 + fs/aufs/module.c | 295 + fs/aufs/module.h | 81 + fs/aufs/opts.c | 1622 ++++ fs/aufs/opts.h | 269 + fs/aufs/plink.c | 364 + fs/aufs/robr.c | 117 + fs/aufs/sbinfo.c | 223 + fs/aufs/super.c | 1032 +++ fs/aufs/super.h | 579 ++ fs/aufs/sysaufs.c | 752 ++ fs/aufs/sysaufs.h | 211 + fs/aufs/sysrq.c | 296 + fs/aufs/vdir.c | 941 +++ fs/aufs/vfsub.c | 682 ++ fs/aufs/vfsub.h | 600 ++ fs/aufs/wbr_policy.c | 702 ++ fs/aufs/whout.c | 1162 +++ fs/aufs/whout.h | 134 + fs/aufs/wkq.c | 385 + fs/aufs/wkq.h | 128 + fs/aufs/xino.c | 1276 ++++ fs/yaffs2/Kconfig | 156 + fs/yaffs2/Makefile | 10 + fs/yaffs2/devextras.h | 199 + fs/yaffs2/moduleconfig.h | 65 + fs/yaffs2/yaffs_checkptrw.c | 405 + fs/yaffs2/yaffs_checkptrw.h | 35 + fs/yaffs2/yaffs_ecc.c | 331 + fs/yaffs2/yaffs_ecc.h | 44 + fs/yaffs2/yaffs_fs.c | 2393 ++++++ fs/yaffs2/yaffs_getblockinfo.h | 34 + fs/yaffs2/yaffs_guts.c | 7676 +++++++++++++++++++ fs/yaffs2/yaffs_guts.h | 906 +++ fs/yaffs2/yaffs_mtdif.c | 241 + fs/yaffs2/yaffs_mtdif.h | 32 + fs/yaffs2/yaffs_mtdif1.c | 369 + fs/yaffs2/yaffs_mtdif1.h | 28 + fs/yaffs2/yaffs_mtdif2.c | 248 + fs/yaffs2/yaffs_mtdif2.h | 29 + fs/yaffs2/yaffs_nand.c | 135 + fs/yaffs2/yaffs_nand.h | 44 + fs/yaffs2/yaffs_nandemul2k.h | 39 + fs/yaffs2/yaffs_packedtags1.c | 52 + fs/yaffs2/yaffs_packedtags1.h | 37 + fs/yaffs2/yaffs_packedtags2.c | 208 + fs/yaffs2/yaffs_packedtags2.h | 43 + fs/yaffs2/yaffs_qsort.c | 160 + fs/yaffs2/yaffs_qsort.h | 23 + fs/yaffs2/yaffs_tagscompat.c | 547 ++ fs/yaffs2/yaffs_tagscompat.h | 41 + fs/yaffs2/yaffs_tagsvalidity.c | 28 + fs/yaffs2/yaffs_tagsvalidity.h | 24 + fs/yaffs2/yaffsinterface.h | 21 + fs/yaffs2/yportenv.h | 202 + include/asm-arm/arch-s3c2410/gpio.h | 1 + include/asm-arm/arch-s3c2410/hardware.h | 4 +- include/asm-arm/arch-s3c2410/hsmmc.h | 5 +- include/asm-arm/arch-s3c2410/regs-s3c6410-clock.h | 262 + include/asm-arm/arch-s3c2410/reserved_mem.h | 2 +- include/asm-arm/arch-s3c2410/system.h | 3 +- include/asm-arm/plat-s3c24xx/devs.h | 2 + include/asm-arm/plat-s3c64xx/hhtech_gpio.h | 64 + include/asm-arm/vfp.h | 36 +- include/asm-arm/vfpmacros.h | 20 +- include/linux/aufs_type.h | 127 + include/linux/gpio_keys.h | 2 + kernel/power/main.c | 38 +- sound/soc/codecs/Kconfig | 6 +- sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8987.c | 1330 ++++ sound/soc/codecs/wm8987.h | 110 + sound/soc/s3c64xx/Kconfig | 25 +- sound/soc/s3c64xx/Makefile | 2 + sound/soc/s3c64xx/s3c6410-i2s-v32.c | 4 +- sound/soc/s3c64xx/smdk6410_wm8987.c | 623 ++ 248 files changed, 89139 insertions(+), 813 deletions(-) create mode 100644 arch/arm/configs/hhs3c6410_4_3_defconfig create mode 100644 arch/arm/configs/hhs3c6410_7_defconfig create mode 100644 drivers/char/hhtech_gpio.c create mode 100644 drivers/misc/smartq5_encrypt/Makefile create mode 100644 drivers/misc/smartq5_encrypt/lib_Crypto.h create mode 100644 drivers/misc/smartq5_encrypt/smartq5_encrypt.c create mode 100644 drivers/net/wireless/libertas/ioctl.c create mode 100644 drivers/net/wireless/marvell8686/Makefile create mode 100644 drivers/net/wireless/marvell8686/README create mode 100644 drivers/net/wireless/marvell8686/host.h create mode 100644 drivers/net/wireless/marvell8686/hostcmd.h create mode 100644 drivers/net/wireless/marvell8686/if_sdio.c create mode 100644 drivers/net/wireless/marvell8686/if_sdio.h create mode 100644 drivers/net/wireless/marvell8686/include.h create mode 100644 drivers/net/wireless/marvell8686/os_defs.h create mode 100644 drivers/net/wireless/marvell8686/os_headers.h create mode 100644 drivers/net/wireless/marvell8686/os_macros.h create mode 100644 drivers/net/wireless/marvell8686/os_timers.h create mode 100644 drivers/net/wireless/marvell8686/release_version.h create mode 100644 drivers/net/wireless/marvell8686/sbi.h create mode 100644 drivers/net/wireless/marvell8686/sdio.h create mode 100644 drivers/net/wireless/marvell8686/wlan_11d.c create mode 100644 drivers/net/wireless/marvell8686/wlan_11d.h create mode 100644 drivers/net/wireless/marvell8686/wlan_cmd.c create mode 100644 drivers/net/wireless/marvell8686/wlan_cmdresp.c create mode 100644 drivers/net/wireless/marvell8686/wlan_debug.c create mode 100644 drivers/net/wireless/marvell8686/wlan_decl.h create mode 100644 drivers/net/wireless/marvell8686/wlan_defs.h create mode 100644 drivers/net/wireless/marvell8686/wlan_dev.h create mode 100644 drivers/net/wireless/marvell8686/wlan_fops.c create mode 100644 drivers/net/wireless/marvell8686/wlan_fw.c create mode 100644 drivers/net/wireless/marvell8686/wlan_join.c create mode 100644 drivers/net/wireless/marvell8686/wlan_join.h create mode 100644 drivers/net/wireless/marvell8686/wlan_main.c create mode 100644 drivers/net/wireless/marvell8686/wlan_proc.c create mode 100644 drivers/net/wireless/marvell8686/wlan_rx.c create mode 100644 drivers/net/wireless/marvell8686/wlan_scan.c create mode 100644 drivers/net/wireless/marvell8686/wlan_scan.h create mode 100644 drivers/net/wireless/marvell8686/wlan_thread.h create mode 100644 drivers/net/wireless/marvell8686/wlan_tx.c create mode 100644 drivers/net/wireless/marvell8686/wlan_types.h create mode 100644 drivers/net/wireless/marvell8686/wlan_version.h create mode 100644 drivers/net/wireless/marvell8686/wlan_wext.c create mode 100644 drivers/net/wireless/marvell8686/wlan_wext.h create mode 100644 drivers/net/wireless/marvell8686/wlan_wmm.c create mode 100644 drivers/net/wireless/marvell8686/wlan_wmm.h create mode 100644 drivers/video/s3c/colorbar.c create mode 100644 drivers/video/s3c/s3cfb_a070vw04.c create mode 100644 drivers/video/s3c/s3cfb_td043mtex.c create mode 100644 fs/aufs/Kconfig create mode 100644 fs/aufs/Makefile create mode 100644 fs/aufs/aufs.h create mode 100644 fs/aufs/br_fuse.c create mode 100644 fs/aufs/br_nfs.c create mode 100644 fs/aufs/br_xfs.c create mode 100644 fs/aufs/branch.c create mode 100644 fs/aufs/branch.h create mode 100644 fs/aufs/cpup.c create mode 100644 fs/aufs/cpup.h create mode 100644 fs/aufs/dcsub.c create mode 100644 fs/aufs/dcsub.h create mode 100644 fs/aufs/debug.c create mode 100644 fs/aufs/debug.h create mode 100644 fs/aufs/dentry.c create mode 100644 fs/aufs/dentry.h create mode 100644 fs/aufs/dinfo.c create mode 100644 fs/aufs/dir.c create mode 100644 fs/aufs/dir.h create mode 100644 fs/aufs/dlgt.c create mode 100644 fs/aufs/export.c create mode 100644 fs/aufs/f_op.c create mode 100644 fs/aufs/file.c create mode 100644 fs/aufs/file.h create mode 100644 fs/aufs/finfo.c create mode 100644 fs/aufs/getattr.c create mode 100644 fs/aufs/hin_or_dlgt.c create mode 100644 fs/aufs/hinode.h create mode 100644 fs/aufs/hinotify.c create mode 100644 fs/aufs/i_op.c create mode 100644 fs/aufs/i_op_add.c create mode 100644 fs/aufs/i_op_del.c create mode 100644 fs/aufs/i_op_ren.c create mode 100644 fs/aufs/iinfo.c create mode 100644 fs/aufs/inode.c create mode 100644 fs/aufs/inode.h create mode 100644 fs/aufs/misc.c create mode 100644 fs/aufs/misc.h create mode 100644 fs/aufs/module.c create mode 100644 fs/aufs/module.h create mode 100644 fs/aufs/opts.c create mode 100644 fs/aufs/opts.h create mode 100644 fs/aufs/plink.c create mode 100644 fs/aufs/robr.c create mode 100644 fs/aufs/sbinfo.c create mode 100644 fs/aufs/super.c create mode 100644 fs/aufs/super.h create mode 100644 fs/aufs/sysaufs.c create mode 100644 fs/aufs/sysaufs.h create mode 100644 fs/aufs/sysrq.c create mode 100644 fs/aufs/vdir.c create mode 100644 fs/aufs/vfsub.c create mode 100644 fs/aufs/vfsub.h create mode 100644 fs/aufs/wbr_policy.c create mode 100644 fs/aufs/whout.c create mode 100644 fs/aufs/whout.h create mode 100644 fs/aufs/wkq.c create mode 100644 fs/aufs/wkq.h create mode 100644 fs/aufs/xino.c create mode 100644 fs/yaffs2/Kconfig create mode 100644 fs/yaffs2/Makefile create mode 100644 fs/yaffs2/devextras.h create mode 100644 fs/yaffs2/moduleconfig.h create mode 100644 fs/yaffs2/yaffs_checkptrw.c create mode 100644 fs/yaffs2/yaffs_checkptrw.h create mode 100644 fs/yaffs2/yaffs_ecc.c create mode 100644 fs/yaffs2/yaffs_ecc.h create mode 100644 fs/yaffs2/yaffs_fs.c create mode 100644 fs/yaffs2/yaffs_getblockinfo.h create mode 100644 fs/yaffs2/yaffs_guts.c create mode 100644 fs/yaffs2/yaffs_guts.h create mode 100644 fs/yaffs2/yaffs_mtdif.c create mode 100644 fs/yaffs2/yaffs_mtdif.h create mode 100644 fs/yaffs2/yaffs_mtdif1.c create mode 100644 fs/yaffs2/yaffs_mtdif1.h create mode 100644 fs/yaffs2/yaffs_mtdif2.c create mode 100644 fs/yaffs2/yaffs_mtdif2.h create mode 100644 fs/yaffs2/yaffs_nand.c create mode 100644 fs/yaffs2/yaffs_nand.h create mode 100644 fs/yaffs2/yaffs_nandemul2k.h create mode 100644 fs/yaffs2/yaffs_packedtags1.c create mode 100644 fs/yaffs2/yaffs_packedtags1.h create mode 100644 fs/yaffs2/yaffs_packedtags2.c create mode 100644 fs/yaffs2/yaffs_packedtags2.h create mode 100644 fs/yaffs2/yaffs_qsort.c create mode 100644 fs/yaffs2/yaffs_qsort.h create mode 100644 fs/yaffs2/yaffs_tagscompat.c create mode 100644 fs/yaffs2/yaffs_tagscompat.h create mode 100644 fs/yaffs2/yaffs_tagsvalidity.c create mode 100644 fs/yaffs2/yaffs_tagsvalidity.h create mode 100644 fs/yaffs2/yaffsinterface.h create mode 100644 fs/yaffs2/yportenv.h create mode 100644 include/asm-arm/arch-s3c2410/regs-s3c6410-clock.h create mode 100644 include/asm-arm/plat-s3c64xx/hhtech_gpio.h create mode 100644 include/linux/aufs_type.h create mode 100644 sound/soc/codecs/wm8987.c create mode 100644 sound/soc/codecs/wm8987.h create mode 100644 sound/soc/s3c64xx/smdk6410_wm8987.c diff --git a/.gitignore b/.gitignore index 8d14531..7320219 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ cscope.* *.orig *.rej + +*.order + diff --git a/Makefile b/Makefile index 21bd152..e63ae19 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ VERSION = 2 PATCHLEVEL = 6 SUBLEVEL = 24 EXTRAVERSION = .7 -EXTRAVERSION += $(shell echo ''; \ - sed -n '/[dr][e][vl]-*[0-9]-*[0-9]-*[0-9]/p' Changelog | \ - awk 'BEGIN {FS=":"} {last=$$1} END {print last}') +#EXTRAVERSION += $(shell echo ''; \ +# sed -n '/[dr][e][vl]-*[0-9]-*[0-9]-*[0-9]/p' Changelog | \ +# awk 'BEGIN {FS=":"} {last=$$1} END {print last}') NAME = Err Metey! A Heury Beelge-a Ret! # *DOCUMENTATION* diff --git a/arch/arm/Makefile b/arch/arm/Makefile index d19cd22..4073783 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -76,7 +76,11 @@ tune-$(CONFIG_CPU_SA110) :=-mtune=strongarm110 tune-$(CONFIG_CPU_SA1100) :=-mtune=strongarm1100 tune-$(CONFIG_CPU_XSCALE) :=$(call cc-option,-mtune=xscale,-mtune=strongarm110) -Wa,-mcpu=xscale tune-$(CONFIG_CPU_XSC3) :=$(call cc-option,-mtune=xscale,-mtune=strongarm110) -Wa,-mcpu=xscale +ifeq ($(CONFIG_PLAT_S3C64XX),y) +tune-$(CONFIG_CPU_V6) :=$(call cc-option,-mtune=arm1176jzf-s,-mtune=strongarm) +else tune-$(CONFIG_CPU_V6) :=$(call cc-option,-mtune=arm1136j-s,-mtune=strongarm) +endif ifeq ($(CONFIG_AEABI),y) CFLAGS_ABI :=-mabi=aapcs-linux -mno-thumb-interwork diff --git a/arch/arm/boot/.gitignore b/arch/arm/boot/.gitignore index ce1c5ff..1e6d563 100644 --- a/arch/arm/boot/.gitignore +++ b/arch/arm/boot/.gitignore @@ -3,3 +3,6 @@ zImage xipImage bootpImage uImage + +rootfs.* + diff --git a/arch/arm/configs/hhs3c6410_4_3_defconfig b/arch/arm/configs/hhs3c6410_4_3_defconfig new file mode 100644 index 0000000..aa1758a --- /dev/null +++ b/arch/arm/configs/hhs3c6410_4_3_defconfig @@ -0,0 +1,1592 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.24.7 +# Tue Jun 16 10:48:11 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_GENERIC_GPIO=y +# CONFIG_GENERIC_TIME is not set +# CONFIG_GENERIC_CLOCKEVENTS is not set +CONFIG_MMU=y +CONFIG_NO_IOPORT=y +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=13 +# CONFIG_CGROUPS is not set +# CONFIG_FAIR_GROUP_SCHED is not set +# CONFIG_SYSFS_DEPRECATED is not set +# CONFIG_RELAY is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +# CONFIG_UID16 is not set +CONFIG_SYSCTL_SYSCALL=y +# CONFIG_KALLSYMS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +# CONFIG_BUG is not set +# CONFIG_ELF_CORE is not set +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_EVENTFD=y +# CONFIG_SHMEM is not set +# CONFIG_VM_EVENT_COUNTERS is not set +# CONFIG_SLUB_DEBUG is not set +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +CONFIG_TINY_SHMEM=y +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +# CONFIG_IOSCHED_AS is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_CO285 is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_MXC is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +CONFIG_ARCH_S3C2410=y +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +CONFIG_PLAT_S3C64XX=y +CONFIG_S3C64XX_DMA=y +# CONFIG_S3C64XX_ADC is not set +CONFIG_PLAT_S3C=y + +# +# Boot options +# +# CONFIG_S3C_BOOT_WATCHDOG is not set +CONFIG_S3C_BOOT_ERROR_RESET=y + +# +# Power management +# +# CONFIG_S3C2410_PM_DEBUG is not set +# CONFIG_S3C2410_PM_CHECK is not set +CONFIG_S3C_LOWLEVEL_UART_PORT=0 +CONFIG_MACH_SMDK=y + +# +# S3C2400 Machines +# + +# +# S3C2410 Machines +# +# CONFIG_ARCH_SMDK2410 is not set +# CONFIG_ARCH_H1940 is not set +# CONFIG_MACH_N30 is not set +# CONFIG_ARCH_BAST is not set +# CONFIG_MACH_OTOM is not set +# CONFIG_MACH_AML_M5900 is not set +# CONFIG_MACH_VR1000 is not set +# CONFIG_MACH_QT2410 is not set + +# +# S3C2412 Machines +# +# CONFIG_MACH_SMDK2413 is not set +# CONFIG_MACH_SMDK2412 is not set +# CONFIG_MACH_VSTMS is not set + +# +# S3C2440 Machines +# +# CONFIG_MACH_ANUBIS is not set +# CONFIG_MACH_OSIRIS is not set +# CONFIG_MACH_RX3715 is not set +# CONFIG_ARCH_S3C2440 is not set +# CONFIG_MACH_NEXCODER_2440 is not set + +# +# S3C2442 Machines +# + +# +# S3C2443 Machines +# +# CONFIG_MACH_SMDK2443 is not set + +# +# S3C2450 Machines +# +# CONFIG_MACH_SMDK2450 is not set + +# +# S3C2416 Machines +# +# CONFIG_MACH_SMDK2416 is not set + +# +# S3C6400 Machines +# +# CONFIG_MACH_SMDK6400 is not set + +# +# S3C6410 Machines +# +CONFIG_MACH_SMDK6410=y +# CONFIG_MACH_SMDK6430 is not set +CONFIG_S3C6410_PDFW=m +CONFIG_S3C6410_PDFW_PROC=y +CONFIG_S3C6410_KDPMD=m +CONFIG_CPU_S3C6410=y +CONFIG_S3C6410_PM=y + +# +# S5PC100 Machines +# +# CONFIG_MACH_SMDKC100 is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_V6=y +CONFIG_CPU_32v6K=y +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +CONFIG_ISA=y +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +# CONFIG_TICK_ONESHOT is not set +CONFIG_PREEMPT=y +CONFIG_NO_IDLE_HZ=y +CONFIG_HZ=200 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +# CONFIG_ARCH_DISCONTIGMEM_ENABLE is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +# CONFIG_SPARSEMEM_STATIC is not set +# CONFIG_SPARSEMEM_VMEMMAP_ENABLE is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="console=ttySAC0,115200n8 root=/dev/mmcblk0p1 rootwait splash" +# CONFIG_XIP_KERNEL is not set +CONFIG_KEXEC=y + +# +# CPU Frequency scaling +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_GOV_USERSPACE=m +CONFIG_CPU_FREQ_GOV_ONDEMAND=m +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=m + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_BINFMT_AOUT is not set +CONFIG_BINFMT_MISC=m + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_LEGACY is not set +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND_UP_POSSIBLE=y +CONFIG_SUSPEND=y +CONFIG_APM_EMULATION=m + +# +# DVFS support +# +CONFIG_S3C64XX_DVFS=m + +# +# Networking +# +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=m +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +# CONFIG_NET_KEY is not set +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +CONFIG_INET_LRO=m +CONFIG_INET_DIAG=m +CONFIG_INET_TCP_DIAG=m +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IP_VS is not set +# CONFIG_IPV6 is not set +# CONFIG_INET6_XFRM_TUNNEL is not set +# CONFIG_INET6_TUNNEL is not set +# CONFIG_NETWORK_SECMARK is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_DEBUG is not set + +# +# Core Netfilter Configuration +# +# CONFIG_NETFILTER_NETLINK is not set +# CONFIG_NF_CONNTRACK_ENABLED is not set +# CONFIG_NF_CONNTRACK is not set +CONFIG_NETFILTER_XTABLES=m +# CONFIG_NETFILTER_XT_TARGET_CLASSIFY is not set +# CONFIG_NETFILTER_XT_TARGET_MARK is not set +# CONFIG_NETFILTER_XT_TARGET_NFQUEUE is not set +# CONFIG_NETFILTER_XT_TARGET_NFLOG is not set +# CONFIG_NETFILTER_XT_TARGET_TCPMSS is not set +# CONFIG_NETFILTER_XT_MATCH_COMMENT is not set +# CONFIG_NETFILTER_XT_MATCH_DCCP is not set +# CONFIG_NETFILTER_XT_MATCH_DSCP is not set +# CONFIG_NETFILTER_XT_MATCH_ESP is not set +# CONFIG_NETFILTER_XT_MATCH_LENGTH is not set +# CONFIG_NETFILTER_XT_MATCH_LIMIT is not set +# CONFIG_NETFILTER_XT_MATCH_MAC is not set +# CONFIG_NETFILTER_XT_MATCH_MARK is not set +# CONFIG_NETFILTER_XT_MATCH_MULTIPORT is not set +# CONFIG_NETFILTER_XT_MATCH_PKTTYPE is not set +# CONFIG_NETFILTER_XT_MATCH_QUOTA is not set +# CONFIG_NETFILTER_XT_MATCH_REALM is not set +# CONFIG_NETFILTER_XT_MATCH_SCTP is not set +# CONFIG_NETFILTER_XT_MATCH_STATISTIC is not set +# CONFIG_NETFILTER_XT_MATCH_STRING is not set +# CONFIG_NETFILTER_XT_MATCH_TCPMSS is not set +# CONFIG_NETFILTER_XT_MATCH_TIME is not set +# CONFIG_NETFILTER_XT_MATCH_U32 is not set +# CONFIG_NETFILTER_XT_MATCH_HASHLIMIT is not set + +# +# IP: Netfilter Configuration +# +# CONFIG_IP_NF_QUEUE is not set +# CONFIG_IP_NF_IPTABLES is not set +# CONFIG_IP_NF_ARPTABLES is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set +CONFIG_NET_SCH_FIFO=y + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_IRDA is not set +CONFIG_BT=m +CONFIG_BT_L2CAP=m +CONFIG_BT_SCO=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m + +# +# Bluetooth device drivers +# +CONFIG_BT_HCIBTUSB=m +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set +# CONFIG_AF_RXRPC is not set + +# +# Wireless +# +CONFIG_CFG80211=m +CONFIG_NL80211=y +CONFIG_WIRELESS_EXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_RCSIMPLE=y +CONFIG_MAC80211_LEDS=y +# CONFIG_MAC80211_DEBUG is not set +CONFIG_IEEE80211=m +# CONFIG_IEEE80211_DEBUG is not set +CONFIG_IEEE80211_CRYPT_WEP=m +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_IEEE80211_SOFTMAC is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=m +# CONFIG_MTD is not set +# CONFIG_PARPORT is not set +# CONFIG_PNP is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=m +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_BLK_DEV_RAM_BLOCKSIZE=1024 +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +CONFIG_SMARTQ5_ENCRYPT=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=m +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=m +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +CONFIG_BLK_DEV_SR=m +# CONFIG_BLK_DEV_SR_VENDOR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_NETDEVICES_MULTIQUEUE is not set +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +CONFIG_TUN=m +# CONFIG_VETH is not set +# CONFIG_ARCNET is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=m +# CONFIG_AX88796 is not set +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_SMC is not set +# CONFIG_SMC91X is not set +CONFIG_DM9000=m +# CONFIG_SMC911X is not set +# CONFIG_NET_VENDOR_RACAL is not set +# CONFIG_AT1700 is not set +# CONFIG_DEPCA is not set +# CONFIG_HP100 is not set +# CONFIG_NET_ISA is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_NET_PCI is not set +# CONFIG_B44 is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_TR is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +CONFIG_WLAN_80211=y +# CONFIG_LIBERTAS is not set +CONFIG_MARVELL_8686_SDIO=m +CONFIG_MARVELL_8686_PROC_FS=y +CONFIG_MARVELL_8686_DEBUG=y +# CONFIG_USB_ZD1201 is not set +# CONFIG_RTL8187 is not set +# CONFIG_P54_COMMON is not set +# CONFIG_HOSTAP is not set +# CONFIG_B43 is not set +# CONFIG_B43LEGACY is not set +# CONFIG_RT2X00 is not set + +# +# USB Network Adapters +# +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_USBNET=m +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_BELKIN=y +CONFIG_USB_ARMLINUX=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +# CONFIG_WAN is not set +CONFIG_PPP=m +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_MPPE=m +# CONFIG_PPPOE is not set +# CONFIG_PPPOL2TP is not set +# CONFIG_SLIP is not set +CONFIG_SLHC=m +# CONFIG_SHAPER is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=m +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_MOUSEDEV_SCREEN_X=800 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=480 +# CONFIG_INPUT_JOYDEV is not set +# CONFIG_INPUT_TSDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYPAD_S3C=m +CONFIG_INPUT_MOUSE=y +# CONFIG_MOUSE_PS2 is not set +# CONFIG_MOUSE_SERIAL is not set +# CONFIG_MOUSE_APPLETOUCH is not set +# CONFIG_MOUSE_INPORT is not set +# CONFIG_MOUSE_LOGIBM is not set +# CONFIG_MOUSE_PC110PAD is not set +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_MOUSE_GPIO is not set +# CONFIG_INPUT_JOYSTICK is not set +CONFIG_INPUT_TABLET=y +# CONFIG_TABLET_USB_ACECAD is not set +# CONFIG_TABLET_USB_AIPTEK is not set +# CONFIG_TABLET_USB_GTCO is not set +# CONFIG_TABLET_USB_KBTAB is not set +# CONFIG_TABLET_USB_WACOM is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_S3C=y +# CONFIG_TOUCHSCREEN_NEW is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_UCB1400 is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +CONFIG_INPUT_UINPUT=m + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +CONFIG_VT_HW_CONSOLE_BINDING=y +# CONFIG_SERIAL_NONSTANDARD is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_S3C2410 is not set +CONFIG_SERIAL_S3C6400=y +CONFIG_SERIAL_S3C64XX_CONSOLE=y +# CONFIG_SERIAL_S3C64XX_HS_UART is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +# CONFIG_LEGACY_PTYS is not set +# CONFIG_IPMI_HANDLER is not set +# CONFIG_HW_RANDOM is not set +# CONFIG_NVRAM is not set +# CONFIG_S3C_ADC is not set +# CONFIG_DTLK is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_DEVPORT=y +CONFIG_S3C_MEM=y +CONFIG_LCD_4=y +# CONFIG_LCD_7 is not set +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=m + +# +# I2C Algorithms +# +# CONFIG_I2C_ALGOBIT is not set +# CONFIG_I2C_ALGOPCF is not set +# CONFIG_I2C_ALGOPCA is not set + +# +# I2C Hardware Bus support +# +# CONFIG_I2C_ELEKTOR is not set +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PARPORT_LIGHT is not set +CONFIG_I2C_S3C64XX=y +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_TINY_USB is not set +# CONFIG_I2C_PCA_ISA is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_SENSORS_DS1337 is not set +# CONFIG_SENSORS_DS1374 is not set +# CONFIG_DS1682 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set + +# +# SPI support +# +# CONFIG_SPI is not set +# CONFIG_SPI_MASTER is not set +# CONFIG_W1 is not set +CONFIG_POWER_SUPPLY=m +# CONFIG_POWER_SUPPLY_DEBUG is not set +CONFIG_PDA_POWER=m +CONFIG_APM_POWER=m +# CONFIG_BATTERY_DS2760 is not set +CONFIG_HWMON=m +# CONFIG_HWMON_VID is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_WATCHDOG=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_S3C2410_WATCHDOG=y + +# +# ISA-based Watchdog Cards +# +# CONFIG_PCWATCHDOG is not set +# CONFIG_MIXCOMWD is not set +# CONFIG_WDT is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set + +# +# Sonics Silicon Backplane +# +CONFIG_SSB_POSSIBLE=y +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_SM501 is not set + +# +# Multimedia devices +# +# CONFIG_VIDEO_DEV is not set +# CONFIG_DVB_CORE is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +CONFIG_VIDEO_OUTPUT_CONTROL=m +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_SYS_FOPS is not set +CONFIG_FB_DEFERRED_IO=y +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_S3C=y +# CONFIG_FB_S3C_LTE480WV is not set +# CONFIG_FB_S3C_LTV350QV is not set +# CONFIG_FB_S3C_LTS222QV is not set +# CONFIG_FB_S3C_A070VW04 is not set +CONFIG_FB_S3C_TD043MTEX=y +CONFIG_FB_S3C_BPP=y +# CONFIG_FB_S3C_BPP_8 is not set +CONFIG_FB_S3C_BPP_16=y +# CONFIG_FB_S3C_BPP_24 is not set +# CONFIG_FB_S3C_BPP_32 is not set +CONFIG_FB_S3C_NUM=1 +# CONFIG_FB_S3C_VIRTUAL_SCREEN is not set +# CONFIG_FB_S3C_DOUBLE_BUFFERING is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_S3C2410 is not set +# CONFIG_FB_VIRTUAL is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=m +CONFIG_BACKLIGHT_CLASS_DEVICE=m +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_SMDK=m + +# +# Display device support +# +CONFIG_DISPLAY_SUPPORT=m + +# +# Display hardware drivers +# + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +# CONFIG_MDA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=m +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +# CONFIG_LOGO_LINUX_CLUT224 is not set +CONFIG_LOGO_LINUX_LANDSCAPED_CLUT224=y + +# +# Sound +# +CONFIG_SOUND=y + +# +# Advanced Linux Sound Architecture +# +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +# CONFIG_SND_SUPPORT_OLD_API is not set +# CONFIG_SND_VERBOSE_PROCFS is not set +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set + +# +# Generic devices +# +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set + +# +# ALSA ARM devices +# + +# +# USB devices +# +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set + +# +# System on Chip audio support +# +CONFIG_SND_SOC=y +CONFIG_SND_S3C_SOC=y + +# +# SoC Audio for the Samsung S3C +# +CONFIG_SND_S3C6410_SOC_I2S_V32=y +# CONFIG_SND_S3C64XX_SOC_SMDK6410_WM9713 is not set +# CONFIG_SND_S3C6410_SOC_AC97 is not set +# CONFIG_SND_S3C6410_SOC_SMDK6410_WM8580 is not set +CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8987=y +CONFIG_AUDIO_CODEC_PROCFS=y +# CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8990 is not set +CONFIG_SND_SOC_WM8987=y + +# +# Open Sound System +# +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=m +CONFIG_HID_DEBUG=y +CONFIG_HIDRAW=y + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_USB_HIDINPUT_POWERBOOK is not set +# CONFIG_HID_FF is not set +CONFIG_USB_HIDDEV=y + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +CONFIG_USB_ARCH_HAS_OHCI=y +# CONFIG_USB_ARCH_HAS_EHCI is not set +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_PERSIST is not set +# CONFIG_USB_OTG is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_ISP116X_HCD is not set +CONFIG_USB_OHCI_HCD=m +# CONFIG_USB_OHCI_BIG_ENDIAN_DESC is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +CONFIG_USB_S3C_OTG_HOST=m + +# +# USB Device Class drivers +# +CONFIG_USB_ACM=m +# CONFIG_USB_PRINTER is not set + +# +# NOTE: USB_STORAGE enables SCSI, and 'SCSI disk support' +# + +# +# may also be needed; see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=m +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +# CONFIG_USB_STORAGE_DPCM is not set +CONFIG_USB_STORAGE_USBAT=y +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +CONFIG_USB_MON=y + +# +# USB port drivers +# + +# +# USB Serial Converter support +# +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +# CONFIG_USB_SERIAL_AIRCABLE is not set +# CONFIG_USB_SERIAL_AIRPRIME is not set +# CONFIG_USB_SERIAL_ARK3116 is not set +# CONFIG_USB_SERIAL_BELKIN is not set +# CONFIG_USB_SERIAL_CH341 is not set +# CONFIG_USB_SERIAL_WHITEHEAT is not set +# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set +# CONFIG_USB_SERIAL_CP2101 is not set +# CONFIG_USB_SERIAL_CYPRESS_M8 is not set +# CONFIG_USB_SERIAL_EMPEG is not set +# CONFIG_USB_SERIAL_FTDI_SIO is not set +# CONFIG_USB_SERIAL_FUNSOFT is not set +# CONFIG_USB_SERIAL_VISOR is not set +# CONFIG_USB_SERIAL_IPAQ is not set +# CONFIG_USB_SERIAL_IR is not set +# CONFIG_USB_SERIAL_EDGEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT_TI is not set +# CONFIG_USB_SERIAL_GARMIN is not set +# CONFIG_USB_SERIAL_IPW is not set +# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set +# CONFIG_USB_SERIAL_KEYSPAN is not set +# CONFIG_USB_SERIAL_KLSI is not set +# CONFIG_USB_SERIAL_KOBIL_SCT is not set +# CONFIG_USB_SERIAL_MCT_U232 is not set +# CONFIG_USB_SERIAL_MOS7720 is not set +# CONFIG_USB_SERIAL_MOS7840 is not set +# CONFIG_USB_SERIAL_NAVMAN is not set +# CONFIG_USB_SERIAL_PL2303 is not set +# CONFIG_USB_SERIAL_OTI6858 is not set +# CONFIG_USB_SERIAL_HP4X is not set +# CONFIG_USB_SERIAL_SAFE is not set +# CONFIG_USB_SERIAL_SIERRAWIRELESS is not set +# CONFIG_USB_SERIAL_TI is not set +# CONFIG_USB_SERIAL_CYBERJACK is not set +# CONFIG_USB_SERIAL_XIRCOM is not set +CONFIG_USB_SERIAL_OPTION=m +# CONFIG_USB_SERIAL_OMNINET is not set +# CONFIG_USB_SERIAL_DEBUG is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_JZ4755 is not set +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_AUERSWALD is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set + +# +# USB DSL modem support +# + +# +# USB Gadget Support +# +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_PXA2XX is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_GOKU is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_S3C_FS is not set +# CONFIG_USB_GADGET_S3C_HS is not set +CONFIG_USB_GADGET_S3C_OTGD_HS=y +CONFIG_USB_S3C=y +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_DUMMY_HCD is not set + +# +# NOTE: S3C OTG device role enables the controller driver below +# +CONFIG_USB_S3C_OTGD_HS=y +CONFIG_USB_GADGET_S3C_OTGD_HS_DMA_MODE=y +# CONFIG_USB_GADGET_S3C_OTGD_HS_SLAVE_MODE is not set +CONFIG_USB_GADGET_DUALSPEED=y +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +# CONFIG_USB_GADGETFS is not set +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +# CONFIG_USB_G_SERIAL is not set +# CONFIG_USB_MIDI_GADGET is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD Card Drivers +# +CONFIG_MMC_BLOCK=y +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_SDIO_UART=m + +# +# MMC/SD Host Controller Drivers +# +CONFIG_HSMMC_S3C=y +CONFIG_USE_MMC_AS_ROOT=y +CONFIG_HSMMC_S3C_IRQ_WORKAROUND=y +CONFIG_HSMMC_SCATTERGATHER=y +# CONFIG_S3CMMC_DEBUG is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=m + +# +# LED drivers +# +# CONFIG_LEDS_S3C24XX is not set +CONFIG_LEDS_GPIO=m + +# +# LED Triggers +# +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=m +CONFIG_LEDS_TRIGGER_HEARTBEAT=m +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set + +# +# SPI RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_DRV_S3C=y + +# +# Pulse Width Modulation Timer +# +CONFIG_PWM=y +CONFIG_S3C6410_PWM=y + +# +# File systems +# +# CONFIG_EXT2_FS is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_XATTR=y +# CONFIG_EXT3_FS_POSIX_ACL is not set +# CONFIG_EXT3_FS_SECURITY is not set +# CONFIG_EXT4DEV_FS is not set +CONFIG_JBD=y +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_ROMFS_FS is not set +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +CONFIG_DNOTIFY=y +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +CONFIG_FUSE_FS=m + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=m +# CONFIG_JOLIET is not set +# CONFIG_ZISOFS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +# CONFIG_MSDOS_FS is not set +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="cp437" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_SYSFS=y +# CONFIG_TMPFS is not set +# CONFIG_HUGETLB_PAGE is not set +CONFIG_CONFIGFS_FS=m + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_AUFS=y + +# +# These options are for 2.6.24.7 +# +CONFIG_AUFS_BRANCH_MAX_127=y +# CONFIG_AUFS_BRANCH_MAX_511 is not set +# CONFIG_AUFS_BRANCH_MAX_1023 is not set +# CONFIG_AUFS_BRANCH_MAX_32767 is not set +CONFIG_AUFS_SYSAUFS=y +# CONFIG_AUFS_HINOTIFY is not set + +# +# EXPORTFS and AUFS_EXPORT are disabled +# +# CONFIG_AUFS_ROBR is not set +# CONFIG_AUFS_DLGT is not set +# CONFIG_AUFS_SHWH is not set +CONFIG_AUFS_RR_SQUASHFS=y + +# +# SECURITY and AUFS_SEC_PERM_PATCH are disabled +# +# CONFIG_AUFS_SPLICE_PATCH is not set +# CONFIG_AUFS_LHASH_PATCH is not set + +# +# NFS_V4 and AUFS_PUT_FILP_PATCH are disabled +# +# CONFIG_AUFS_WORKAROUND_FUSE is not set +# CONFIG_AUFS_DEBUG is not set +# CONFIG_AUFS_COMPAT is not set +# CONFIG_AUFS_UNIONFS23_PATCH is not set +# CONFIG_CRAMFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=m +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_NFS_DIRECTIO=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=m +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=m +# CONFIG_SUNRPC_BIND34 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="cp437" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +# CONFIG_NLS_ISO8859_1 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=y +# CONFIG_DLM is not set +CONFIG_INSTRUMENTATION=y +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +CONFIG_FRAME_POINTER=y +# CONFIG_SAMPLES is not set +# CONFIG_DEBUG_USER is not set +CONFIG_DEBUG_S3C_UART=0 + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y +CONFIG_CRYPTO_ALGAPI=m +CONFIG_CRYPTO_BLKCIPHER=m +CONFIG_CRYPTO_MANAGER=m +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +CONFIG_CRYPTO_SHA1=m +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_WP512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_GF128MUL is not set +CONFIG_CRYPTO_ECB=m +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_XTS is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_TWOFISH is not set +# CONFIG_CRYPTO_SERPENT is not set +CONFIG_CRYPTO_AES=m +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_TEA is not set +CONFIG_CRYPTO_ARC4=m +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_AUTHENC is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=m +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=m +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=m +CONFIG_ZLIB_DEFLATE=m +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/configs/hhs3c6410_7_defconfig b/arch/arm/configs/hhs3c6410_7_defconfig new file mode 100644 index 0000000..2d3147a --- /dev/null +++ b/arch/arm/configs/hhs3c6410_7_defconfig @@ -0,0 +1,1592 @@ +# +# Automatically generated make config: don't edit +# Linux kernel version: 2.6.24.7 +# Tue Jun 16 10:44:09 2009 +# +CONFIG_ARM=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_GENERIC_GPIO=y +# CONFIG_GENERIC_TIME is not set +# CONFIG_GENERIC_CLOCKEVENTS is not set +CONFIG_MMU=y +CONFIG_NO_IOPORT=y +CONFIG_GENERIC_HARDIRQS=y +CONFIG_STACKTRACE_SUPPORT=y +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_RWSEM_GENERIC_SPINLOCK=y +# CONFIG_ARCH_HAS_ILOG2_U32 is not set +# CONFIG_ARCH_HAS_ILOG2_U64 is not set +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_ZONE_DMA=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" + +# +# General setup +# +CONFIG_EXPERIMENTAL=y +CONFIG_BROKEN_ON_SMP=y +CONFIG_LOCK_KERNEL=y +CONFIG_INIT_ENV_ARG_LIMIT=32 +CONFIG_LOCALVERSION="" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SWAP=y +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +# CONFIG_POSIX_MQUEUE is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_TASKSTATS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +# CONFIG_AUDIT is not set +CONFIG_IKCONFIG=m +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=13 +# CONFIG_CGROUPS is not set +# CONFIG_FAIR_GROUP_SCHED is not set +# CONFIG_SYSFS_DEPRECATED is not set +# CONFIG_RELAY is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="" +# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set +CONFIG_SYSCTL=y +CONFIG_EMBEDDED=y +# CONFIG_UID16 is not set +CONFIG_SYSCTL_SYSCALL=y +# CONFIG_KALLSYMS is not set +CONFIG_HOTPLUG=y +CONFIG_PRINTK=y +# CONFIG_BUG is not set +# CONFIG_ELF_CORE is not set +CONFIG_BASE_FULL=y +CONFIG_FUTEX=y +CONFIG_ANON_INODES=y +CONFIG_EPOLL=y +CONFIG_SIGNALFD=y +CONFIG_EVENTFD=y +# CONFIG_SHMEM is not set +# CONFIG_VM_EVENT_COUNTERS is not set +# CONFIG_SLUB_DEBUG is not set +# CONFIG_SLAB is not set +CONFIG_SLUB=y +# CONFIG_SLOB is not set +CONFIG_SLABINFO=y +CONFIG_RT_MUTEXES=y +CONFIG_TINY_SHMEM=y +CONFIG_BASE_SMALL=0 +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +# CONFIG_MODVERSIONS is not set +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_KMOD=y +CONFIG_BLOCK=y +# CONFIG_LBD is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_LSF is not set +# CONFIG_BLK_DEV_BSG is not set + +# +# IO Schedulers +# +CONFIG_IOSCHED_NOOP=y +# CONFIG_IOSCHED_AS is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_IOSCHED_CFQ=y +# CONFIG_DEFAULT_AS is not set +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_CFQ=y +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DEFAULT_IOSCHED="cfq" + +# +# System Type +# +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_VERSATILE is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_CLPS7500 is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_CO285 is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_IMX is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_MXC is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_SA1100 is not set +CONFIG_ARCH_S3C2410=y +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_OMAP is not set +CONFIG_PLAT_S3C64XX=y +CONFIG_S3C64XX_DMA=y +# CONFIG_S3C64XX_ADC is not set +CONFIG_PLAT_S3C=y + +# +# Boot options +# +# CONFIG_S3C_BOOT_WATCHDOG is not set +CONFIG_S3C_BOOT_ERROR_RESET=y + +# +# Power management +# +# CONFIG_S3C2410_PM_DEBUG is not set +# CONFIG_S3C2410_PM_CHECK is not set +CONFIG_S3C_LOWLEVEL_UART_PORT=0 +CONFIG_MACH_SMDK=y + +# +# S3C2400 Machines +# + +# +# S3C2410 Machines +# +# CONFIG_ARCH_SMDK2410 is not set +# CONFIG_ARCH_H1940 is not set +# CONFIG_MACH_N30 is not set +# CONFIG_ARCH_BAST is not set +# CONFIG_MACH_OTOM is not set +# CONFIG_MACH_AML_M5900 is not set +# CONFIG_MACH_VR1000 is not set +# CONFIG_MACH_QT2410 is not set + +# +# S3C2412 Machines +# +# CONFIG_MACH_SMDK2413 is not set +# CONFIG_MACH_SMDK2412 is not set +# CONFIG_MACH_VSTMS is not set + +# +# S3C2440 Machines +# +# CONFIG_MACH_ANUBIS is not set +# CONFIG_MACH_OSIRIS is not set +# CONFIG_MACH_RX3715 is not set +# CONFIG_ARCH_S3C2440 is not set +# CONFIG_MACH_NEXCODER_2440 is not set + +# +# S3C2442 Machines +# + +# +# S3C2443 Machines +# +# CONFIG_MACH_SMDK2443 is not set + +# +# S3C2450 Machines +# +# CONFIG_MACH_SMDK2450 is not set + +# +# S3C2416 Machines +# +# CONFIG_MACH_SMDK2416 is not set + +# +# S3C6400 Machines +# +# CONFIG_MACH_SMDK6400 is not set + +# +# S3C6410 Machines +# +CONFIG_MACH_SMDK6410=y +# CONFIG_MACH_SMDK6430 is not set +CONFIG_S3C6410_PDFW=m +CONFIG_S3C6410_PDFW_PROC=y +CONFIG_S3C6410_KDPMD=m +CONFIG_CPU_S3C6410=y +CONFIG_S3C6410_PM=y + +# +# S5PC100 Machines +# +# CONFIG_MACH_SMDKC100 is not set + +# +# Processor Type +# +CONFIG_CPU_32=y +CONFIG_CPU_V6=y +CONFIG_CPU_32v6K=y +CONFIG_CPU_32v6=y +CONFIG_CPU_ABRT_EV6=y +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y + +# +# Processor Features +# +CONFIG_ARM_THUMB=y +# CONFIG_CPU_ICACHE_DISABLE is not set +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +# CONFIG_OUTER_CACHE is not set + +# +# Bus support +# +CONFIG_ISA=y +# CONFIG_PCI_SYSCALL is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_PCCARD is not set + +# +# Kernel Features +# +# CONFIG_TICK_ONESHOT is not set +CONFIG_PREEMPT=y +CONFIG_NO_IDLE_HZ=y +CONFIG_HZ=200 +CONFIG_AEABI=y +# CONFIG_OABI_COMPAT is not set +# CONFIG_ARCH_DISCONTIGMEM_ENABLE is not set +CONFIG_SELECT_MEMORY_MODEL=y +CONFIG_FLATMEM_MANUAL=y +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_SPARSEMEM_MANUAL is not set +CONFIG_FLATMEM=y +CONFIG_FLAT_NODE_MEM_MAP=y +# CONFIG_SPARSEMEM_STATIC is not set +# CONFIG_SPARSEMEM_VMEMMAP_ENABLE is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_RESOURCES_64BIT is not set +CONFIG_ZONE_DMA_FLAG=1 +CONFIG_BOUNCE=y +CONFIG_VIRT_TO_BUS=y +# CONFIG_LEDS is not set +CONFIG_ALIGNMENT_TRAP=y + +# +# Boot options +# +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="console=ttySAC0,115200n8 root=/dev/mmcblk0p1 rootwait splash" +# CONFIG_XIP_KERNEL is not set +CONFIG_KEXEC=y + +# +# CPU Frequency scaling +# +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_TABLE=y +# CONFIG_CPU_FREQ_DEBUG is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set +CONFIG_CPU_FREQ_GOV_USERSPACE=m +CONFIG_CPU_FREQ_GOV_ONDEMAND=m +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=m + +# +# Floating point emulation +# + +# +# At least one emulation must be selected +# +CONFIG_VFP=y + +# +# Userspace binary formats +# +CONFIG_BINFMT_ELF=y +# CONFIG_BINFMT_AOUT is not set +CONFIG_BINFMT_MISC=m + +# +# Power management options +# +CONFIG_PM=y +# CONFIG_PM_LEGACY is not set +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_SUSPEND_UP_POSSIBLE=y +CONFIG_SUSPEND=y +CONFIG_APM_EMULATION=m + +# +# DVFS support +# +CONFIG_S3C64XX_DVFS=m + +# +# Networking +# +CONFIG_NET=y + +# +# Networking options +# +CONFIG_PACKET=m +CONFIG_PACKET_MMAP=y +CONFIG_UNIX=y +# CONFIG_NET_KEY is not set +CONFIG_INET=y +# CONFIG_IP_MULTICAST is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +CONFIG_IP_FIB_HASH=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +# CONFIG_IP_PNP_RARP is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_IPGRE is not set +# CONFIG_ARPD is not set +# CONFIG_SYN_COOKIES is not set +# CONFIG_INET_AH is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_TUNNEL is not set +# CONFIG_INET_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +CONFIG_INET_LRO=m +CONFIG_INET_DIAG=m +CONFIG_INET_TCP_DIAG=m +# CONFIG_TCP_CONG_ADVANCED is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_DEFAULT_TCP_CONG="cubic" +# CONFIG_TCP_MD5SIG is not set +# CONFIG_IP_VS is not set +# CONFIG_IPV6 is not set +# CONFIG_INET6_XFRM_TUNNEL is not set +# CONFIG_INET6_TUNNEL is not set +# CONFIG_NETWORK_SECMARK is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_DEBUG is not set + +# +# Core Netfilter Configuration +# +# CONFIG_NETFILTER_NETLINK is not set +# CONFIG_NF_CONNTRACK_ENABLED is not set +# CONFIG_NF_CONNTRACK is not set +CONFIG_NETFILTER_XTABLES=m +# CONFIG_NETFILTER_XT_TARGET_CLASSIFY is not set +# CONFIG_NETFILTER_XT_TARGET_MARK is not set +# CONFIG_NETFILTER_XT_TARGET_NFQUEUE is not set +# CONFIG_NETFILTER_XT_TARGET_NFLOG is not set +# CONFIG_NETFILTER_XT_TARGET_TCPMSS is not set +# CONFIG_NETFILTER_XT_MATCH_COMMENT is not set +# CONFIG_NETFILTER_XT_MATCH_DCCP is not set +# CONFIG_NETFILTER_XT_MATCH_DSCP is not set +# CONFIG_NETFILTER_XT_MATCH_ESP is not set +# CONFIG_NETFILTER_XT_MATCH_LENGTH is not set +# CONFIG_NETFILTER_XT_MATCH_LIMIT is not set +# CONFIG_NETFILTER_XT_MATCH_MAC is not set +# CONFIG_NETFILTER_XT_MATCH_MARK is not set +# CONFIG_NETFILTER_XT_MATCH_MULTIPORT is not set +# CONFIG_NETFILTER_XT_MATCH_PKTTYPE is not set +# CONFIG_NETFILTER_XT_MATCH_QUOTA is not set +# CONFIG_NETFILTER_XT_MATCH_REALM is not set +# CONFIG_NETFILTER_XT_MATCH_SCTP is not set +# CONFIG_NETFILTER_XT_MATCH_STATISTIC is not set +# CONFIG_NETFILTER_XT_MATCH_STRING is not set +# CONFIG_NETFILTER_XT_MATCH_TCPMSS is not set +# CONFIG_NETFILTER_XT_MATCH_TIME is not set +# CONFIG_NETFILTER_XT_MATCH_U32 is not set +# CONFIG_NETFILTER_XT_MATCH_HASHLIMIT is not set + +# +# IP: Netfilter Configuration +# +# CONFIG_IP_NF_QUEUE is not set +# CONFIG_IP_NF_IPTABLES is not set +# CONFIG_IP_NF_ARPTABLES is not set +# CONFIG_IP_DCCP is not set +# CONFIG_IP_SCTP is not set +# CONFIG_TIPC is not set +# CONFIG_ATM is not set +# CONFIG_BRIDGE is not set +# CONFIG_VLAN_8021Q is not set +# CONFIG_DECNET is not set +# CONFIG_LLC2 is not set +# CONFIG_IPX is not set +# CONFIG_ATALK is not set +# CONFIG_X25 is not set +# CONFIG_LAPB is not set +# CONFIG_ECONET is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_NET_SCHED is not set +CONFIG_NET_SCH_FIFO=y + +# +# Network testing +# +# CONFIG_NET_PKTGEN is not set +# CONFIG_HAMRADIO is not set +# CONFIG_IRDA is not set +CONFIG_BT=m +CONFIG_BT_L2CAP=m +CONFIG_BT_SCO=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m + +# +# Bluetooth device drivers +# +CONFIG_BT_HCIBTUSB=m +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set +# CONFIG_AF_RXRPC is not set + +# +# Wireless +# +CONFIG_CFG80211=m +CONFIG_NL80211=y +CONFIG_WIRELESS_EXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_RCSIMPLE=y +CONFIG_MAC80211_LEDS=y +# CONFIG_MAC80211_DEBUG is not set +CONFIG_IEEE80211=m +# CONFIG_IEEE80211_DEBUG is not set +CONFIG_IEEE80211_CRYPT_WEP=m +# CONFIG_IEEE80211_CRYPT_CCMP is not set +# CONFIG_IEEE80211_CRYPT_TKIP is not set +# CONFIG_IEEE80211_SOFTMAC is not set +# CONFIG_RFKILL is not set +# CONFIG_NET_9P is not set + +# +# Device Drivers +# + +# +# Generic Driver Options +# +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_STANDALONE=y +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_FW_LOADER=y +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_CONNECTOR=m +# CONFIG_MTD is not set +# CONFIG_PARPORT is not set +# CONFIG_PNP is not set +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_LOOP=m +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_NBD is not set +# CONFIG_BLK_DEV_UB is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_BLK_DEV_RAM_BLOCKSIZE=1024 +# CONFIG_CDROM_PKTCDVD is not set +# CONFIG_ATA_OVER_ETH is not set +CONFIG_MISC_DEVICES=y +CONFIG_SMARTQ5_ENCRYPT=y +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_IDE is not set + +# +# SCSI device support +# +# CONFIG_RAID_ATTRS is not set +CONFIG_SCSI=m +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_TGT is not set +# CONFIG_SCSI_NETLINK is not set +CONFIG_SCSI_PROC_FS=y + +# +# SCSI support type (disk, tape, CD-ROM) +# +CONFIG_BLK_DEV_SD=m +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CHR_DEV_OSST is not set +CONFIG_BLK_DEV_SR=m +# CONFIG_BLK_DEV_SR_VENDOR is not set +# CONFIG_CHR_DEV_SG is not set +# CONFIG_CHR_DEV_SCH is not set + +# +# Some SCSI devices (e.g. CD jukebox) support multiple LUNs +# +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_WAIT_SCAN=m + +# +# SCSI Transports +# +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_ATA is not set +# CONFIG_MD is not set +CONFIG_NETDEVICES=y +# CONFIG_NETDEVICES_MULTIQUEUE is not set +# CONFIG_DUMMY is not set +# CONFIG_BONDING is not set +# CONFIG_MACVLAN is not set +# CONFIG_EQUALIZER is not set +CONFIG_TUN=m +# CONFIG_VETH is not set +# CONFIG_ARCNET is not set +# CONFIG_PHYLIB is not set +CONFIG_NET_ETHERNET=y +CONFIG_MII=m +# CONFIG_AX88796 is not set +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_SMC is not set +# CONFIG_SMC91X is not set +CONFIG_DM9000=m +# CONFIG_SMC911X is not set +# CONFIG_NET_VENDOR_RACAL is not set +# CONFIG_AT1700 is not set +# CONFIG_DEPCA is not set +# CONFIG_HP100 is not set +# CONFIG_NET_ISA is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_NET_PCI is not set +# CONFIG_B44 is not set +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_TR is not set + +# +# Wireless LAN +# +# CONFIG_WLAN_PRE80211 is not set +CONFIG_WLAN_80211=y +# CONFIG_LIBERTAS is not set +CONFIG_MARVELL_8686_SDIO=m +CONFIG_MARVELL_8686_PROC_FS=y +CONFIG_MARVELL_8686_DEBUG=y +# CONFIG_USB_ZD1201 is not set +# CONFIG_RTL8187 is not set +# CONFIG_P54_COMMON is not set +# CONFIG_HOSTAP is not set +# CONFIG_B43 is not set +# CONFIG_B43LEGACY is not set +# CONFIG_RT2X00 is not set + +# +# USB Network Adapters +# +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=m +CONFIG_USB_RTL8150=m +CONFIG_USB_USBNET=m +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_BELKIN=y +CONFIG_USB_ARMLINUX=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +# CONFIG_WAN is not set +CONFIG_PPP=m +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_MPPE=m +# CONFIG_PPPOE is not set +# CONFIG_PPPOL2TP is not set +# CONFIG_SLIP is not set +CONFIG_SLHC=m +# CONFIG_SHAPER is not set +# CONFIG_NETCONSOLE is not set +# CONFIG_NETPOLL is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_ISDN is not set + +# +# Input device support +# +CONFIG_INPUT=y +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_POLLDEV is not set + +# +# Userland interfaces +# +CONFIG_INPUT_MOUSEDEV=m +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_MOUSEDEV_SCREEN_X=800 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=480 +# CONFIG_INPUT_JOYDEV is not set +# CONFIG_INPUT_TSDEV is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_EVBUG is not set + +# +# Input Device Drivers +# +CONFIG_INPUT_KEYBOARD=y +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYPAD_S3C=m +CONFIG_INPUT_MOUSE=y +# CONFIG_MOUSE_PS2 is not set +# CONFIG_MOUSE_SERIAL is not set +# CONFIG_MOUSE_APPLETOUCH is not set +# CONFIG_MOUSE_INPORT is not set +# CONFIG_MOUSE_LOGIBM is not set +# CONFIG_MOUSE_PC110PAD is not set +# CONFIG_MOUSE_VSXXXAA is not set +# CONFIG_MOUSE_GPIO is not set +# CONFIG_INPUT_JOYSTICK is not set +CONFIG_INPUT_TABLET=y +# CONFIG_TABLET_USB_ACECAD is not set +# CONFIG_TABLET_USB_AIPTEK is not set +# CONFIG_TABLET_USB_GTCO is not set +# CONFIG_TABLET_USB_KBTAB is not set +# CONFIG_TABLET_USB_WACOM is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_S3C=y +# CONFIG_TOUCHSCREEN_NEW is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_UCB1400 is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +CONFIG_INPUT_UINPUT=m + +# +# Hardware I/O ports +# +# CONFIG_SERIO is not set +# CONFIG_GAMEPORT is not set + +# +# Character devices +# +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_HW_CONSOLE=y +CONFIG_VT_HW_CONSOLE_BINDING=y +# CONFIG_SERIAL_NONSTANDARD is not set + +# +# Serial drivers +# +# CONFIG_SERIAL_8250 is not set + +# +# Non-8250 serial port support +# +# CONFIG_SERIAL_S3C2410 is not set +CONFIG_SERIAL_S3C6400=y +CONFIG_SERIAL_S3C64XX_CONSOLE=y +# CONFIG_SERIAL_S3C64XX_HS_UART is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_UNIX98_PTYS=y +# CONFIG_LEGACY_PTYS is not set +# CONFIG_IPMI_HANDLER is not set +# CONFIG_HW_RANDOM is not set +# CONFIG_NVRAM is not set +# CONFIG_S3C_ADC is not set +# CONFIG_DTLK is not set +# CONFIG_R3964 is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_TCG_TPM is not set +CONFIG_DEVPORT=y +CONFIG_S3C_MEM=y +# CONFIG_LCD_4 is not set +CONFIG_LCD_7=y +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=m + +# +# I2C Algorithms +# +# CONFIG_I2C_ALGOBIT is not set +# CONFIG_I2C_ALGOPCF is not set +# CONFIG_I2C_ALGOPCA is not set + +# +# I2C Hardware Bus support +# +# CONFIG_I2C_ELEKTOR is not set +# CONFIG_I2C_GPIO is not set +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PARPORT_LIGHT is not set +CONFIG_I2C_S3C64XX=y +# CONFIG_I2C_SIMTEC is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_STUB is not set +# CONFIG_I2C_TINY_USB is not set +# CONFIG_I2C_PCA_ISA is not set + +# +# Miscellaneous I2C Chip support +# +# CONFIG_SENSORS_DS1337 is not set +# CONFIG_SENSORS_DS1374 is not set +# CONFIG_DS1682 is not set +# CONFIG_SENSORS_EEPROM is not set +# CONFIG_SENSORS_PCF8574 is not set +# CONFIG_SENSORS_PCA9539 is not set +# CONFIG_SENSORS_PCF8591 is not set +# CONFIG_SENSORS_MAX6875 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CHIP is not set + +# +# SPI support +# +# CONFIG_SPI is not set +# CONFIG_SPI_MASTER is not set +# CONFIG_W1 is not set +CONFIG_POWER_SUPPLY=m +# CONFIG_POWER_SUPPLY_DEBUG is not set +CONFIG_PDA_POWER=m +CONFIG_APM_POWER=m +# CONFIG_BATTERY_DS2760 is not set +CONFIG_HWMON=m +# CONFIG_HWMON_VID is not set +# CONFIG_SENSORS_AD7418 is not set +# CONFIG_SENSORS_ADM1021 is not set +# CONFIG_SENSORS_ADM1025 is not set +# CONFIG_SENSORS_ADM1026 is not set +# CONFIG_SENSORS_ADM1029 is not set +# CONFIG_SENSORS_ADM1031 is not set +# CONFIG_SENSORS_ADM9240 is not set +# CONFIG_SENSORS_ADT7470 is not set +# CONFIG_SENSORS_ATXP1 is not set +# CONFIG_SENSORS_DS1621 is not set +# CONFIG_SENSORS_F71805F is not set +# CONFIG_SENSORS_F71882FG is not set +# CONFIG_SENSORS_F75375S is not set +# CONFIG_SENSORS_GL518SM is not set +# CONFIG_SENSORS_GL520SM is not set +# CONFIG_SENSORS_IT87 is not set +# CONFIG_SENSORS_LM63 is not set +# CONFIG_SENSORS_LM75 is not set +# CONFIG_SENSORS_LM77 is not set +# CONFIG_SENSORS_LM78 is not set +# CONFIG_SENSORS_LM80 is not set +# CONFIG_SENSORS_LM83 is not set +# CONFIG_SENSORS_LM85 is not set +# CONFIG_SENSORS_LM87 is not set +# CONFIG_SENSORS_LM90 is not set +# CONFIG_SENSORS_LM92 is not set +# CONFIG_SENSORS_LM93 is not set +# CONFIG_SENSORS_MAX1619 is not set +# CONFIG_SENSORS_MAX6650 is not set +# CONFIG_SENSORS_PC87360 is not set +# CONFIG_SENSORS_PC87427 is not set +# CONFIG_SENSORS_DME1737 is not set +# CONFIG_SENSORS_SMSC47M1 is not set +# CONFIG_SENSORS_SMSC47M192 is not set +# CONFIG_SENSORS_SMSC47B397 is not set +# CONFIG_SENSORS_THMC50 is not set +# CONFIG_SENSORS_VT1211 is not set +# CONFIG_SENSORS_W83781D is not set +# CONFIG_SENSORS_W83791D is not set +# CONFIG_SENSORS_W83792D is not set +# CONFIG_SENSORS_W83793 is not set +# CONFIG_SENSORS_W83L785TS is not set +# CONFIG_SENSORS_W83627HF is not set +# CONFIG_SENSORS_W83627EHF is not set +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_WATCHDOG=y +# CONFIG_WATCHDOG_NOWAYOUT is not set + +# +# Watchdog Device Drivers +# +# CONFIG_SOFT_WATCHDOG is not set +CONFIG_S3C2410_WATCHDOG=y + +# +# ISA-based Watchdog Cards +# +# CONFIG_PCWATCHDOG is not set +# CONFIG_MIXCOMWD is not set +# CONFIG_WDT is not set + +# +# USB-based Watchdog Cards +# +# CONFIG_USBPCWATCHDOG is not set + +# +# Sonics Silicon Backplane +# +CONFIG_SSB_POSSIBLE=y +# CONFIG_SSB is not set + +# +# Multifunction device drivers +# +# CONFIG_MFD_SM501 is not set + +# +# Multimedia devices +# +# CONFIG_VIDEO_DEV is not set +# CONFIG_DVB_CORE is not set +# CONFIG_DAB is not set + +# +# Graphics support +# +# CONFIG_VGASTATE is not set +CONFIG_VIDEO_OUTPUT_CONTROL=m +CONFIG_FB=y +# CONFIG_FIRMWARE_EDID is not set +# CONFIG_FB_DDC is not set +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_SYS_FOPS is not set +CONFIG_FB_DEFERRED_IO=y +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set + +# +# Frame buffer hardware drivers +# +CONFIG_FB_S3C=y +# CONFIG_FB_S3C_LTE480WV is not set +# CONFIG_FB_S3C_LTV350QV is not set +# CONFIG_FB_S3C_LTS222QV is not set +CONFIG_FB_S3C_A070VW04=y +# CONFIG_FB_S3C_TD043MTEX is not set +CONFIG_FB_S3C_BPP=y +# CONFIG_FB_S3C_BPP_8 is not set +CONFIG_FB_S3C_BPP_16=y +# CONFIG_FB_S3C_BPP_24 is not set +# CONFIG_FB_S3C_BPP_32 is not set +CONFIG_FB_S3C_NUM=1 +# CONFIG_FB_S3C_VIRTUAL_SCREEN is not set +# CONFIG_FB_S3C_DOUBLE_BUFFERING is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_S3C2410 is not set +# CONFIG_FB_VIRTUAL is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=m +CONFIG_BACKLIGHT_CLASS_DEVICE=m +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_SMDK=m + +# +# Display device support +# +CONFIG_DISPLAY_SUPPORT=m + +# +# Display hardware drivers +# + +# +# Console display driver support +# +# CONFIG_VGA_CONSOLE is not set +# CONFIG_MDA_CONSOLE is not set +CONFIG_DUMMY_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE=m +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +# CONFIG_FONTS is not set +CONFIG_FONT_8x8=y +CONFIG_FONT_8x16=y +CONFIG_LOGO=y +# CONFIG_LOGO_LINUX_MONO is not set +# CONFIG_LOGO_LINUX_VGA16 is not set +# CONFIG_LOGO_LINUX_CLUT224 is not set +CONFIG_LOGO_LINUX_LANDSCAPED_CLUT224=y + +# +# Sound +# +CONFIG_SOUND=y + +# +# Advanced Linux Sound Architecture +# +CONFIG_SND=y +CONFIG_SND_TIMER=y +CONFIG_SND_PCM=y +# CONFIG_SND_SEQUENCER is not set +CONFIG_SND_OSSEMUL=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_DYNAMIC_MINORS is not set +# CONFIG_SND_SUPPORT_OLD_API is not set +# CONFIG_SND_VERBOSE_PROCFS is not set +# CONFIG_SND_VERBOSE_PRINTK is not set +# CONFIG_SND_DEBUG is not set + +# +# Generic devices +# +# CONFIG_SND_DUMMY is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_MPU401 is not set + +# +# ALSA ARM devices +# + +# +# USB devices +# +# CONFIG_SND_USB_AUDIO is not set +# CONFIG_SND_USB_CAIAQ is not set + +# +# System on Chip audio support +# +CONFIG_SND_SOC=y +CONFIG_SND_S3C_SOC=y + +# +# SoC Audio for the Samsung S3C +# +CONFIG_SND_S3C6410_SOC_I2S_V32=y +# CONFIG_SND_S3C64XX_SOC_SMDK6410_WM9713 is not set +# CONFIG_SND_S3C6410_SOC_AC97 is not set +# CONFIG_SND_S3C6410_SOC_SMDK6410_WM8580 is not set +CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8987=y +CONFIG_AUDIO_CODEC_PROCFS=y +# CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8990 is not set +CONFIG_SND_SOC_WM8987=y + +# +# Open Sound System +# +# CONFIG_SOUND_PRIME is not set +CONFIG_HID_SUPPORT=y +CONFIG_HID=m +CONFIG_HID_DEBUG=y +CONFIG_HIDRAW=y + +# +# USB Input Devices +# +CONFIG_USB_HID=m +# CONFIG_USB_HIDINPUT_POWERBOOK is not set +# CONFIG_HID_FF is not set +CONFIG_USB_HIDDEV=y + +# +# USB HID Boot Protocol drivers +# +# CONFIG_USB_KBD is not set +# CONFIG_USB_MOUSE is not set +CONFIG_USB_SUPPORT=y +CONFIG_USB_ARCH_HAS_HCD=y +CONFIG_USB_ARCH_HAS_OHCI=y +# CONFIG_USB_ARCH_HAS_EHCI is not set +CONFIG_USB=y +# CONFIG_USB_DEBUG is not set + +# +# Miscellaneous USB options +# +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +CONFIG_USB_SUSPEND=y +# CONFIG_USB_PERSIST is not set +# CONFIG_USB_OTG is not set + +# +# USB Host Controller Drivers +# +# CONFIG_USB_ISP116X_HCD is not set +CONFIG_USB_OHCI_HCD=m +# CONFIG_USB_OHCI_BIG_ENDIAN_DESC is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +# CONFIG_USB_SL811_HCD is not set +# CONFIG_USB_R8A66597_HCD is not set +CONFIG_USB_S3C_OTG_HOST=m + +# +# USB Device Class drivers +# +CONFIG_USB_ACM=m +# CONFIG_USB_PRINTER is not set + +# +# NOTE: USB_STORAGE enables SCSI, and 'SCSI disk support' +# + +# +# may also be needed; see USB_STORAGE Help for more information +# +CONFIG_USB_STORAGE=m +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +# CONFIG_USB_STORAGE_DPCM is not set +CONFIG_USB_STORAGE_USBAT=y +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_LIBUSUAL is not set + +# +# USB Imaging devices +# +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +CONFIG_USB_MON=y + +# +# USB port drivers +# + +# +# USB Serial Converter support +# +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +# CONFIG_USB_SERIAL_AIRCABLE is not set +# CONFIG_USB_SERIAL_AIRPRIME is not set +# CONFIG_USB_SERIAL_ARK3116 is not set +# CONFIG_USB_SERIAL_BELKIN is not set +# CONFIG_USB_SERIAL_CH341 is not set +# CONFIG_USB_SERIAL_WHITEHEAT is not set +# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set +# CONFIG_USB_SERIAL_CP2101 is not set +# CONFIG_USB_SERIAL_CYPRESS_M8 is not set +# CONFIG_USB_SERIAL_EMPEG is not set +# CONFIG_USB_SERIAL_FTDI_SIO is not set +# CONFIG_USB_SERIAL_FUNSOFT is not set +# CONFIG_USB_SERIAL_VISOR is not set +# CONFIG_USB_SERIAL_IPAQ is not set +# CONFIG_USB_SERIAL_IR is not set +# CONFIG_USB_SERIAL_EDGEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT_TI is not set +# CONFIG_USB_SERIAL_GARMIN is not set +# CONFIG_USB_SERIAL_IPW is not set +# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set +# CONFIG_USB_SERIAL_KEYSPAN is not set +# CONFIG_USB_SERIAL_KLSI is not set +# CONFIG_USB_SERIAL_KOBIL_SCT is not set +# CONFIG_USB_SERIAL_MCT_U232 is not set +# CONFIG_USB_SERIAL_MOS7720 is not set +# CONFIG_USB_SERIAL_MOS7840 is not set +# CONFIG_USB_SERIAL_NAVMAN is not set +# CONFIG_USB_SERIAL_PL2303 is not set +# CONFIG_USB_SERIAL_OTI6858 is not set +# CONFIG_USB_SERIAL_HP4X is not set +# CONFIG_USB_SERIAL_SAFE is not set +# CONFIG_USB_SERIAL_SIERRAWIRELESS is not set +# CONFIG_USB_SERIAL_TI is not set +# CONFIG_USB_SERIAL_CYBERJACK is not set +# CONFIG_USB_SERIAL_XIRCOM is not set +CONFIG_USB_SERIAL_OPTION=m +# CONFIG_USB_SERIAL_OMNINET is not set +# CONFIG_USB_SERIAL_DEBUG is not set + +# +# USB Miscellaneous drivers +# +# CONFIG_USB_JZ4755 is not set +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_ADUTUX is not set +# CONFIG_USB_AUERSWALD is not set +# CONFIG_USB_RIO500 is not set +# CONFIG_USB_LEGOTOWER is not set +# CONFIG_USB_LCD is not set +# CONFIG_USB_BERRY_CHARGE is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_PHIDGET is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_APPLEDISPLAY is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_TEST is not set + +# +# USB DSL modem support +# + +# +# USB Gadget Support +# +CONFIG_USB_GADGET=y +# CONFIG_USB_GADGET_DEBUG_FILES is not set +CONFIG_USB_GADGET_SELECTED=y +# CONFIG_USB_GADGET_AMD5536UDC is not set +# CONFIG_USB_GADGET_ATMEL_USBA is not set +# CONFIG_USB_GADGET_FSL_USB2 is not set +# CONFIG_USB_GADGET_NET2280 is not set +# CONFIG_USB_GADGET_PXA2XX is not set +# CONFIG_USB_GADGET_M66592 is not set +# CONFIG_USB_GADGET_GOKU is not set +# CONFIG_USB_GADGET_LH7A40X is not set +# CONFIG_USB_GADGET_OMAP is not set +# CONFIG_USB_GADGET_S3C_FS is not set +# CONFIG_USB_GADGET_S3C_HS is not set +CONFIG_USB_GADGET_S3C_OTGD_HS=y +CONFIG_USB_S3C=y +# CONFIG_USB_GADGET_S3C2410 is not set +# CONFIG_USB_GADGET_AT91 is not set +# CONFIG_USB_GADGET_DUMMY_HCD is not set + +# +# NOTE: S3C OTG device role enables the controller driver below +# +CONFIG_USB_S3C_OTGD_HS=m +CONFIG_USB_GADGET_S3C_OTGD_HS_DMA_MODE=y +# CONFIG_USB_GADGET_S3C_OTGD_HS_SLAVE_MODE is not set +CONFIG_USB_GADGET_DUALSPEED=y +# CONFIG_USB_ZERO is not set +CONFIG_USB_ETH=m +CONFIG_USB_ETH_RNDIS=y +# CONFIG_USB_GADGETFS is not set +CONFIG_USB_FILE_STORAGE=m +# CONFIG_USB_FILE_STORAGE_TEST is not set +# CONFIG_USB_G_SERIAL is not set +# CONFIG_USB_MIDI_GADGET is not set +CONFIG_MMC=y +# CONFIG_MMC_DEBUG is not set +CONFIG_MMC_UNSAFE_RESUME=y + +# +# MMC/SD Card Drivers +# +CONFIG_MMC_BLOCK=y +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_SDIO_UART=m + +# +# MMC/SD Host Controller Drivers +# +CONFIG_HSMMC_S3C=y +CONFIG_USE_MMC_AS_ROOT=y +CONFIG_HSMMC_S3C_IRQ_WORKAROUND=y +CONFIG_HSMMC_SCATTERGATHER=y +# CONFIG_S3CMMC_DEBUG is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=m + +# +# LED drivers +# +# CONFIG_LEDS_S3C24XX is not set +CONFIG_LEDS_GPIO=m + +# +# LED Triggers +# +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=m +CONFIG_LEDS_TRIGGER_HEARTBEAT=m +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_DEBUG is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# +# CONFIG_RTC_DRV_DS1307 is not set +# CONFIG_RTC_DRV_DS1374 is not set +# CONFIG_RTC_DRV_DS1672 is not set +# CONFIG_RTC_DRV_MAX6900 is not set +# CONFIG_RTC_DRV_RS5C372 is not set +# CONFIG_RTC_DRV_ISL1208 is not set +# CONFIG_RTC_DRV_X1205 is not set +# CONFIG_RTC_DRV_PCF8563 is not set +# CONFIG_RTC_DRV_PCF8583 is not set +# CONFIG_RTC_DRV_M41T80 is not set + +# +# SPI RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_CMOS is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_V3020 is not set + +# +# on-CPU RTC drivers +# +CONFIG_RTC_DRV_S3C=y + +# +# Pulse Width Modulation Timer +# +CONFIG_PWM=y +CONFIG_S3C6410_PWM=y + +# +# File systems +# +# CONFIG_EXT2_FS is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_XATTR=y +# CONFIG_EXT3_FS_POSIX_ACL is not set +# CONFIG_EXT3_FS_SECURITY is not set +# CONFIG_EXT4DEV_FS is not set +CONFIG_JBD=y +CONFIG_FS_MBCACHE=y +# CONFIG_REISERFS_FS is not set +# CONFIG_JFS_FS is not set +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_XFS_FS is not set +# CONFIG_GFS2_FS is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_MINIX_FS is not set +# CONFIG_ROMFS_FS is not set +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +# CONFIG_QUOTA is not set +CONFIG_DNOTIFY=y +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUTOFS4_FS is not set +CONFIG_FUSE_FS=m + +# +# CD-ROM/DVD Filesystems +# +CONFIG_ISO9660_FS=m +# CONFIG_JOLIET is not set +# CONFIG_ZISOFS is not set +# CONFIG_UDF_FS is not set + +# +# DOS/FAT/NT Filesystems +# +CONFIG_FAT_FS=y +# CONFIG_MSDOS_FS is not set +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="cp437" +# CONFIG_NTFS_FS is not set + +# +# Pseudo filesystems +# +CONFIG_PROC_FS=y +CONFIG_PROC_SYSCTL=y +CONFIG_SYSFS=y +# CONFIG_TMPFS is not set +# CONFIG_HUGETLB_PAGE is not set +CONFIG_CONFIGFS_FS=m + +# +# Miscellaneous filesystems +# +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_HFS_FS is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +# CONFIG_EFS_FS is not set +CONFIG_AUFS=y + +# +# These options are for 2.6.24.7 +# +CONFIG_AUFS_BRANCH_MAX_127=y +# CONFIG_AUFS_BRANCH_MAX_511 is not set +# CONFIG_AUFS_BRANCH_MAX_1023 is not set +# CONFIG_AUFS_BRANCH_MAX_32767 is not set +CONFIG_AUFS_SYSAUFS=y +# CONFIG_AUFS_HINOTIFY is not set + +# +# EXPORTFS and AUFS_EXPORT are disabled +# +# CONFIG_AUFS_ROBR is not set +# CONFIG_AUFS_DLGT is not set +# CONFIG_AUFS_SHWH is not set +CONFIG_AUFS_RR_SQUASHFS=y + +# +# SECURITY and AUFS_SEC_PERM_PATCH are disabled +# +# CONFIG_AUFS_SPLICE_PATCH is not set +# CONFIG_AUFS_LHASH_PATCH is not set + +# +# NFS_V4 and AUFS_PUT_FILP_PATCH are disabled +# +# CONFIG_AUFS_WORKAROUND_FUSE is not set +# CONFIG_AUFS_DEBUG is not set +# CONFIG_AUFS_COMPAT is not set +# CONFIG_AUFS_UNIONFS23_PATCH is not set +# CONFIG_CRAMFS is not set +# CONFIG_VXFS_FS is not set +# CONFIG_HPFS_FS is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_SYSV_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_NETWORK_FILESYSTEMS=y +CONFIG_NFS_FS=m +CONFIG_NFS_V3=y +# CONFIG_NFS_V3_ACL is not set +# CONFIG_NFS_V4 is not set +CONFIG_NFS_DIRECTIO=y +# CONFIG_NFSD is not set +CONFIG_LOCKD=m +CONFIG_LOCKD_V4=y +CONFIG_NFS_COMMON=y +CONFIG_SUNRPC=m +# CONFIG_SUNRPC_BIND34 is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +# CONFIG_RPCSEC_GSS_SPKM3 is not set +# CONFIG_SMB_FS is not set +# CONFIG_CIFS is not set +# CONFIG_NCP_FS is not set +# CONFIG_CODA_FS is not set +# CONFIG_AFS_FS is not set + +# +# Partition Types +# +# CONFIG_PARTITION_ADVANCED is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NLS=y +CONFIG_NLS_DEFAULT="cp437" +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +# CONFIG_NLS_ASCII is not set +# CONFIG_NLS_ISO8859_1 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=y +# CONFIG_DLM is not set +CONFIG_INSTRUMENTATION=y +# CONFIG_PROFILING is not set +# CONFIG_MARKERS is not set + +# +# Kernel hacking +# +# CONFIG_PRINTK_TIME is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +CONFIG_ENABLE_MUST_CHECK=y +# CONFIG_MAGIC_SYSRQ is not set +# CONFIG_UNUSED_SYMBOLS is not set +# CONFIG_DEBUG_FS is not set +# CONFIG_HEADERS_CHECK is not set +# CONFIG_DEBUG_KERNEL is not set +CONFIG_FRAME_POINTER=y +# CONFIG_SAMPLES is not set +# CONFIG_DEBUG_USER is not set +CONFIG_DEBUG_S3C_UART=0 + +# +# Security options +# +# CONFIG_KEYS is not set +# CONFIG_SECURITY is not set +# CONFIG_SECURITY_FILE_CAPABILITIES is not set +CONFIG_CRYPTO=y +CONFIG_CRYPTO_ALGAPI=m +CONFIG_CRYPTO_BLKCIPHER=m +CONFIG_CRYPTO_MANAGER=m +# CONFIG_CRYPTO_HMAC is not set +# CONFIG_CRYPTO_XCBC is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_MD5 is not set +CONFIG_CRYPTO_SHA1=m +# CONFIG_CRYPTO_SHA256 is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_WP512 is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_GF128MUL is not set +CONFIG_CRYPTO_ECB=m +# CONFIG_CRYPTO_CBC is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_XTS is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_DES is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_TWOFISH is not set +# CONFIG_CRYPTO_SERPENT is not set +CONFIG_CRYPTO_AES=m +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_TEA is not set +CONFIG_CRYPTO_ARC4=m +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_MICHAEL_MIC is not set +# CONFIG_CRYPTO_CRC32C is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_AUTHENC is not set +CONFIG_CRYPTO_HW=y + +# +# Library routines +# +CONFIG_BITREVERSE=m +CONFIG_CRC_CCITT=m +# CONFIG_CRC16 is not set +# CONFIG_CRC_ITU_T is not set +CONFIG_CRC32=m +# CONFIG_CRC7 is not set +# CONFIG_LIBCRC32C is not set +CONFIG_ZLIB_INFLATE=m +CONFIG_ZLIB_DEFLATE=m +CONFIG_PLIST=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_DMA=y diff --git a/arch/arm/mach-s3c6400/Makefile b/arch/arm/mach-s3c6400/Makefile index 8319eb1..9aa12da 100644 --- a/arch/arm/mach-s3c6400/Makefile +++ b/arch/arm/mach-s3c6400/Makefile @@ -5,7 +5,7 @@ # Object file lists. obj-y := -led-y := leds.o +led-y := obj-n := obj- := @@ -20,6 +20,6 @@ obj-$(CONFIG_S3C6400_PM) += pm.o obj-$(CONFIG_MACH_SMDK6400) += mach-smdk6400.o # LEDs support -led-$(CONFIG_MACH_SMDK6400) += leds-s3c6400.o +led-$(CONFIG_MACH_SMDK6400) += leds.o leds-s3c6400.o obj-$(CONFIG_LEDS) += $(led-y) diff --git a/arch/arm/mach-s3c6400/leds-s3c6400.c b/arch/arm/mach-s3c6400/leds-s3c6400.c index 96e7774..4baa7e2 100644 --- a/arch/arm/mach-s3c6400/leds-s3c6400.c +++ b/arch/arm/mach-s3c6400/leds-s3c6400.c @@ -19,7 +19,7 @@ #include #include -#include +#include #include "leds.h" diff --git a/arch/arm/mach-s3c6400/leds.c b/arch/arm/mach-s3c6400/leds.c index 557bd42..33fe606 100644 --- a/arch/arm/mach-s3c6400/leds.c +++ b/arch/arm/mach-s3c6400/leds.c @@ -22,12 +22,12 @@ #include #include -#include +#include #include #include #include "leds.h" -static irqreturn_t eint9_switch(int irq, void *dev_id, struct pt_regs *regs) +static irqreturn_t eint9_switch(int irq, void *dev_id) { printk("EINT9 interrupt occures!!!\n"); @@ -47,25 +47,25 @@ s3c6400_leds_init(void) { /*GPN12~15 used for LED*/ /*Set GPN12~15 to output mode */ - gpio_direction_output(S3C_GPN12); + gpio_direction_output(S3C_GPN12, 1); if (gpio_get_pin(S3C_GPN12) == 0) { printk(KERN_WARNING "LED: can't set GPN12 output mode\n"); } - gpio_direction_output(S3C_GPN13); + gpio_direction_output(S3C_GPN13, 1); if (gpio_get_pin(S3C_GPN13) == 0) { printk(KERN_WARNING "LED: can't set GPN13 output mode\n"); } - gpio_direction_output(S3C_GPN14); + gpio_direction_output(S3C_GPN14, 1); if (gpio_get_pin(S3C_GPN14) == 0) { printk(KERN_WARNING "LED: can't set GPN14 output mode\n"); } - gpio_direction_output(S3C_GPN15); + gpio_direction_output(S3C_GPN15, 1); if (gpio_get_pin(S3C_GPN15) == 0) { printk(KERN_WARNING "LED: can't set GPN15 output mode\n"); diff --git a/arch/arm/mach-s3c6400/mach-smdk6400.c b/arch/arm/mach-s3c6400/mach-smdk6400.c index 6284fd7..e76a10d 100644 --- a/arch/arm/mach-s3c6400/mach-smdk6400.c +++ b/arch/arm/mach-s3c6400/mach-smdk6400.c @@ -65,14 +65,6 @@ #include #include -#if defined(CONFIG_RTC_DRV_S3C) -#include -#endif - -#if defined(CONFIG_USB_GADGET_S3C_OTGD_HS) || defined(CONFIG_USB_OHCI_HCD) -#include -#endif - #include #include @@ -146,6 +138,46 @@ static struct s3c2410_uartcfg smdk6400_uartcfgs[] = { }; +static struct resource s3c64xx_i2c0_resource[] = { + [0] = { + .start = S3C6400_PA_IIC0, + .end = S3C6400_PA_IIC0 + S3C24XX_SZ_IIC - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_IIC0, + .end = IRQ_IIC0, + .flags = IORESOURCE_IRQ, + } +}; + +static struct platform_device s3c64xx_device_i2c0 = { + .name = "s3c64xx-i2c", + .id = 0, /* This is so the driver gets forced to use bus 0. */ + .num_resources = ARRAY_SIZE(s3c64xx_i2c0_resource), + .resource = s3c64xx_i2c0_resource, +}; + +static struct resource s3c64xx_i2c1_resource[] = { + [0] = { + .start = S3C6400_PA_IIC1, + .end = S3C6400_PA_IIC1 + S3C24XX_SZ_IIC - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_IIC1, + .end = IRQ_IIC1, + .flags = IORESOURCE_IRQ, + } +}; + +static struct platform_device s3c64xx_device_i2c1 = { + .name = "s3c64xx-i2c", + .id = 1, /* This is so the driver gets forced to use bus 0. */ + .num_resources = ARRAY_SIZE(s3c64xx_i2c1_resource), + .resource = s3c64xx_i2c1_resource, +}; + /*add devices as drivers are integrated*/ @@ -155,7 +187,8 @@ static struct platform_device *smdk6400_devices[] __initdata = { &s3c_device_iis, &s3c_device_adc, &s3c_device_ts, - &s3c_device_i2c, + &s3c64xx_device_i2c0, + //&s3c64xx_device_i2c1, &s3c_device_usb, &s3c_device_usbgadget, &s3c_device_usb_otghcd, @@ -467,7 +500,9 @@ struct s3c_hsmmc_cfg s3c_hsmmc2_platform = { }, }; -#if defined(CONFIG_RTC_DRV_S3C) +#if defined(CONFIG_RTC_DRV_S3C) || defined(CONFIG_RTC_DRV_S3C_MODULE) +#include + /* rtc function */ unsigned int s3c_rtc_set_bit_byte(void __iomem *base, uint offset, uint val) { @@ -475,11 +510,13 @@ unsigned int s3c_rtc_set_bit_byte(void __iomem *base, uint offset, uint val) return 0; } +EXPORT_SYMBOL(s3c_rtc_set_bit_byte); unsigned int s3c_rtc_read_alarm_status(void __iomem *base) { return 1; } +EXPORT_SYMBOL(s3c_rtc_read_alarm_status); void s3c_rtc_set_pie(void __iomem *base, uint to) { @@ -493,6 +530,7 @@ void s3c_rtc_set_pie(void __iomem *base, uint to) writew(tmp, base + S3C2410_RTCCON); } +EXPORT_SYMBOL(s3c_rtc_set_pie); void s3c_rtc_set_freq_regs(void __iomem *base, uint freq, uint *s3c_freq) { @@ -504,6 +542,7 @@ void s3c_rtc_set_freq_regs(void __iomem *base, uint freq, uint *s3c_freq) tmp = (32768 / freq)-1; writew(tmp, base + S3C2410_TICNT); } +EXPORT_SYMBOL(s3c_rtc_set_freq_regs); void s3c_rtc_enable_set(struct platform_device *pdev,void __iomem *base, int en) { @@ -536,9 +575,13 @@ void s3c_rtc_enable_set(struct platform_device *pdev,void __iomem *base, int en) } } } +EXPORT_SYMBOL(s3c_rtc_enable_set); #endif -#if defined(CONFIG_USB_GADGET_S3C_OTGD_HS) || defined(CONFIG_USB_OHCI_HCD) +#if defined(CONFIG_USB_GADGET_S3C_OTGD_HS) || \ + defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) +#include + /* Initializes OTG Phy. */ void otg_phy_init(u32 otg_phy_clk) { @@ -561,7 +604,7 @@ void otg_phy_off(void) { } #endif -#ifdef CONFIG_USB_OHCI_HCD +#if defined(CONFIG_USB_OHCI_HCD) || defined(CONFIG_USB_OHCI_HCD_MODULE) void usb_host_clk_en(int usb_host_clksrc, u32 otg_phy_clk) { switch (usb_host_clksrc) { case 0: /* epll clk */ @@ -591,6 +634,7 @@ void usb_host_clk_en(int usb_host_clksrc, u32 otg_phy_clk) { writel(readl(S3C_SCLK_GATE)|S3C_CLKCON_SCLK_UHOST, S3C_SCLK_GATE); } +EXPORT_SYMBOL(usb_host_clk_en); #endif /* smdk6410 adc common function */ diff --git a/arch/arm/mach-s3c6410/Makefile b/arch/arm/mach-s3c6410/Makefile index 7e65ecb..287c6a6 100644 --- a/arch/arm/mach-s3c6410/Makefile +++ b/arch/arm/mach-s3c6410/Makefile @@ -5,7 +5,7 @@ # Object file lists. obj-y := -led-y := leds.o +led-y := obj-n := obj- := @@ -20,7 +20,7 @@ obj-$(CONFIG_S3C6410_PM) += pm.o # Power Management support # LEDs support -led-$(CONFIG_MACH_SMDK6410) += leds-s3c6410.o +led-$(CONFIG_MACH_SMDK6410) += leds.o leds-s3c6410.o obj-$(CONFIG_LEDS) += $(led-y) # PWM Support diff --git a/arch/arm/mach-s3c6410/clock.c b/arch/arm/mach-s3c6410/clock.c index 235c07a..1268a73 100644 --- a/arch/arm/mach-s3c6410/clock.c +++ b/arch/arm/mach-s3c6410/clock.c @@ -56,7 +56,7 @@ #include #endif -#define HCLK_GATING_ON_LIST S3C_CLKCON_HCLK_BUS | S3C_CLKCON_HCLK_DDR1 | S3C_CLKCON_HCLK_DDR0 |\ +#define HCLK_GATING_ON_LIST /*S3C_CLKCON_HCLK_BUS |*/ S3C_CLKCON_HCLK_DDR1 | S3C_CLKCON_HCLK_DDR0 |\ S3C_CLKCON_HCLK_MEM1 | S3C_CLKCON_HCLK_MEM0 | S3C_CLKCON_HCLK_DMA0 |\ S3C_CLKCON_HCLK_DMA1 | S3C_CLKCON_HCLK_INTC | S3C_CLKCON_HCLK_LCD |\ S3C_CLKCON_HCLK_DHOST | S3C_CLKCON_HCLK_POST0 | S3C_CLKCON_HCLK_MFC @@ -349,7 +349,7 @@ static unsigned long s3c6410_getrate_DOUTmpll_hsmmc_clk(struct clk *clk) { unsigned long parent_rate = clk_get_rate(clk->parent); unsigned long div = readl(S3C_CLK_DIV1); - div &= ~S3C_CLKDIV1_HSMMCDIV_MASK; + div &= ~S3C_CLKDIV1_HSMMCDIV0_MASK; /* MMC_RATIO = 2+1 */ div |= 0x2; @@ -373,6 +373,22 @@ static unsigned long s3c6410_getrate_DOUTmpll_hsmmc1_clk(struct clk *clk) return parent_rate / (0x2 + 1); } +static unsigned long s3c6410_getrate_DOUTmpll_hsmmc2_clk(struct clk *clk) +{ + unsigned long parent_rate = clk_get_rate(clk->parent); + unsigned long div = readl(S3C_CLK_DIV1); + div &= ~S3C_CLKDIV1_HSMMCDIV2_MASK; + + /* MMC1_RATIO = 2+1 */ + div |= (0x2< #include -#include +#include #include "leds.h" diff --git a/arch/arm/mach-s3c6410/leds.c b/arch/arm/mach-s3c6410/leds.c index 8113540..07435b9 100644 --- a/arch/arm/mach-s3c6410/leds.c +++ b/arch/arm/mach-s3c6410/leds.c @@ -22,12 +22,12 @@ #include #include -#include +#include #include #include #include "leds.h" -static irqreturn_t eint9_switch(int irq, void *dev_id, struct pt_regs *regs) +static irqreturn_t eint9_switch(int irq, void *dev_id) { printk("EINT9 interrupt occures!!!\n"); @@ -47,25 +47,25 @@ s3c6410_leds_init(void) { /*GPN12~15 used for LED*/ /*Set GPN12~15 to output mode */ - gpio_direction_output(S3C_GPN12); + gpio_direction_output(S3C_GPN12, 1); if(gpio_get_pin(S3C_GPN12) == 0) { printk(KERN_WARNING "LED: can't set GPN12 output mode\n"); } - gpio_direction_output(S3C_GPN13); + gpio_direction_output(S3C_GPN13, 1); if(gpio_get_pin(S3C_GPN13) == 0) { printk(KERN_WARNING "LED: can't set GPN13 output mode\n"); } - gpio_direction_output(S3C_GPN14); + gpio_direction_output(S3C_GPN14, 1); if(gpio_get_pin(S3C_GPN14) == 0) { printk(KERN_WARNING "LED: can't set GPN14 output mode\n"); } - gpio_direction_output(S3C_GPN15); + gpio_direction_output(S3C_GPN15, 1); if(gpio_get_pin(S3C_GPN15) == 0) { printk(KERN_WARNING "LED: can't set GPN15 output mode\n"); diff --git a/arch/arm/mach-s3c6410/mach-smdk6410.c b/arch/arm/mach-s3c6410/mach-smdk6410.c index ec86f3e..74dadd1 100644 --- a/arch/arm/mach-s3c6410/mach-smdk6410.c +++ b/arch/arm/mach-s3c6410/mach-smdk6410.c @@ -80,6 +80,10 @@ #include #include +#include //HHTECH wk +#include //HHTECH wk +#include +#include extern struct sys_timer s3c_timer; @@ -264,6 +268,138 @@ static struct i2c_board_info i2c_devs1[] __initdata = { { I2C_BOARD_INFO("24c128", 0x57), }, }; +#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE) +static struct resource dm9000_resources[] = { + [0] = { + .start = S3C24XX_VA_CS8900 + 0x300, + .end = S3C24XX_VA_CS8900 + S3C24XX_SZ_CS8900 - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_EINT10, + .end = IRQ_EINT10, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device s3c_device_dm9000 = { + .name = "dm9000", + .id = -1, + .num_resources = ARRAY_SIZE(dm9000_resources), + .resource = dm9000_resources, +}; +#endif +/////////////////////////////////////////////////////HHTECH wk//////////////// +/* + * GPIO Buttons + */ +#if defined(CONFIG_KEYBOARD_GPIO) || defined(CONFIG_KEYBOARD_GPIO_MODULE) +//static struct gpio_keys_button hhmid_buttons[] __initdata = { +static struct gpio_keys_button hhmid_buttons[] = { +#if defined (CONFIG_LCD_4) + { + .gpio = S3C_GPN2, //32*16 + 2 + .code = KEY_PAGEDOWN, + .desc = "Button 0", + .active_low = 1, + .debounce_interval = 5, + }, + { + .gpio = S3C_GPN12, + .code = KEY_PAGEUP, + .desc = "Button 3", + .active_low = 1, + .debounce_interval = 5, + }, + { + .gpio = S3C_GPN15, + .code = KEY_LEFTALT, + .desc = "Button 4", + .active_low = 1, + .debounce_interval = 5, + }, +#else + { + .gpio = S3C_GPN4, + .code = KEY_PAGEUP, + .desc = "Button 0", + .active_low = 1, + .debounce_interval = 5, + }, + { + .gpio = S3C_GPN3, + .code = KEY_PAGEDOWN, + .desc = "Button 1", + .active_low = 1, + .debounce_interval = 5, + }, + { + .gpio = S3C_GPN2, //32*16 + 2 + .code = KEY_LEFTALT, + .desc = "Button 2", + .active_low = 1, + .debounce_interval = 5, + }, + { + .gpio = S3C_GPN15, + .code = KEY_ENTER, + .desc = "Button 3", + .active_low = 1, + .debounce_interval = 5, + }, + { + .gpio = S3C_GPN12, + .code = KEY_ESC, + .desc = "Button 4", + .active_low = 1, + .debounce_interval = 5, + }, +#endif +}; + +//static struct gpio_keys_platform_data hhmid_button_data __initdata = { +static struct gpio_keys_platform_data hhmid_button_data = { + .buttons = hhmid_buttons, + .nbuttons = ARRAY_SIZE(hhmid_buttons), +}; + +static struct platform_device hhmid_button_device = { + .name = "gpio-keys", + .id = 0, + .num_resources = 0, + .dev = { + .platform_data = &hhmid_button_data, + } +}; + +//************************************************************************************* +static struct gpio_keys_button hhmid_buttons_2[] = { + { + .gpio = S3C_GPL14, + .code = KEY_POWER, + .desc = "Button 5", + .active_low = 1, + .debounce_interval = 5, + }, + +}; +static struct gpio_keys_platform_data hhmid_button_data_2 = { + .buttons = hhmid_buttons_2, + .nbuttons = ARRAY_SIZE(hhmid_buttons_2), + .rep = 1, +}; + +static struct platform_device hhmid_button_device_2 = { + .name = "gpio-keys", + .id = 1, + .num_resources = 0, + .dev = { + .platform_data = &hhmid_button_data_2, + } +}; +#endif +/* HHTECH wk */ + /* Add devices as drivers are integrated */ static struct platform_device *smdk6410_devices[] __initdata = { &s3c_device_rtc, @@ -273,13 +409,16 @@ static struct platform_device *smdk6410_devices[] __initdata = { &s3c_device_adc, &s3c64xx_device_i2c0, &s3c64xx_device_i2c1, + &hhmid_button_device, + &hhmid_button_device_2, + &s3c_gpio, &s3c_device_usb, &s3c_device_usbgadget, &s3c_device_usb_otghcd, &s3c_device_tvenc, &s3c_device_tvscaler, + &s3c_device_hsmmc1, /* INAND initialize before SD card */ &s3c_device_hsmmc0, - &s3c_device_hsmmc1, &s3c_device_hsmmc2, &s3c_device_wdt, &s3c_device_jpeg, @@ -294,11 +433,33 @@ static struct platform_device *smdk6410_devices[] __initdata = { &s3c_device_mfc, &s3c_device_g3d, &s3c_device_rotator, +#if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE) + &s3c_device_dm9000, +#endif }; +extern void s3cfb_clear_lcd(int flag); + +static void s3c6410_power_off(void) +{ + int dc_status = 0; +#if defined (CONFIG_LCD_4) + dc_status = gpio_get_value(GPIO_DC_DETE); +#else + dc_status = !gpio_get_value(GPIO_DC_DETE); +#endif + + if(dc_status) + machine_restart(NULL); + else { + s3cfb_clear_lcd(0); // clear the LCD screen to white + gpio_direction_output(S3C_GPK15, 1); + } +} static void __init smdk6410_map_io(void) { + pm_power_off = s3c6410_power_off; s3c24xx_init_io(smdk6410_iodesc, ARRAY_SIZE(smdk6410_iodesc)); s3c24xx_init_clocks(0); s3c24xx_init_uarts(smdk6410_uartcfgs, ARRAY_SIZE(smdk6410_uartcfgs)); @@ -366,14 +527,16 @@ static void __init smdk6410_fixup (struct machine_desc *desc, struct tag *tags, static void smdk6410_hsmmc_init (void) { /* hsmmc data strength */ - writel(readl(S3C_SPCON) | (0x3 << 26), S3C_SPCON); + writel(readl(S3C_SPCON) | (0x3 << 26) | (0x3 << 18), S3C_SPCON); /* jsgood: hsmmc0/1 card detect pin should be high before setup gpio. (GPG6 to Input) */ writel(readl(S3C_GPGCON) & 0xf0ffffff, S3C_GPGCON); + #if 0 /* GPIO N 13 (external interrupt) : Chip detect */ gpio_set_pin(S3C_GPN13, S3C_GPN13_EXTINT13); /* GPN13 to EINT13 */ gpio_pullup(S3C_GPN13, 0x2); /* Pull-up Enable */ + #endif /* jsgood: MUXmmc# to DOUTmpll for MPLL Clock Source */ writel((readl(S3C_CLK_SRC) & ~(0x3f << 18)) | (0x15 << 18), S3C_CLK_SRC); @@ -433,7 +596,6 @@ static void __init smdk6410_machine_init (void) } smdk_machine_init(); - smdk6410_hsmmc_init(); smdk6410_set_qos(); } @@ -473,7 +635,7 @@ void hsmmc_set_gpio (uint channel, uint width) gpio_pullup(S3C_GPG1, 0x0); /* Pull-up/down disable */ /* GPIO G : Chip detect + LED */ - gpio_set_pin(S3C_GPG6, S3C_GPG6_MMC_CD1); + gpio_set_pin(S3C_GPG6, S3C_GPG6_MMC_CD0); gpio_pullup(S3C_GPG6, 0x2); /* Pull-up Enable */ if (width == 1) { @@ -506,8 +668,10 @@ void hsmmc_set_gpio (uint channel, uint width) gpio_pullup(S3C_GPH1, 0x0); /* Pull-up/down disable */ /* GPIO G : Chip detect + LED */ + #if 0 gpio_set_pin(S3C_GPG6, S3C_GPG6_MMC_CD1); gpio_pullup(S3C_GPG6, 0x2); /* Pull-up Enable */ + #endif if (width == 1) { /* GPIO H : MMC DATA1[0] */ @@ -552,15 +716,18 @@ void hsmmc_set_gpio (uint channel, uint width) /* can supports 1 and 4 bit bus, no irq_cd */ case 2: /* GPIO H : Command, Clock */ - gpio_set_pin(S3C_GPH0, S3C_GPH0_MMC_CLK1); - gpio_set_pin(S3C_GPH1, S3C_GPH1_MMC_CMD1); - gpio_pullup(S3C_GPH0, 0x0); /* Pull-up/down disable */ - gpio_pullup(S3C_GPH1, 0x0); /* Pull-up/down disable */ + gpio_set_pin(S3C_GPC4, S3C_GPC4_MMC_CMD2); + gpio_set_pin(S3C_GPC5, S3C_GPC5_MMC_CLK2); + gpio_pullup(S3C_GPC4, 0x0); /* Pull-up/down disable */ + gpio_pullup(S3C_GPC5, 0x0); /* Pull-up/down disable */ + + #if 0 /* GPIO G : Chip detect + LED */ gpio_set_pin(S3C_GPG6, S3C_GPG6_MMC_CD1); gpio_pullup(S3C_GPG6, 0x2); /* Pull-up Enable */ + #endif if (width == 1) { /* GPIO H : MMC DATA1[0] */ @@ -580,7 +747,6 @@ void hsmmc_set_gpio (uint channel, uint width) gpio_pullup(S3C_GPH8, 0x0); /* Pull-up/down disable */ gpio_pullup(S3C_GPH9, 0x0); /* Pull-up/down disable */ } - break; default: @@ -596,10 +762,11 @@ EXPORT_SYMBOL(hsmmc_set_gpio); struct s3c_hsmmc_cfg s3c_hsmmc0_platform = { .hwport = 0, - .enabled = 0, + .enabled = 1, .host_caps = HOST_CAPS, .bus_width = 4, - .highspeed = 0, + .highspeed = 0, /* High speed for mmc channel 0, SD clock up to 50M*/ + .max_clock = 50 * 1000 * 1000, /* ctrl for mmc */ .fd_ctrl[MMC_TYPE_MMC] = { @@ -617,6 +784,13 @@ struct s3c_hsmmc_cfg s3c_hsmmc0_platform = { .ctrl4 = 0x3, }, + .fd_ctrl[MMC_TYPE_SDIO] = { + .ctrl2 = 0xC0004100, /* ctrl2 for sdio */ + .ctrl3[SPEED_NORMAL] = 0x80808080, /* ctrl3 for low speed */ + .ctrl3[SPEED_HIGH] = 0x00008080, /* ctrl3 for high speed */ + .ctrl4 = 0x3, + }, + .clocks[0] = { .name = "sclk_DOUTmpll_mmc0", .src = 0x2, @@ -632,6 +806,7 @@ struct s3c_hsmmc_cfg s3c_hsmmc1_platform = { //.bus_width = 8, .highspeed = 0, + .max_clock = 25 * 1000 * 1000, /* ctrl for mmc */ .fd_ctrl[MMC_TYPE_MMC] = { @@ -649,6 +824,13 @@ struct s3c_hsmmc_cfg s3c_hsmmc1_platform = { .ctrl4 = 0x3, }, + .fd_ctrl[MMC_TYPE_SDIO] = { + .ctrl2 = 0xC0004100, /* ctrl2 for sdio */ + .ctrl3[SPEED_NORMAL] = 0x80808080, /* ctrl3 for low speed */ + .ctrl3[SPEED_HIGH] = 0x00008080, /* ctrl3 for high speed */ + .ctrl4 = 0x3, + }, + .clocks[0] = { .name = "sclk_DOUTmpll_mmc1", .src = 0x2, @@ -657,10 +839,11 @@ struct s3c_hsmmc_cfg s3c_hsmmc1_platform = { struct s3c_hsmmc_cfg s3c_hsmmc2_platform = { .hwport = 2, - .enabled = 0, - .host_caps = HOST_CAPS, + .enabled = 1, + .host_caps = HOST_CAPS | MMC_CAP_SDIO_IRQ, .bus_width = 4, .highspeed = 0, + .max_clock = 25 * 1000 * 1000, /* ctrl for mmc */ .fd_ctrl[MMC_TYPE_MMC] = { @@ -678,6 +861,13 @@ struct s3c_hsmmc_cfg s3c_hsmmc2_platform = { .ctrl4 = 0x3, }, + .fd_ctrl[MMC_TYPE_SDIO] = { + .ctrl2 = 0xC0004100, /* ctrl2 for sdio */ + .ctrl3[SPEED_NORMAL] = 0x80808080, /* ctrl3 for low speed */ + .ctrl3[SPEED_HIGH] = 0x00008080, /* ctrl3 for high speed */ + .ctrl4 = 0x3, + }, + .clocks[0] = { .name = "sclk_DOUTmpll_mmc2", .src = 0x2, @@ -774,7 +964,6 @@ void otg_phy_init(u32 otg_phy_clk) { writel(0x0, S3C_USBOTG_RSTCON); udelay(50); } - EXPORT_SYMBOL(otg_phy_init); /* USB Control request data struct must be located here for DMA transfer */ @@ -783,10 +972,11 @@ EXPORT_SYMBOL(usb_ctrl); /* OTG PHY Power Off */ void otg_phy_off(void) { +#if 0 /* Prevent usb host can't work by BXL */ writel(readl(S3C_USBOTG_PHYPWR)|(0x1F<<1), S3C_USBOTG_PHYPWR); writel(readl(S3C_OTHERS)&~S3C_OTHERS_USB_SIG_MASK, S3C_OTHERS); +#endif } - EXPORT_SYMBOL(otg_phy_off); #endif @@ -828,7 +1018,7 @@ EXPORT_SYMBOL(usb_host_clk_en); struct s3c_adc_cfg s3c_adc_platform={ /* s3c6410 support 12-bit resolution */ .delay = 10000, - .presc = 49, + .presc = 99, .resolution = 12, }; @@ -860,9 +1050,9 @@ EXPORT_SYMBOL(s3c_adc_convert); struct s3c_ts_mach_info s3c_ts_platform = { - .delay = 10000, - .presc = 49, - .oversampling_shift = 2, + .delay = 65535, + .presc = 99, + .oversampling_shift = 4, .resol_bit = 12, }; diff --git a/arch/arm/mach-s3c6410/pwm/pwm-s3c6410.c b/arch/arm/mach-s3c6410/pwm/pwm-s3c6410.c index f6203ce..17a4fe2 100644 --- a/arch/arm/mach-s3c6410/pwm/pwm-s3c6410.c +++ b/arch/arm/mach-s3c6410/pwm/pwm-s3c6410.c @@ -93,7 +93,7 @@ int s3c6410_timer_setup (int channel, int usec, unsigned long g_tcnt, unsigned l unsigned long pclk; struct clk *clk; - printk("PWM channel %d set g_tcnt = %ld, g_tcmp = %ld \n", channel, g_tcnt, g_tcmp); + //printk("PWM channel %d set g_tcnt = %ld, g_tcmp = %ld \n", channel, g_tcnt, g_tcmp); tcnt = 0xffffffff; /* default value for tcnt */ @@ -134,7 +134,7 @@ int s3c6410_timer_setup (int channel, int usec, unsigned long g_tcnt, unsigned l tcfg1 |= S3C_TCFG1_MUX1_DIV2; tcfg0 &= ~S3C_TCFG_PRESCALER0_MASK; - tcfg0 |= (PRESCALER) << S3C_TCFG_PRESCALER0_SHIFT; + tcfg0 |= (0) << S3C_TCFG_PRESCALER0_SHIFT; tcon &= ~(7<<8); tcon |= S3C_TCON_T1RELOAD; @@ -216,6 +216,7 @@ int s3c6410_timer_setup (int channel, int usec, unsigned long g_tcnt, unsigned l return 0; } +EXPORT_SYMBOL(s3c6410_timer_setup); static irqreturn_t s3c6410_pwm_irq(int irq, void *devpw) diff --git a/arch/arm/plat-s3c24xx/devs.c b/arch/arm/plat-s3c24xx/devs.c index 10007c8..5a69455 100644 --- a/arch/arm/plat-s3c24xx/devs.c +++ b/arch/arm/plat-s3c24xx/devs.c @@ -1078,12 +1078,14 @@ static struct resource s3c_hsmmc0_resource[] = { .end = IRQ_HSMMC0, .flags = IORESOURCE_IRQ, }, + #if 0 /* To detect a card inserted, use an external interrupt */ [2] = { .start = IRQ_EINT13, .end = IRQ_EINT13, .flags = IORESOURCE_IRQ, } + #endif }; static struct resource s3c_hsmmc1_resource[] = { @@ -1110,11 +1112,13 @@ static struct resource s3c_hsmmc2_resource[] = { .end = IRQ_HSMMC2, .flags = IORESOURCE_IRQ, }, + #if 0 [2] = { .start = IRQ_EINT15, .end = IRQ_EINT15, .flags = IORESOURCE_IRQ, } + #endif }; struct platform_device s3c_device_hsmmc0 = { @@ -1453,5 +1457,12 @@ struct platform_device s3c_device_smc911x = { .resource = s3c_smc911x_resources, }; EXPORT_SYMBOL(s3c_device_smc911x); + +struct platform_device s3c_gpio = { + .name = "hhtech_gpio", + .id = -1, + .num_resources = 0, +}; +EXPORT_SYMBOL(s3c_gpio); #endif diff --git a/arch/arm/plat-s3c24xx/gpio.c b/arch/arm/plat-s3c24xx/gpio.c index 28f1dc0..b2c986b 100644 --- a/arch/arm/plat-s3c24xx/gpio.c +++ b/arch/arm/plat-s3c24xx/gpio.c @@ -377,6 +377,7 @@ unsigned int s3c_gpio_getpin(unsigned int pin) { void __iomem *base = gpio_base_offset[S3C_GPIO_BASE(pin)]; unsigned long offs = S3C_GPIO_OFFSET(pin); + unsigned long dat; //HHTECH wk if((pin>=S3C_GPH0 && pin<=S3C_GPH9)||\ (pin>=S3C_GPK0 && pin<=S3C_GPK15)||\ @@ -384,17 +385,29 @@ unsigned int s3c_gpio_getpin(unsigned int pin) { base = base+0x04; } - return __raw_readl(base + 0x04) & (1<< offs); + //return __raw_readl(base + 0x04) & (1<< offs); //HHTECH wk orign + dat = __raw_readl(base + 0x04) & (1<< offs); //HHTECH wk +// printk("get GPXDAT pin status!\n"); //HHTECH wk + return (dat >> offs); } - EXPORT_SYMBOL(s3c_gpio_getpin); int s3c_gpio_getirq(unsigned int pin) { - /* mandatory */ /* Implement this function */ - return 0; +//return 0; +// printk("gpio->irq begin *****************************\n"); //HHTECH wk + if (pin >=S3C_GPN0 && pin <=S3C_GPN15) + return ((pin-S3C_GPN0) + 64); + else + {if(pin >= S3C_GPL8 && pin <= S3C_GPL14) + return ((pin - S3C_GPL8) + 80); // include/asm-arm/arch-s3c2410/irqs.h + else + { printk("Plese implement other GPIO interrupts except GPN0~GPN15\n"); + return -1; + } + } } EXPORT_SYMBOL(s3c_gpio_getirq); diff --git a/arch/arm/plat-s3c64xx/Makefile b/arch/arm/plat-s3c64xx/Makefile index bce4072..0e442b5 100644 --- a/arch/arm/plat-s3c64xx/Makefile +++ b/arch/arm/plat-s3c64xx/Makefile @@ -28,4 +28,4 @@ obj-$(CONFIG_PM) += pm.o obj-$(CONFIG_PM) += sleep.o obj-$(CONFIG_S3C64XX_DVFS) += s3c64xx-dvfs.o obj-$(CONFIG_CPU_FREQ) += s3c64xx-cpufreq.o -obj-$(CONFIG_CPU_FREQ) += ltc3714.o +#obj-$(CONFIG_CPU_FREQ) += ltc3714.o diff --git a/arch/arm/plat-s3c64xx/irq-pl192.c b/arch/arm/plat-s3c64xx/irq-pl192.c index 52d0234..e078316 100644 --- a/arch/arm/plat-s3c64xx/irq-pl192.c +++ b/arch/arm/plat-s3c64xx/irq-pl192.c @@ -270,6 +270,61 @@ s3c_irqext_type(unsigned int irq, unsigned int type) } switch (irq) { +////////////////////////////////HHTECH wk ////////////////////////////////// + + case IRQ_EINT0: + gpio_set_pin(S3C_GPN0, S3C_GPN0_EXTINT0); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 0)) | + (newvalue << 0), S3C_EINTCON0); + break; + + case IRQ_EINT1: + gpio_set_pin(S3C_GPN1, S3C_GPN1_EXTINT1); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0x7 << 0)) | + (newvalue << 0), S3C_EINTCON0); + break; + + case IRQ_EINT2: + gpio_set_pin(S3C_GPN2, S3C_GPN2_EXTINT2); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 4)) | + (newvalue << 4), S3C_EINTCON0); + break; + + case IRQ_EINT3: + gpio_set_pin(S3C_GPN3, S3C_GPN3_EXTINT3); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 4)) | + (newvalue << 4), S3C_EINTCON0); + break; + + case IRQ_EINT4: + gpio_set_pin(S3C_GPN4, S3C_GPN4_EXTINT4); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 8)) | + (newvalue << 8), S3C_EINTCON0); + break; + case IRQ_EINT5: + gpio_set_pin(S3C_GPN5, S3C_GPN5_EXTINT5); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 8)) | + (newvalue << 8), S3C_EINTCON0); + break; + case IRQ_EINT6: + gpio_set_pin(S3C_GPN6, S3C_GPN6_EXTINT6); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 12)) | + (newvalue << 12), S3C_EINTCON0); + break; + + case IRQ_EINT7: + gpio_set_pin(S3C_GPN7, S3C_GPN7_EXTINT7); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 12)) | + (newvalue << 12), S3C_EINTCON0); + break; + + case IRQ_EINT8: + gpio_set_pin(S3C_GPN8, S3C_GPN8_EXTINT8); + __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 16)) | + (newvalue << 16), S3C_EINTCON0); + break; +////////////////////////////////HHTECH wk ////////////////////////////////// + case IRQ_EINT9: gpio_set_pin(S3C_GPN9, S3C_GPN9_EXTINT9); __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 16)) | @@ -293,26 +348,39 @@ s3c_irqext_type(unsigned int irq, unsigned int type) (newvalue << 24), S3C_EINTCON0); break; - case IRQ_EINT13: - gpio_set_pin(S3C_GPN13, S3C_GPN13_EXTINT13); - __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 24)) | - (newvalue << 24), S3C_EINTCON0); - break; - - case IRQ_EINT14: - gpio_set_pin(S3C_GPN14, S3C_GPN14_EXTINT14); - __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 28)) | - (newvalue << 28), S3C_EINTCON0); - break; - case IRQ_EINT15: gpio_set_pin(S3C_GPN15, S3C_GPN15_EXTINT15); __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 28)) | (newvalue << 28), S3C_EINTCON0); break; +//////////////////////////////////////////////////////////////// + case IRQ_EINT18: + gpio_set_pin(S3C_GPL10, S3C_GPL10_EXTINT18); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0xf << 4)) | + (newvalue << 4), S3C_EINTCON1); + break; + + case IRQ_EINT19: + gpio_set_pin(S3C_GPL11, S3C_GPL11_EXTINT19); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0xf << 4)) | + (newvalue << 4), S3C_EINTCON1); + break; + + case IRQ_EINT20: + gpio_set_pin(S3C_GPL12, S3C_GPL12_EXTINT20); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0xf << 8)) | + (newvalue << 8), S3C_EINTCON1); + break; + + case IRQ_EINT22: + gpio_set_pin(S3C_GPL14, S3C_GPL14_EXTINT22); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0xf << 12)) | + (newvalue << 12), S3C_EINTCON1); + break; +///////////////////////////////////////////////////////////////////////// default: printk(KERN_ERR - "s3c_irqext_type : Only support EINT9,10,11,12,13,14,15 interrupt.\n"); + "ttttttttttttttttttttt s3c_irqext_type : Only support EINT9,10,11,12,13,14,15,22 interrupt.\n"); break; } diff --git a/arch/arm/plat-s3c64xx/pm.c b/arch/arm/plat-s3c64xx/pm.c index 08efe07..8f8ce9a 100644 --- a/arch/arm/plat-s3c64xx/pm.c +++ b/arch/arm/plat-s3c64xx/pm.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,8 @@ #include #include +#include +#include /* for external use */ @@ -237,6 +240,22 @@ static struct sleep_save sromc_save[] = { SAVE_ITEM(S3C_SROM_BC5), }; +static struct sleep_save clock_save[] = { + SAVE_ITEM(S3C_APLL_CON), + SAVE_ITEM(S3C_MPLL_CON), + SAVE_ITEM(S3C_EPLL_CON0), + SAVE_ITEM(S3C_EPLL_CON1), + SAVE_ITEM(S3C_CLK_SRC), + SAVE_ITEM(S3C_CLK_SRC2), + SAVE_ITEM(S3C_CLK_DIV0), + SAVE_ITEM(S3C_CLK_DIV1), + SAVE_ITEM(S3C_CLK_DIV2), + SAVE_ITEM(S3C_CLK_OUT), + SAVE_ITEM(S3C_HCLK_GATE), + SAVE_ITEM(S3C_PCLK_GATE), + SAVE_ITEM(S3C_SCLK_GATE), +}; + #endif #ifdef CONFIG_S3C2410_PM_DEBUG @@ -603,26 +622,46 @@ static void s3c2410_pm_show_resume_irqs(int start, unsigned long which, extern int s3c_irqext_type(unsigned int irq, unsigned int type); extern void s3c_irqext_unmaskack(unsigned int irqno); #endif +extern void s3c_rtc_setaie(int to); +extern int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm); +extern int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm); +extern unsigned long rtc_wakeup_time; static void s3c6410_pm_configure_extint(void) { + int dc_status; + struct timeval tv; + unsigned long now_seconds, alrm_seconds; + struct rtc_time now_time, alrm_time; + struct rtc_wkalrm wkalrm; + +#if defined (CONFIG_LCD_4) + dc_status = gpio_get_value(GPIO_DC_DETE) ? 1 : 0; +#else + dc_status = gpio_get_value(GPIO_DC_DETE) ? 0 : 1; +#endif - /* for each of the external interrupts (EINT0..EINT15) we - * need to check wether it is an external interrupt source, - * and then configure it as an input if it is not - */ - - gpio_set_pin(S3C_GPN10, S3C_GPN10_EXTINT10); - gpio_pullup(S3C_GPN10, 2); - udelay(50); - - __raw_writel((__raw_readl(S3C_EINTCON0) & ~(0x7 << 20)) | - (S3C_EXTINT_FALLEDGE << 20), S3C_EINTCON0); - - __raw_writel(1UL << (IRQ_EINT10 - IRQ_EINT0), S3C_EINTPEND); - __raw_writel(__raw_readl(S3C_EINTMASK)&~(1UL << (IRQ_EINT10 - IRQ_EINT0)), S3C_EINTMASK); + if(0 == dc_status) { + /* make system wake up after rtc_wakeup_time seconds by RTC alarm */ + s3c_rtc_gettime(NULL, &now_time); + rtc_tm_to_time(&now_time, &now_seconds); + alrm_seconds = now_seconds + rtc_wakeup_time; + rtc_time_to_tm(alrm_seconds, &alrm_time); + wkalrm.enabled = 1; + wkalrm.pending = 0; + wkalrm.time = alrm_time; + s3c_rtc_setalarm(NULL, &wkalrm); + s3c_rtc_setaie(1); + } - __raw_writel((0x0fffffff&~(3<<9)), S3C_EINT_MASK); + /* make the POWER key: GPL14 the wakeup source, but the other keys are excluded */ + set_irq_wake(IRQ_EINT12, 0); // GPN12 is excluded + set_irq_wake(IRQ_EINT15, 0); // GPN15 is excluded + set_irq_wake(IRQ_EINT2, 0); // GPN2 is excluded +#if defined (CONFIG_LCD_7) + set_irq_wake(IRQ_EINT3, 0); // GPN3 is excluded + set_irq_wake(IRQ_EINT4, 0); // GPN4 is excluded +#endif } void (*pm_cpu_prep)(void); @@ -654,10 +693,12 @@ static int s3c6410_pm_enter(suspend_state_t state) s3c2410_sleep_save_phys = virt_to_phys(regs_save); /* save all necessary core registers not covered by the drivers */ + //gpio_direction_output(S3C_GPF15, 0); // close the backlight of LCD s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save)); s3c2410_pm_do_save(irq_save, ARRAY_SIZE(irq_save)); s3c2410_pm_do_save(core_save, ARRAY_SIZE(core_save)); s3c2410_pm_do_save(sromc_save, ARRAY_SIZE(sromc_save)); + s3c2410_pm_do_save(clock_save, ARRAY_SIZE(clock_save)); /* ensure INF_REG0 has the resume address */ __raw_writel(virt_to_phys(s3c2410_cpu_resume), S3C_INFORM0); @@ -721,6 +762,7 @@ static int s3c6410_pm_enter(suspend_state_t state) cpu_init(); /* restore the system state */ + s3c2410_pm_do_restore(clock_save, ARRAY_SIZE(clock_save)); s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); s3c2410_pm_do_restore(sromc_save, ARRAY_SIZE(sromc_save)); s3c2410_pm_do_restore(gpio_save, ARRAY_SIZE(gpio_save)); @@ -733,6 +775,14 @@ static int s3c6410_pm_enter(suspend_state_t state) s3c2410_pm_check_restore(); + /* Enable all the keys function after waken up from sleep mode */ + set_irq_wake(IRQ_EINT12, 1); + set_irq_wake(IRQ_EINT15, 1); + set_irq_wake(IRQ_EINT2, 1); +#if defined (CONFIG_LCD_7) + set_irq_wake(IRQ_EINT3, 1); + set_irq_wake(IRQ_EINT4, 1); +#endif /* ok, let's return from sleep */ DBG("S3C6400 PM Resume (post-restore)\n"); return 0; diff --git a/arch/arm/plat-s3c64xx/s3c64xx-cpufreq.c b/arch/arm/plat-s3c64xx/s3c64xx-cpufreq.c index 2e10ea9..a509071 100644 --- a/arch/arm/plat-s3c64xx/s3c64xx-cpufreq.c +++ b/arch/arm/plat-s3c64xx/s3c64xx-cpufreq.c @@ -27,9 +27,10 @@ #include #define USE_FREQ_TABLE -#define USE_DVS +//#define USE_DVS #define VERY_HI_RATE 532*1000*1000 #define APLL_GEN_CLK 532*1000 //khz +#define APLL_GEN_CLK2 400*1000 //khz #define KHZ_T 1000 #define MPU_CLK "fclk" @@ -43,9 +44,13 @@ extern int ltc3714_init(void); /* frequency */ static struct cpufreq_frequency_table s3c6410_freq_table[] = { + {APLL_GEN_CLK, 667*1000}, {APLL_GEN_CLK, APLL_GEN_CLK}, + {APLL_GEN_CLK, APLL_GEN_CLK2}, {APLL_GEN_CLK, APLL_GEN_CLK/2}, + {APLL_GEN_CLK, APLL_GEN_CLK2/2}, {APLL_GEN_CLK, APLL_GEN_CLK/4}, + {APLL_GEN_CLK, APLL_GEN_CLK2/4}, {0, CPUFREQ_TABLE_END}, }; diff --git a/arch/arm/vfp/entry.S b/arch/arm/vfp/entry.S index 806ce26..ba592a9 100644 --- a/arch/arm/vfp/entry.S +++ b/arch/arm/vfp/entry.S @@ -21,13 +21,13 @@ #include #include - .globl do_vfp -do_vfp: +ENTRY(do_vfp) enable_irq ldr r4, .LCvfp ldr r11, [r10, #TI_CPU] @ CPU number add r10, r10, #TI_VFPSTATE @ r10 = workspace ldr pc, [r4] @ call VFP entry point +ENDPROC(do_vfp) ENTRY(vfp_null_entry) mov pc, lr @@ -40,11 +40,11 @@ ENDPROC(vfp_null_entry) @ failure to the VFP initialisation code. __INIT - .globl vfp_testing_entry -vfp_testing_entry: +ENTRY(vfp_testing_entry) ldr r0, VFP_arch_address str r5, [r0] @ known non-zero value mov pc, r9 @ we have handled the fault +ENDPROC(vfp_testing_entry) VFP_arch_address: .word VFP_arch diff --git a/arch/arm/vfp/vfp.h b/arch/arm/vfp/vfp.h index 791d023..8de86e4 100644 --- a/arch/arm/vfp/vfp.h +++ b/arch/arm/vfp/vfp.h @@ -265,7 +265,11 @@ struct vfp_double { * which returns (double)0.0. This is useful for the compare with * zero instructions. */ +#ifdef CONFIG_VFPv3 +#define VFP_REG_ZERO 32 +#else #define VFP_REG_ZERO 16 +#endif extern u64 vfp_get_double(unsigned int reg); extern void vfp_put_double(u64 val, unsigned int reg); @@ -373,6 +377,6 @@ struct op { u32 flags; }; -#ifdef CONFIG_SMP +#if defined(CONFIG_SMP) || defined(CONFIG_PM) extern void vfp_save_state(void *location, u32 fpexc); #endif diff --git a/arch/arm/vfp/vfphw.S b/arch/arm/vfp/vfphw.S index 0ac022f..c92a08b 100644 --- a/arch/arm/vfp/vfphw.S +++ b/arch/arm/vfp/vfphw.S @@ -68,8 +68,7 @@ @ r11 = CPU number @ lr = failure return - .globl vfp_support_entry -vfp_support_entry: +ENTRY(vfp_support_entry) DBGSTR3 "instr %08x pc %08x state %p", r0, r2, r10 VFPFMRX r1, FPEXC @ Is the VFP enabled? @@ -99,12 +98,15 @@ vfp_support_entry: DBGSTR1 "save old state %p", r4 cmp r4, #0 beq no_old_VFP_process + VFPFSTMIA r4, r5 @ save the working registers VFPFMRX r5, FPSCR @ current status - VFPFMRX r6, FPINST @ FPINST (always there, rev0 onwards) - tst r1, #FPEXC_FPV2 @ is there an FPINST2 to read? - VFPFMRX r8, FPINST2, NE @ FPINST2 if needed - avoids reading - @ nonexistant reg on rev0 - VFPFSTMIA r4 @ save the working registers + tst r1, #FPEXC_EX @ is there additional state to save? + beq 1f + VFPFMRX r6, FPINST @ FPINST (only if FPEXC.EX is set) + tst r1, #FPEXC_FP2V @ is there an FPINST2 to read? + beq 1f + VFPFMRX r8, FPINST2 @ FPINST2 if needed (and present) +1: stmia r4, {r1, r5, r6, r8} @ save FPEXC, FPSCR, FPINST, FPINST2 @ and point r4 at the word at the @ start of the register dump @@ -114,13 +116,16 @@ no_old_VFP_process: DBGSTR1 "load state %p", r10 str r10, [r3, r11, lsl #2] @ update the last_VFP_context pointer @ Load the saved state back into the VFP - VFPFLDMIA r10 @ reload the working registers while + VFPFLDMIA r10, r5 @ reload the working registers while @ FPEXC is in a safe state ldmia r10, {r1, r5, r6, r8} @ load FPEXC, FPSCR, FPINST, FPINST2 - tst r1, #FPEXC_FPV2 @ is there an FPINST2 to write? - VFPFMXR FPINST2, r8, NE @ FPINST2 if needed - avoids writing - @ nonexistant reg on rev0 - VFPFMXR FPINST, r6 + tst r1, #FPEXC_EX @ is there additional state to restore? + beq 1f + VFPFMXR FPINST, r6 @ restore FPINST (only if FPEXC.EX is set) + tst r1, #FPEXC_FP2V @ is there an FPINST2 to write? + beq 1f + VFPFMXR FPINST2, r8 @ FPINST2 if needed (and present) +1: VFPFMXR FPSCR, r5 @ restore status check_for_exception: @@ -136,10 +141,14 @@ check_for_exception: look_for_VFP_exceptions: - tst r1, #FPEXC_EX + @ Check for synchronous or asynchronous exception + tst r1, #FPEXC_EX | FPEXC_DEX bne process_exception + @ On some implementations of the VFP subarch 1, setting FPSCR.IXE + @ causes all the CDP instructions to be bounced synchronously without + @ setting the FPEXC.EX bit VFPFMRX r5, FPSCR - tst r5, #FPSCR_IXE @ IXE doesn't set FPEXC_EX ! + tst r5, #FPSCR_IXE bne process_exception @ Fall into hand on to next handler - appropriate coproc instr @@ -150,10 +159,6 @@ look_for_VFP_exceptions: process_exception: DBGSTR "bounce" - sub r2, r2, #4 - str r2, [sp, #S_PC] @ retry the instruction on exit from - @ the imprecise exception handling in - @ the support code mov r2, sp @ nothing stacked - regdump is at TOS mov lr, r9 @ setup for a return to the user code. @@ -161,34 +166,36 @@ process_exception: @ r0 holds the trigger instruction @ r1 holds the FPEXC value @ r2 pointer to register dump - b VFP9_bounce @ we have handled this - the support + b VFP_bounce @ we have handled this - the support @ code will raise an exception if @ required. If not, the user code will @ retry the faulted instruction +ENDPROC(vfp_support_entry) -#ifdef CONFIG_SMP - .globl vfp_save_state - .type vfp_save_state, %function -vfp_save_state: +#if defined(CONFIG_SMP) || defined(CONFIG_PM) +ENTRY(vfp_save_state) @ Save the current VFP state @ r0 - save location @ r1 - FPEXC DBGSTR1 "save VFP state %p", r0 + VFPFSTMIA r0, r2 @ save the working registers VFPFMRX r2, FPSCR @ current status - VFPFMRX r3, FPINST @ FPINST (always there, rev0 onwards) - tst r1, #FPEXC_FPV2 @ is there an FPINST2 to read? - VFPFMRX r12, FPINST2, NE @ FPINST2 if needed - avoids reading - @ nonexistant reg on rev0 - VFPFSTMIA r0 @ save the working registers + tst r1, #FPEXC_EX @ is there additional state to save? + beq 1f + VFPFMRX r3, FPINST @ FPINST (only if FPEXC.EX is set) + tst r1, #FPEXC_FP2V @ is there an FPINST2 to read? + beq 1f + VFPFMRX r12, FPINST2 @ FPINST2 if needed (and present) +1: stmia r0, {r1, r2, r3, r12} @ save FPEXC, FPSCR, FPINST, FPINST2 mov pc, lr +ENDPROC(vfp_save_state) #endif last_VFP_context_address: .word last_VFP_context - .globl vfp_get_float -vfp_get_float: +ENTRY(vfp_get_float) add pc, pc, r0, lsl #3 mov r0, r0 .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 @@ -197,9 +204,9 @@ vfp_get_float: mrc p10, 0, r0, c\dr, c0, 4 @ fmrs r0, s1 mov pc, lr .endr +ENDPROC(vfp_get_float) - .globl vfp_put_float -vfp_put_float: +ENTRY(vfp_put_float) add pc, pc, r1, lsl #3 mov r0, r0 .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 @@ -208,26 +215,41 @@ vfp_put_float: mcr p10, 0, r0, c\dr, c0, 4 @ fmsr r0, s1 mov pc, lr .endr +ENDPROC(vfp_put_float) - .globl vfp_get_double -vfp_get_double: +ENTRY(vfp_get_double) add pc, pc, r0, lsl #3 mov r0, r0 .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 fmrrd r0, r1, d\dr mov pc, lr .endr +#ifdef CONFIG_VFPv3 + @ d16 - d31 registers + .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 + mrrc p11, 3, r0, r1, c\dr @ fmrrd r0, r1, d\dr + mov pc, lr + .endr +#endif - @ virtual register 16 for compare with zero + @ virtual register 16 (or 32 if VFPv3) for compare with zero mov r0, #0 mov r1, #0 mov pc, lr +ENDPROC(vfp_get_double) - .globl vfp_put_double -vfp_put_double: +ENTRY(vfp_put_double) add pc, pc, r2, lsl #3 mov r0, r0 .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 fmdrr d\dr, r0, r1 mov pc, lr .endr +#ifdef CONFIG_VFPv3 + @ d16 - d31 registers + .irp dr,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 + mcrr p11, 3, r1, r2, c\dr @ fmdrr r1, r2, d\dr + mov pc, lr + .endr +#endif +ENDPROC(vfp_put_double) diff --git a/arch/arm/vfp/vfpinstr.h b/arch/arm/vfp/vfpinstr.h index 7f343a4..15b95b5 100644 --- a/arch/arm/vfp/vfpinstr.h +++ b/arch/arm/vfp/vfpinstr.h @@ -52,11 +52,11 @@ #define FEXT_TO_IDX(inst) ((inst & 0x000f0000) >> 15 | (inst & (1 << 7)) >> 7) #define vfp_get_sd(inst) ((inst & 0x0000f000) >> 11 | (inst & (1 << 22)) >> 22) -#define vfp_get_dd(inst) ((inst & 0x0000f000) >> 12) +#define vfp_get_dd(inst) ((inst & 0x0000f000) >> 12 | (inst & (1 << 22)) >> 18) #define vfp_get_sm(inst) ((inst & 0x0000000f) << 1 | (inst & (1 << 5)) >> 5) -#define vfp_get_dm(inst) ((inst & 0x0000000f)) +#define vfp_get_dm(inst) ((inst & 0x0000000f) | (inst & (1 << 5)) >> 1) #define vfp_get_sn(inst) ((inst & 0x000f0000) >> 15 | (inst & (1 << 7)) >> 7) -#define vfp_get_dn(inst) ((inst & 0x000f0000) >> 16) +#define vfp_get_dn(inst) ((inst & 0x000f0000) >> 16 | (inst & (1 << 7)) >> 3) #define vfp_single(inst) (((inst) & 0x0000f00) == 0xa00) diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index b4e210d..00d2562 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c @@ -125,13 +125,13 @@ void vfp_raise_sigfpe(unsigned int sicode, struct pt_regs *regs) send_sig_info(SIGFPE, &info, current); } -static void vfp_panic(char *reason) +static void vfp_panic(char *reason, u32 inst) { int i; printk(KERN_ERR "VFP: Error: %s\n", reason); printk(KERN_ERR "VFP: EXC 0x%08x SCR 0x%08x INST 0x%08x\n", - fmrx(FPEXC), fmrx(FPSCR), fmrx(FPINST)); + fmrx(FPEXC), fmrx(FPSCR), inst); for (i = 0; i < 32; i += 2) printk(KERN_ERR "VFP: s%2u: 0x%08x s%2u: 0x%08x\n", i, vfp_get_float(i), i+1, vfp_get_float(i+1)); @@ -147,19 +147,16 @@ static void vfp_raise_exceptions(u32 exceptions, u32 inst, u32 fpscr, struct pt_ pr_debug("VFP: raising exceptions %08x\n", exceptions); if (exceptions == VFP_EXCEPTION_ERROR) { - vfp_panic("unhandled bounce"); + vfp_panic("unhandled bounce", inst); vfp_raise_sigfpe(0, regs); return; } /* - * If any of the status flags are set, update the FPSCR. + * Update the FPSCR with the additional exception flags. * Comparison instructions always return at least one of * these flags set. */ - if (exceptions & (FPSCR_N|FPSCR_Z|FPSCR_C|FPSCR_V)) - fpscr &= ~(FPSCR_N|FPSCR_Z|FPSCR_C|FPSCR_V); - fpscr |= exceptions; fmxr(FPSCR, fpscr); @@ -220,35 +217,64 @@ static u32 vfp_emulate_instruction(u32 inst, u32 fpscr, struct pt_regs *regs) /* * Package up a bounce condition. */ -void VFP9_bounce(u32 trigger, u32 fpexc, struct pt_regs *regs) +void VFP_bounce(u32 trigger, u32 fpexc, struct pt_regs *regs) { - u32 fpscr, orig_fpscr, exceptions, inst; + u32 fpscr, orig_fpscr, fpsid, exceptions; pr_debug("VFP: bounce: trigger %08x fpexc %08x\n", trigger, fpexc); /* - * Enable access to the VFP so we can handle the bounce. + * At this point, FPEXC can have the following configuration: + * + * EX DEX IXE + * 0 1 x - synchronous exception + * 1 x 0 - asynchronous exception + * 1 x 1 - sychronous on VFP subarch 1 and asynchronous on later + * 0 0 1 - synchronous on VFP9 (non-standard subarch 1 + * implementation), undefined otherwise + * + * Clear various bits and enable access to the VFP so we can + * handle the bounce. */ - fmxr(FPEXC, fpexc & ~(FPEXC_EX|FPEXC_FPV2|FPEXC_INV|FPEXC_UFC|FPEXC_OFC|FPEXC_IOC)); + fmxr(FPEXC, fpexc & ~(FPEXC_EX|FPEXC_DEX|FPEXC_FP2V|FPEXC_VV|FPEXC_TRAP_MASK)); + fpsid = fmrx(FPSID); orig_fpscr = fpscr = fmrx(FPSCR); /* - * If we are running with inexact exceptions enabled, we need to - * emulate the trigger instruction. Note that as we're emulating - * the trigger instruction, we need to increment PC. + * Check for the special VFP subarch 1 and FPSCR.IXE bit case */ - if (fpscr & FPSCR_IXE) { - regs->ARM_pc += 4; + if ((fpsid & FPSID_ARCH_MASK) == (1 << FPSID_ARCH_BIT) + && (fpscr & FPSCR_IXE)) { + /* + * Synchronous exception, emulate the trigger instruction + */ goto emulate; } - barrier(); + if (fpexc & FPEXC_EX) { + /* + * Asynchronous exception. The instruction is read from FPINST + * and the interrupted instruction has to be restarted. + */ + trigger = fmrx(FPINST); + regs->ARM_pc -= 4; + } else if (!(fpexc & FPEXC_DEX)) { + /* + * Illegal combination of bits. It can be caused by an + * unallocated VFP instruction but with FPSCR.IXE set and not + * on VFP subarch 1. + */ + vfp_raise_exceptions(VFP_EXCEPTION_ERROR, trigger, fpscr, regs); + return; + } /* - * Modify fpscr to indicate the number of iterations remaining + * Modify fpscr to indicate the number of iterations remaining. + * If FPEXC.EX is 0, FPEXC.DEX is 1 and the FPEXC.VV bit indicates + * whether FPEXC.VECITR or FPSCR.LEN is used. */ - if (fpexc & FPEXC_EX) { + if (fpexc & (FPEXC_EX | FPEXC_VV)) { u32 len; len = fpexc + (1 << FPEXC_LENGTH_BIT); @@ -262,15 +288,15 @@ void VFP9_bounce(u32 trigger, u32 fpexc, struct pt_regs *regs) * FPEXC bounce reason, but this appears to be unreliable. * Emulate the bounced instruction instead. */ - inst = fmrx(FPINST); - exceptions = vfp_emulate_instruction(inst, fpscr, regs); + exceptions = vfp_emulate_instruction(trigger, fpscr, regs); if (exceptions) - vfp_raise_exceptions(exceptions, inst, orig_fpscr, regs); + vfp_raise_exceptions(exceptions, trigger, orig_fpscr, regs); /* - * If there isn't a second FP instruction, exit now. + * If there isn't a second FP instruction, exit now. Note that + * the FPEXC.FP2V bit is valid only if FPEXC.EX is 1. */ - if (!(fpexc & FPEXC_FPV2)) + if (fpexc ^ (FPEXC_EX | FPEXC_FP2V)) return; /* @@ -279,10 +305,9 @@ void VFP9_bounce(u32 trigger, u32 fpexc, struct pt_regs *regs) */ barrier(); trigger = fmrx(FPINST2); - orig_fpscr = fpscr = fmrx(FPSCR); emulate: - exceptions = vfp_emulate_instruction(trigger, fpscr, regs); + exceptions = vfp_emulate_instruction(trigger, orig_fpscr, regs); if (exceptions) vfp_raise_exceptions(exceptions, trigger, orig_fpscr, regs); } @@ -297,6 +322,62 @@ static void vfp_enable(void *unused) set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11)); } +#ifdef CONFIG_PM +#include + +static int vfp_pm_suspend(struct sys_device *dev, pm_message_t state) +{ + struct thread_info *ti = current_thread_info(); + u32 fpexc = fmrx(FPEXC); + + /* if vfp is on, then save state for resumption */ + if (fpexc & FPEXC_EN) { + printk(KERN_DEBUG "%s: saving vfp state\n", __func__); + vfp_save_state(&ti->vfpstate, fpexc); + + /* disable, just in case */ + fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); + } + + /* clear any information we had about last context state */ + memset(last_VFP_context, 0, sizeof(last_VFP_context)); + + return 0; +} + +static int vfp_pm_resume(struct sys_device *dev) +{ + /* ensure we have access to the vfp */ + vfp_enable(NULL); + + /* and disable it to ensure the next usage restores the state */ + fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); + + return 0; +} + +static struct sysdev_class vfp_pm_sysclass = { + //.name = "vfp", + set_kset_name("vfp"), + .suspend = vfp_pm_suspend, + .resume = vfp_pm_resume, +}; + +static struct sys_device vfp_pm_sysdev = { + .cls = &vfp_pm_sysclass, +}; + +static void vfp_pm_init(void) +{ + sysdev_class_register(&vfp_pm_sysclass); + sysdev_register(&vfp_pm_sysdev); +} + + +#else +static inline void vfp_pm_init(void) { } +#endif /* CONFIG_PM */ + #include /* @@ -306,16 +387,9 @@ static int __init vfp_init(void) { unsigned int vfpsid; unsigned int cpu_arch = cpu_architecture(); - u32 access = 0; - - if (cpu_arch >= CPU_ARCH_ARMv6) { - access = get_copro_access(); - /* - * Enable full access to VFP (cp10 and cp11) - */ - set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11)); - } + if (cpu_arch >= CPU_ARCH_ARMv6) + vfp_enable(NULL); /* * First check that there is a VFP that we can use. @@ -329,15 +403,9 @@ static int __init vfp_init(void) vfp_vector = vfp_null_entry; printk(KERN_INFO "VFP support v0.3: "); - if (VFP_arch) { + if (VFP_arch) printk("not present\n"); - - /* - * Restore the copro access register. - */ - if (cpu_arch >= CPU_ARCH_ARMv6) - set_copro_access(access); - } else if (vfpsid & FPSID_NODOUBLE) { + else if (vfpsid & FPSID_NODOUBLE) { printk("no double precision support\n"); } else { smp_call_function(vfp_enable, NULL, 1, 1); @@ -353,12 +421,22 @@ static int __init vfp_init(void) vfp_vector = vfp_support_entry; thread_register_notifier(&vfp_notifier_block); + vfp_pm_init(); /* * We detected VFP, and the support code is * in place; report VFP support to userspace. */ elf_hwcap |= HWCAP_VFP; +#ifdef CONFIG_NEON + /* + * Check for the presence of the Advanced SIMD + * load/store instructions, integer and single + * precision floating point operations. + */ + if ((fmrx(MVFR1) & 0x000fff00) == 0x00011100) + elf_hwcap |= HWCAP_NEON; +#endif } return 0; } diff --git a/drivers/Makefile b/drivers/Makefile index 8cb37e3..3672a9f 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_XEN) += xen/ # char/ comes before serial/ etc so that the VT console is the boot-time # default. +obj-$(CONFIG_INPUT) += input/ obj-y += char/ obj-$(CONFIG_CONNECTOR) += connector/ @@ -58,7 +59,6 @@ obj-$(CONFIG_PCI) += usb/ obj-$(CONFIG_USB_GADGET) += usb/gadget/ obj-$(CONFIG_SERIO) += input/serio/ obj-$(CONFIG_GAMEPORT) += input/gameport/ -obj-$(CONFIG_INPUT) += input/ obj-$(CONFIG_I2O) += message/ obj-$(CONFIG_RTC_LIB) += rtc/ obj-y += i2c/ diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 075598e..7cb4029 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -3,8 +3,8 @@ menu "Bluetooth device drivers" depends on BT config BT_HCIUSB - tristate "HCI USB driver" - depends on USB + tristate "HCI USB driver (old version)" + depends on USB && BT_HCIBTUSB=n help Bluetooth HCI USB driver. This driver is required if you want to use Bluetooth devices with @@ -23,15 +23,13 @@ config BT_HCIUSB_SCO Say Y here to compile support for SCO over HCI USB. config BT_HCIBTUSB - tristate "HCI USB driver (alternate version)" - depends on USB && EXPERIMENTAL && BT_HCIUSB=n + tristate "HCI USB driver" + depends on USB help Bluetooth HCI USB driver. This driver is required if you want to use Bluetooth devices with USB interface. - This driver is still experimental and has no SCO support. - Say Y here to compile support for Bluetooth USB devices into the kernel or say M to compile it as module (btusb). @@ -71,6 +69,7 @@ config BT_HCIUART_H4 config BT_HCIUART_BCSP bool "BCSP protocol support" depends on BT_HCIUART + select BITREVERSE help BCSP (BlueCore Serial Protocol) is serial protocol for communication between Bluetooth device and host. This protocol is required for non diff --git a/drivers/bluetooth/bcm203x.c b/drivers/bluetooth/bcm203x.c index 8919ccf..ee40201 100644 --- a/drivers/bluetooth/bcm203x.c +++ b/drivers/bluetooth/bcm203x.c @@ -42,9 +42,7 @@ #define BT_DBG(D...) #endif -#define VERSION "1.1" - -static int ignore = 0; +#define VERSION "1.2" static struct usb_device_id bcm203x_table[] = { /* Broadcom Blutonium (BCM2033) */ @@ -175,7 +173,7 @@ static int bcm203x_probe(struct usb_interface *intf, const struct usb_device_id BT_DBG("intf %p id %p", intf, id); - if (ignore || (intf->cur_altsetting->desc.bInterfaceNumber != 0)) + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) return -ENODEV; data = kzalloc(sizeof(*data), GFP_KERNEL); @@ -300,9 +298,6 @@ static void __exit bcm203x_exit(void) module_init(bcm203x_init); module_exit(bcm203x_exit); -module_param(ignore, bool, 0644); -MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); - MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Broadcom Blutonium firmware driver ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c index b990805..90a0946 100644 --- a/drivers/bluetooth/bfusb.c +++ b/drivers/bluetooth/bfusb.c @@ -43,9 +43,7 @@ #define BT_DBG(D...) #endif -#define VERSION "1.1" - -static int ignore = 0; +#define VERSION "1.2" static struct usb_driver bfusb_driver; @@ -566,7 +564,8 @@ static int bfusb_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg return -ENOIOCTLCMD; } -static int bfusb_load_firmware(struct bfusb_data *data, unsigned char *firmware, int count) +static int bfusb_load_firmware(struct bfusb_data *data, + const unsigned char *firmware, int count) { unsigned char *buf; int err, pipe, len, size, sent = 0; @@ -655,9 +654,6 @@ static int bfusb_probe(struct usb_interface *intf, const struct usb_device_id *i BT_DBG("intf %p id %p", intf, id); - if (ignore) - return -ENODEV; - /* Check number of endpoints */ if (intf->cur_altsetting->desc.bNumEndpoints < 2) return -EIO; @@ -794,9 +790,6 @@ static void __exit bfusb_exit(void) module_init(bfusb_init); module_exit(bfusb_exit); -module_param(ignore, bool, 0644); -MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); - MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("BlueFRITZ! USB driver ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c index bcf5792..b0e569b 100644 --- a/drivers/bluetooth/bluecard_cs.c +++ b/drivers/bluetooth/bluecard_cs.c @@ -867,7 +867,7 @@ static int bluecard_probe(struct pcmcia_device *link) link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; link->io.NumPorts1 = 8; - link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING | IRQ_HANDLE_PRESENT; link->irq.IRQInfo1 = IRQ_LEVEL_ID; link->irq.Handler = bluecard_interrupt; @@ -901,23 +901,23 @@ static int bluecard_config(struct pcmcia_device *link) for (n = 0; n < 0x400; n += 0x40) { link->io.BasePort1 = n ^ 0x300; i = pcmcia_request_io(link, &link->io); - if (i == CS_SUCCESS) + if (i == 0) break; } - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestIO, i); goto failed; } i = pcmcia_request_irq(link, &link->irq); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestIRQ, i); link->irq.AssignedIRQ = 0; } i = pcmcia_request_configuration(link, &link->conf); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestConfiguration, i); goto failed; } diff --git a/drivers/bluetooth/bpa10x.c b/drivers/bluetooth/bpa10x.c index 1375b53..b936d8c 100644 --- a/drivers/bluetooth/bpa10x.c +++ b/drivers/bluetooth/bpa10x.c @@ -40,9 +40,7 @@ #define BT_DBG(D...) #endif -#define VERSION "0.9" - -static int ignore = 0; +#define VERSION "0.10" static struct usb_device_id bpa10x_table[] = { /* Tektronix BPA 100/105 (Digianswer) */ @@ -258,7 +256,6 @@ static inline int bpa10x_submit_intr_urb(struct hci_dev *hdev) BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); - kfree(buf); } usb_free_urb(urb); @@ -300,7 +297,6 @@ static inline int bpa10x_submit_bulk_urb(struct hci_dev *hdev) BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); - kfree(buf); } usb_free_urb(urb); @@ -423,6 +419,7 @@ static int bpa10x_send_frame(struct sk_buff *skb) break; default: + usb_free_urb(urb); return -EILSEQ; } @@ -446,8 +443,8 @@ static void bpa10x_destruct(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - kfree(data->rx_skb[0]); - kfree(data->rx_skb[1]); + kfree_skb(data->rx_skb[0]); + kfree_skb(data->rx_skb[1]); kfree(data); } @@ -459,9 +456,6 @@ static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id * BT_DBG("intf %p id %p", intf, id); - if (ignore) - return -ENODEV; - if (intf->cur_altsetting->desc.bInterfaceNumber != 0) return -ENODEV; @@ -545,9 +539,6 @@ static void __exit bpa10x_exit(void) module_init(bpa10x_init); module_exit(bpa10x_exit); -module_param(ignore, bool, 0644); -MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); - MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Digianswer Bluetooth USB driver ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c index a18f9b8..b3e4d07 100644 --- a/drivers/bluetooth/bt3c_cs.c +++ b/drivers/bluetooth/bt3c_cs.c @@ -60,7 +60,7 @@ /* ======================== Module parameters ======================== */ -MODULE_AUTHOR("Marcel Holtmann , Jose Orlando Pereira "); +MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Bluetooth driver for the 3Com Bluetooth PCMCIA card"); MODULE_LICENSE("GPL"); MODULE_FIRMWARE("BT3CPCC.bin"); @@ -343,6 +343,7 @@ static irqreturn_t bt3c_interrupt(int irq, void *dev_inst) bt3c_info_t *info = dev_inst; unsigned int iobase; int iir; + irqreturn_t r = IRQ_NONE; BUG_ON(!info->hdev); @@ -374,11 +375,12 @@ static irqreturn_t bt3c_interrupt(int irq, void *dev_inst) outb(iir, iobase + CONTROL); } + r = IRQ_HANDLED; } spin_unlock(&(info->lock)); - return IRQ_HANDLED; + return r; } @@ -470,7 +472,8 @@ static int bt3c_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long /* ======================== Card services HCI interaction ======================== */ -static int bt3c_load_firmware(bt3c_info_t *info, unsigned char *firmware, int count) +static int bt3c_load_firmware(bt3c_info_t *info, const unsigned char *firmware, + int count) { char *ptr = (char *) firmware; char b[9]; @@ -656,7 +659,7 @@ static int bt3c_probe(struct pcmcia_device *link) link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; link->io.NumPorts1 = 8; - link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING | IRQ_HANDLE_PRESENT; link->irq.IRQInfo1 = IRQ_LEVEL_ID; link->irq.Handler = bt3c_interrupt; @@ -677,101 +680,78 @@ static void bt3c_detach(struct pcmcia_device *link) kfree(info); } -static int get_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) +static int bt3c_check_config(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cf, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) { - int i; - - i = pcmcia_get_tuple_data(handle, tuple); - if (i != CS_SUCCESS) - return i; - - return pcmcia_parse_tuple(handle, tuple, parse); -} - -static int first_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) -{ - if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) - return CS_NO_MORE_ITEMS; - return get_tuple(handle, tuple, parse); + unsigned long try = (unsigned long) priv_data; + + if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) + p_dev->conf.Vpp = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; + if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && + (cf->io.win[0].base != 0)) { + p_dev->io.BasePort1 = cf->io.win[0].base; + p_dev->io.IOAddrLines = (try == 0) ? 16 : + cf->io.flags & CISTPL_IO_LINES_MASK; + if (!pcmcia_request_io(p_dev, &p_dev->io)) + return 0; + } + return -ENODEV; } -static int next_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) +static int bt3c_check_config_notpicky(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cf, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) { - if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) - return CS_NO_MORE_ITEMS; - return get_tuple(handle, tuple, parse); + static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + int j; + + if ((cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) { + for (j = 0; j < 5; j++) { + p_dev->io.BasePort1 = base[j]; + p_dev->io.IOAddrLines = base[j] ? 16 : 3; + if (!pcmcia_request_io(p_dev, &p_dev->io)) + return 0; + } + } + return -ENODEV; } static int bt3c_config(struct pcmcia_device *link) { - static kio_addr_t base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; bt3c_info_t *info = link->priv; - tuple_t tuple; - u_short buf[256]; - cisparse_t parse; - cistpl_cftable_entry_t *cf = &parse.cftable_entry; - int i, j, try; - - /* First pass: look for a config entry that looks normal. */ - tuple.TupleData = (cisdata_t *)buf; - tuple.TupleOffset = 0; - tuple.TupleDataMax = 255; - tuple.Attributes = 0; - tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; - /* Two tries: without IO aliases, then with aliases */ - for (try = 0; try < 2; try++) { - i = first_tuple(link, &tuple, &parse); - while (i != CS_NO_MORE_ITEMS) { - if (i != CS_SUCCESS) - goto next_entry; - if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) - link->conf.Vpp = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; - if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && (cf->io.win[0].base != 0)) { - link->conf.ConfigIndex = cf->index; - link->io.BasePort1 = cf->io.win[0].base; - link->io.IOAddrLines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK; - i = pcmcia_request_io(link, &link->io); - if (i == CS_SUCCESS) - goto found_port; - } -next_entry: - i = next_tuple(link, &tuple, &parse); - } - } + int i; + unsigned long try; + + /* First pass: look for a config entry that looks normal. + Two tries: without IO aliases, then with aliases */ + for (try = 0; try < 2; try++) + if (!pcmcia_loop_config(link, bt3c_check_config, (void *) try)) + goto found_port; /* Second pass: try to find an entry that isn't picky about its base address, then try to grab any standard serial port address, and finally try to get any free port. */ - i = first_tuple(link, &tuple, &parse); - while (i != CS_NO_MORE_ITEMS) { - if ((i == CS_SUCCESS) && (cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) { - link->conf.ConfigIndex = cf->index; - for (j = 0; j < 5; j++) { - link->io.BasePort1 = base[j]; - link->io.IOAddrLines = base[j] ? 16 : 3; - i = pcmcia_request_io(link, &link->io); - if (i == CS_SUCCESS) - goto found_port; - } - } - i = next_tuple(link, &tuple, &parse); - } + if (!pcmcia_loop_config(link, bt3c_check_config_notpicky, NULL)) + goto found_port; -found_port: - if (i != CS_SUCCESS) { - BT_ERR("No usable port range found"); - cs_error(link, RequestIO, i); - goto failed; - } + BT_ERR("No usable port range found"); + cs_error(link, RequestIO, -ENODEV); + goto failed; +found_port: i = pcmcia_request_irq(link, &link->irq); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestIRQ, i); link->irq.AssignedIRQ = 0; } i = pcmcia_request_configuration(link, &link->conf); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestConfiguration, i); goto failed; } diff --git a/drivers/bluetooth/btsdio.c b/drivers/bluetooth/btsdio.c index b786f61..cda6c7c 100644 --- a/drivers/bluetooth/btsdio.c +++ b/drivers/bluetooth/btsdio.c @@ -152,7 +152,7 @@ static int btsdio_rx_packet(struct btsdio_data *data) err = sdio_readsb(data->func, skb->data, REG_RDAT, len - 4); if (err < 0) { - kfree(skb); + kfree_skb(skb); return err; } @@ -162,10 +162,8 @@ static int btsdio_rx_packet(struct btsdio_data *data) bt_cb(skb)->pkt_type = hdr[3]; err = hci_recv_frame(skb); - if (err < 0) { - kfree(skb); + if (err < 0) return err; - } sdio_writeb(data->func, 0x00, REG_PC_RRT, NULL); diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c index 08f48d5..efd689a 100644 --- a/drivers/bluetooth/btuart_cs.c +++ b/drivers/bluetooth/btuart_cs.c @@ -293,6 +293,7 @@ static irqreturn_t btuart_interrupt(int irq, void *dev_inst) unsigned int iobase; int boguscount = 0; int iir, lsr; + irqreturn_t r = IRQ_NONE; BUG_ON(!info->hdev); @@ -302,6 +303,7 @@ static irqreturn_t btuart_interrupt(int irq, void *dev_inst) iir = inb(iobase + UART_IIR) & UART_IIR_ID; while (iir) { + r = IRQ_HANDLED; /* Clear interrupt */ lsr = inb(iobase + UART_LSR); @@ -335,7 +337,7 @@ static irqreturn_t btuart_interrupt(int irq, void *dev_inst) spin_unlock(&(info->lock)); - return IRQ_HANDLED; + return r; } @@ -383,7 +385,7 @@ static void btuart_change_speed(btuart_info_t *info, unsigned int speed) outb(lcr, iobase + UART_LCR); /* Set 8N1 */ outb(fcr, iobase + UART_FCR); /* Enable FIFO's */ - /* Turn on interrups */ + /* Turn on interrupts */ outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); spin_unlock_irqrestore(&(info->lock), flags); @@ -586,7 +588,7 @@ static int btuart_probe(struct pcmcia_device *link) link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; link->io.NumPorts1 = 8; - link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING | IRQ_HANDLE_PRESENT; link->irq.IRQInfo1 = IRQ_LEVEL_ID; link->irq.Handler = btuart_interrupt; @@ -607,102 +609,78 @@ static void btuart_detach(struct pcmcia_device *link) kfree(info); } -static int get_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) -{ - int i; - - i = pcmcia_get_tuple_data(handle, tuple); - if (i != CS_SUCCESS) - return i; - - return pcmcia_parse_tuple(handle, tuple, parse); -} - -static int first_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) +static int btuart_check_config(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cf, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) { - if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) - return CS_NO_MORE_ITEMS; - return get_tuple(handle, tuple, parse); + int *try = priv_data; + + if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) + p_dev->conf.Vpp = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; + if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && + (cf->io.win[0].base != 0)) { + p_dev->io.BasePort1 = cf->io.win[0].base; + p_dev->io.IOAddrLines = (*try == 0) ? 16 : + cf->io.flags & CISTPL_IO_LINES_MASK; + if (!pcmcia_request_io(p_dev, &p_dev->io)) + return 0; + } + return -ENODEV; } -static int next_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) +static int btuart_check_config_notpicky(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cf, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) { - if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) - return CS_NO_MORE_ITEMS; - return get_tuple(handle, tuple, parse); + static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + int j; + + if ((cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) { + for (j = 0; j < 5; j++) { + p_dev->io.BasePort1 = base[j]; + p_dev->io.IOAddrLines = base[j] ? 16 : 3; + if (!pcmcia_request_io(p_dev, &p_dev->io)) + return 0; + } + } + return -ENODEV; } static int btuart_config(struct pcmcia_device *link) { - static kio_addr_t base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; btuart_info_t *info = link->priv; - tuple_t tuple; - u_short buf[256]; - cisparse_t parse; - cistpl_cftable_entry_t *cf = &parse.cftable_entry; - int i, j, try; - - /* First pass: look for a config entry that looks normal. */ - tuple.TupleData = (cisdata_t *) buf; - tuple.TupleOffset = 0; - tuple.TupleDataMax = 255; - tuple.Attributes = 0; - tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; - /* Two tries: without IO aliases, then with aliases */ - for (try = 0; try < 2; try++) { - i = first_tuple(link, &tuple, &parse); - while (i != CS_NO_MORE_ITEMS) { - if (i != CS_SUCCESS) - goto next_entry; - if (cf->vpp1.present & (1 << CISTPL_POWER_VNOM)) - link->conf.Vpp = cf->vpp1.param[CISTPL_POWER_VNOM] / 10000; - if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) && (cf->io.win[0].base != 0)) { - link->conf.ConfigIndex = cf->index; - link->io.BasePort1 = cf->io.win[0].base; - link->io.IOAddrLines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK; - i = pcmcia_request_io(link, &link->io); - if (i == CS_SUCCESS) - goto found_port; - } -next_entry: - i = next_tuple(link, &tuple, &parse); - } - } + int i; + int try; + + /* First pass: look for a config entry that looks normal. + Two tries: without IO aliases, then with aliases */ + for (try = 0; try < 2; try++) + if (!pcmcia_loop_config(link, btuart_check_config, &try)) + goto found_port; /* Second pass: try to find an entry that isn't picky about its base address, then try to grab any standard serial port address, and finally try to get any free port. */ - i = first_tuple(link, &tuple, &parse); - while (i != CS_NO_MORE_ITEMS) { - if ((i == CS_SUCCESS) && (cf->io.nwin > 0) - && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) { - link->conf.ConfigIndex = cf->index; - for (j = 0; j < 5; j++) { - link->io.BasePort1 = base[j]; - link->io.IOAddrLines = base[j] ? 16 : 3; - i = pcmcia_request_io(link, &link->io); - if (i == CS_SUCCESS) - goto found_port; - } - } - i = next_tuple(link, &tuple, &parse); - } + if (!pcmcia_loop_config(link, btuart_check_config_notpicky, NULL)) + goto found_port; -found_port: - if (i != CS_SUCCESS) { - BT_ERR("No usable port range found"); - cs_error(link, RequestIO, i); - goto failed; - } + BT_ERR("No usable port range found"); + cs_error(link, RequestIO, -ENODEV); + goto failed; +found_port: i = pcmcia_request_irq(link, &link->irq); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestIRQ, i); link->irq.AssignedIRQ = 0; } i = pcmcia_request_configuration(link, &link->conf); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestConfiguration, i); goto failed; } diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 12e1089..64238b0 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -2,7 +2,7 @@ * * Generic Bluetooth USB driver * - * Copyright (C) 2005-2007 Marcel Holtmann + * Copyright (C) 2005-2008 Marcel Holtmann * * * This program is free software; you can redistribute it and/or modify @@ -41,27 +41,144 @@ #define BT_DBG(D...) #endif -#define VERSION "0.1" +#define VERSION "0.3" + +static int ignore_dga; +static int ignore_csr; +static int ignore_sniffer; +static int disable_scofix; +static int force_scofix; +static int reset; + +static struct usb_driver btusb_driver; + +#define BTUSB_IGNORE 0x01 +#define BTUSB_RESET 0x02 +#define BTUSB_DIGIANSWER 0x04 +#define BTUSB_CSR 0x08 +#define BTUSB_SNIFFER 0x10 +#define BTUSB_BCM92035 0x20 +#define BTUSB_BROKEN_ISOC 0x40 +#define BTUSB_WRONG_SCO_MTU 0x80 static struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, + /* AVM BlueFRITZ! USB v2.0 */ + { USB_DEVICE(0x057c, 0x3800) }, + + /* Bluetooth Ultraport Module from IBM */ + { USB_DEVICE(0x04bf, 0x030a) }, + + /* ALPS Modules with non-standard id */ + { USB_DEVICE(0x044e, 0x3001) }, + { USB_DEVICE(0x044e, 0x3002) }, + + /* Ericsson with non-standard id */ + { USB_DEVICE(0x0bdb, 0x1002) }, + + /* Canyon CN-BTU1 with HID interfaces */ + { USB_DEVICE(0x0c10, 0x0000), .driver_info = BTUSB_RESET }, + { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, btusb_table); static struct usb_device_id blacklist_table[] = { + /* CSR BlueCore devices */ + { USB_DEVICE(0x0a12, 0x0001), .driver_info = BTUSB_CSR }, + + /* Broadcom BCM2033 without firmware */ + { USB_DEVICE(0x0a5c, 0x2033), .driver_info = BTUSB_IGNORE }, + + /* Broadcom BCM2035 */ + { USB_DEVICE(0x0a5c, 0x2035), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x200a), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Broadcom BCM2045 */ + { USB_DEVICE(0x0a5c, 0x2039), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x2101), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Broadcom BCM2046 */ + { USB_DEVICE(0x0a5c, 0x2146), .driver_info = BTUSB_RESET }, + { USB_DEVICE(0x0a5c, 0x2151), .driver_info = BTUSB_RESET }, + + /* Apple MacBook Pro with Broadcom chip */ + { USB_DEVICE(0x05ac, 0x820f), .driver_info = BTUSB_RESET }, + + /* IBM/Lenovo ThinkPad with Broadcom chip */ + { USB_DEVICE(0x0a5c, 0x201e), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x2110), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Targus ACB10US */ + { USB_DEVICE(0x0a5c, 0x2100), .driver_info = BTUSB_RESET }, + { USB_DEVICE(0x0a5c, 0x2154), .driver_info = BTUSB_RESET }, + + /* ANYCOM Bluetooth USB-200 and USB-250 */ + { USB_DEVICE(0x0a5c, 0x2111), .driver_info = BTUSB_RESET }, + + /* HP laptop with Broadcom chip */ + { USB_DEVICE(0x03f0, 0x171d), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Dell laptop with Broadcom chip */ + { USB_DEVICE(0x413c, 0x8126), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Dell Wireless 370 */ + { USB_DEVICE(0x413c, 0x8156), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Dell Wireless 410 */ + { USB_DEVICE(0x413c, 0x8152), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Microsoft Wireless Transceiver for Bluetooth 2.0 */ + { USB_DEVICE(0x045e, 0x009c), .driver_info = BTUSB_RESET }, + + /* Kensington Bluetooth USB adapter */ + { USB_DEVICE(0x047d, 0x105d), .driver_info = BTUSB_RESET }, + { USB_DEVICE(0x047d, 0x105e), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* ISSC Bluetooth Adapter v3.1 */ + { USB_DEVICE(0x1131, 0x1001), .driver_info = BTUSB_RESET }, + + /* RTX Telecom based adapters with buggy SCO support */ + { USB_DEVICE(0x0400, 0x0807), .driver_info = BTUSB_BROKEN_ISOC }, + { USB_DEVICE(0x0400, 0x080a), .driver_info = BTUSB_BROKEN_ISOC }, + + /* CONWISE Technology based adapters with buggy SCO support */ + { USB_DEVICE(0x0e5e, 0x6622), .driver_info = BTUSB_BROKEN_ISOC }, + + /* Belkin F8T012 and F8T013 devices */ + { USB_DEVICE(0x050d, 0x0012), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x050d, 0x0013), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU }, + + /* Belkin F8T016 device */ + { USB_DEVICE(0x050d, 0x016a), .driver_info = BTUSB_RESET }, + + /* Digianswer devices */ + { USB_DEVICE(0x08fd, 0x0001), .driver_info = BTUSB_DIGIANSWER }, + { USB_DEVICE(0x08fd, 0x0002), .driver_info = BTUSB_IGNORE }, + + /* CSR BlueCore Bluetooth Sniffer */ + { USB_DEVICE(0x0a12, 0x0002), .driver_info = BTUSB_SNIFFER }, + + /* Frontline ComProbe Bluetooth Sniffer */ + { USB_DEVICE(0x16d3, 0x0002), .driver_info = BTUSB_SNIFFER }, + { } /* Terminating entry */ }; +#define BTUSB_MAX_ISOC_FRAMES 10 + #define BTUSB_INTR_RUNNING 0 #define BTUSB_BULK_RUNNING 1 +#define BTUSB_ISOC_RUNNING 2 struct btusb_data { struct hci_dev *hdev; struct usb_device *udev; + struct usb_interface *intf; + struct usb_interface *isoc; spinlock_t lock; @@ -72,10 +189,15 @@ struct btusb_data { struct usb_anchor tx_anchor; struct usb_anchor intr_anchor; struct usb_anchor bulk_anchor; + struct usb_anchor isoc_anchor; struct usb_endpoint_descriptor *intr_ep; struct usb_endpoint_descriptor *bulk_tx_ep; struct usb_endpoint_descriptor *bulk_rx_ep; + struct usb_endpoint_descriptor *isoc_tx_ep; + struct usb_endpoint_descriptor *isoc_rx_ep; + + int isoc_altsetting; }; static void btusb_intr_complete(struct urb *urb) @@ -91,6 +213,8 @@ static void btusb_intr_complete(struct urb *urb) return; if (urb->status == 0) { + hdev->stat.byte_rx += urb->actual_length; + if (hci_recv_fragment(hdev, HCI_EVENT_PKT, urb->transfer_buffer, urb->actual_length) < 0) { @@ -112,7 +236,7 @@ static void btusb_intr_complete(struct urb *urb) } } -static inline int btusb_submit_intr_urb(struct hci_dev *hdev) +static int btusb_submit_intr_urb(struct hci_dev *hdev) { struct btusb_data *data = hdev->driver_data; struct urb *urb; @@ -122,6 +246,9 @@ static inline int btusb_submit_intr_urb(struct hci_dev *hdev) BT_DBG("%s", hdev->name); + if (!data->intr_ep) + return -ENODEV; + urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) return -ENOMEM; @@ -149,7 +276,6 @@ static inline int btusb_submit_intr_urb(struct hci_dev *hdev) BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); - kfree(buf); } usb_free_urb(urb); @@ -170,6 +296,8 @@ static void btusb_bulk_complete(struct urb *urb) return; if (urb->status == 0) { + hdev->stat.byte_rx += urb->actual_length; + if (hci_recv_fragment(hdev, HCI_ACLDATA_PKT, urb->transfer_buffer, urb->actual_length) < 0) { @@ -191,7 +319,7 @@ static void btusb_bulk_complete(struct urb *urb) } } -static inline int btusb_submit_bulk_urb(struct hci_dev *hdev) +static int btusb_submit_bulk_urb(struct hci_dev *hdev) { struct btusb_data *data = hdev->driver_data; struct urb *urb; @@ -201,6 +329,9 @@ static inline int btusb_submit_bulk_urb(struct hci_dev *hdev) BT_DBG("%s", hdev->name); + if (!data->bulk_rx_ep) + return -ENODEV; + urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; @@ -227,7 +358,126 @@ static inline int btusb_submit_bulk_urb(struct hci_dev *hdev) BT_ERR("%s urb %p submission failed (%d)", hdev->name, urb, -err); usb_unanchor_urb(urb); - kfree(buf); + } + + usb_free_urb(urb); + + return err; +} + +static void btusb_isoc_complete(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct btusb_data *data = hdev->driver_data; + int i, err; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (urb->status == 0) { + for (i = 0; i < urb->number_of_packets; i++) { + unsigned int offset = urb->iso_frame_desc[i].offset; + unsigned int length = urb->iso_frame_desc[i].actual_length; + + if (urb->iso_frame_desc[i].status) + continue; + + hdev->stat.byte_rx += length; + + if (hci_recv_fragment(hdev, HCI_SCODATA_PKT, + urb->transfer_buffer + offset, + length) < 0) { + BT_ERR("%s corrupted SCO packet", hdev->name); + hdev->stat.err_rx++; + } + } + } + + if (!test_bit(BTUSB_ISOC_RUNNING, &data->flags)) + return; + + usb_anchor_urb(urb, &data->isoc_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s urb %p failed to resubmit (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } +} + +static void inline __fill_isoc_descriptor(struct urb *urb, int len, int mtu) +{ + int i, offset = 0; + + BT_DBG("len %d mtu %d", len, mtu); + + for (i = 0; i < BTUSB_MAX_ISOC_FRAMES && len >= mtu; + i++, offset += mtu, len -= mtu) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = mtu; + } + + if (len && i < BTUSB_MAX_ISOC_FRAMES) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = len; + i++; + } + + urb->number_of_packets = i; +} + +static int btusb_submit_isoc_urb(struct hci_dev *hdev) +{ + struct btusb_data *data = hdev->driver_data; + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size; + + BT_DBG("%s", hdev->name); + + if (!data->isoc_rx_ep) + return -ENODEV; + + urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + size = le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize) * + BTUSB_MAX_ISOC_FRAMES; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvisocpipe(data->udev, data->isoc_rx_ep->bEndpointAddress); + + urb->dev = data->udev; + urb->pipe = pipe; + urb->context = hdev; + urb->complete = btusb_isoc_complete; + urb->interval = data->isoc_rx_ep->bInterval; + + urb->transfer_flags = URB_FREE_BUFFER | URB_ISO_ASAP; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = size; + + __fill_isoc_descriptor(urb, size, + le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize)); + + usb_anchor_urb(urb, &data->isoc_anchor); + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) { + BT_ERR("%s urb %p submission failed (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); } usb_free_urb(urb); @@ -272,7 +522,7 @@ static int btusb_open(struct hci_dev *hdev) err = btusb_submit_intr_urb(hdev); if (err < 0) { - clear_bit(BTUSB_INTR_RUNNING, &hdev->flags); + clear_bit(BTUSB_INTR_RUNNING, &data->flags); clear_bit(HCI_RUNNING, &hdev->flags); } @@ -288,6 +538,11 @@ static int btusb_close(struct hci_dev *hdev) if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) return 0; + cancel_work_sync(&data->work); + + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + usb_kill_anchored_urbs(&data->isoc_anchor); + clear_bit(BTUSB_BULK_RUNNING, &data->flags); usb_kill_anchored_urbs(&data->bulk_anchor); @@ -349,6 +604,9 @@ static int btusb_send_frame(struct sk_buff *skb) break; case HCI_ACLDATA_PKT: + if (!data->bulk_tx_ep || hdev->conn_hash.acl_num < 1) + return -ENODEV; + urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) return -ENOMEM; @@ -363,9 +621,31 @@ static int btusb_send_frame(struct sk_buff *skb) break; case HCI_SCODATA_PKT: + if (!data->isoc_tx_ep || hdev->conn_hash.sco_num < 1) + return -ENODEV; + + urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + pipe = usb_sndisocpipe(data->udev, + data->isoc_tx_ep->bEndpointAddress); + + urb->dev = data->udev; + urb->pipe = pipe; + urb->context = skb; + urb->complete = btusb_tx_complete; + urb->interval = data->isoc_tx_ep->bInterval; + + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = skb->data; + urb->transfer_buffer_length = skb->len; + + __fill_isoc_descriptor(urb, skb->len, + le16_to_cpu(data->isoc_tx_ep->wMaxPacketSize)); + hdev->stat.sco_tx++; - kfree_skb(skb); - return 0; + break; default: return -EILSEQ; @@ -404,22 +684,86 @@ static void btusb_notify(struct hci_dev *hdev, unsigned int evt) schedule_work(&data->work); } +static int inline __set_isoc_interface(struct hci_dev *hdev, int altsetting) +{ + struct btusb_data *data = hdev->driver_data; + struct usb_interface *intf = data->isoc; + struct usb_endpoint_descriptor *ep_desc; + int i, err; + + if (!data->isoc) + return -ENODEV; + + err = usb_set_interface(data->udev, 1, altsetting); + if (err < 0) { + BT_ERR("%s setting interface failed (%d)", hdev->name, -err); + return err; + } + + data->isoc_altsetting = altsetting; + + data->isoc_tx_ep = NULL; + data->isoc_rx_ep = NULL; + + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + ep_desc = &intf->cur_altsetting->endpoint[i].desc; + + if (!data->isoc_tx_ep && usb_endpoint_is_isoc_out(ep_desc)) { + data->isoc_tx_ep = ep_desc; + continue; + } + + if (!data->isoc_rx_ep && usb_endpoint_is_isoc_in(ep_desc)) { + data->isoc_rx_ep = ep_desc; + continue; + } + } + + if (!data->isoc_tx_ep || !data->isoc_rx_ep) { + BT_ERR("%s invalid SCO descriptors", hdev->name); + return -ENODEV; + } + + return 0; +} + static void btusb_work(struct work_struct *work) { struct btusb_data *data = container_of(work, struct btusb_data, work); struct hci_dev *hdev = data->hdev; - if (hdev->conn_hash.acl_num == 0) { + if (hdev->conn_hash.acl_num > 0) { + if (!test_and_set_bit(BTUSB_BULK_RUNNING, &data->flags)) { + if (btusb_submit_bulk_urb(hdev) < 0) + clear_bit(BTUSB_BULK_RUNNING, &data->flags); + else + btusb_submit_bulk_urb(hdev); + } + } else { clear_bit(BTUSB_BULK_RUNNING, &data->flags); usb_kill_anchored_urbs(&data->bulk_anchor); - return; } - if (!test_and_set_bit(BTUSB_BULK_RUNNING, &data->flags)) { - if (btusb_submit_bulk_urb(hdev) < 0) - clear_bit(BTUSB_BULK_RUNNING, &data->flags); - else - btusb_submit_bulk_urb(hdev); + if (hdev->conn_hash.sco_num > 0) { + if (data->isoc_altsetting != 2) { + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + usb_kill_anchored_urbs(&data->isoc_anchor); + + if (__set_isoc_interface(hdev, 2) < 0) + return; + } + + if (!test_and_set_bit(BTUSB_ISOC_RUNNING, &data->flags)) { + if (btusb_submit_isoc_urb(hdev) < 0) + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + else + btusb_submit_isoc_urb(hdev); + } + } else { + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + usb_kill_anchored_urbs(&data->isoc_anchor); + + __set_isoc_interface(hdev, 0); } } @@ -433,6 +777,7 @@ static int btusb_probe(struct usb_interface *intf, BT_DBG("intf %p id %p", intf, id); + /* interface numbers are hardcoded in the spec */ if (intf->cur_altsetting->desc.bInterfaceNumber != 0) return -ENODEV; @@ -443,6 +788,18 @@ static int btusb_probe(struct usb_interface *intf, id = match; } + if (id->driver_info == BTUSB_IGNORE) + return -ENODEV; + + if (ignore_dga && id->driver_info & BTUSB_DIGIANSWER) + return -ENODEV; + + if (ignore_csr && id->driver_info & BTUSB_CSR) + return -ENODEV; + + if (ignore_sniffer && id->driver_info & BTUSB_SNIFFER) + return -ENODEV; + data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -472,6 +829,7 @@ static int btusb_probe(struct usb_interface *intf, } data->udev = interface_to_usbdev(intf); + data->intf = intf; spin_lock_init(&data->lock); @@ -480,6 +838,7 @@ static int btusb_probe(struct usb_interface *intf, init_usb_anchor(&data->tx_anchor); init_usb_anchor(&data->intr_anchor); init_usb_anchor(&data->bulk_anchor); + init_usb_anchor(&data->isoc_anchor); hdev = hci_alloc_dev(); if (!hdev) { @@ -503,7 +862,49 @@ static int btusb_probe(struct usb_interface *intf, hdev->owner = THIS_MODULE; - set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks); + /* interface numbers are hardcoded in the spec */ + data->isoc = usb_ifnum_to_if(data->udev, 1); + + if (reset || id->driver_info & BTUSB_RESET) + set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks); + + if (force_scofix || id->driver_info & BTUSB_WRONG_SCO_MTU) { + if (!disable_scofix) + set_bit(HCI_QUIRK_FIXUP_BUFFER_SIZE, &hdev->quirks); + } + + if (id->driver_info & BTUSB_BROKEN_ISOC) + data->isoc = NULL; + + if (id->driver_info & BTUSB_SNIFFER) { + struct usb_device *udev = data->udev; + + if (le16_to_cpu(udev->descriptor.bcdDevice) > 0x997) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + + data->isoc = NULL; + } + + if (id->driver_info & BTUSB_BCM92035) { + unsigned char cmd[] = { 0x3b, 0xfc, 0x01, 0x00 }; + struct sk_buff *skb; + + skb = bt_skb_alloc(sizeof(cmd), GFP_KERNEL); + if (skb) { + memcpy(skb_put(skb, sizeof(cmd)), cmd, sizeof(cmd)); + skb_queue_tail(&hdev->driver_init, skb); + } + } + + if (data->isoc) { + err = usb_driver_claim_interface(&btusb_driver, + data->isoc, data); + if (err < 0) { + hci_free_dev(hdev); + kfree(data); + return err; + } + } err = hci_register_dev(hdev); if (err < 0) { @@ -529,18 +930,45 @@ static void btusb_disconnect(struct usb_interface *intf) hdev = data->hdev; - usb_set_intfdata(intf, NULL); + __hci_dev_hold(hdev); + + usb_set_intfdata(data->intf, NULL); + + if (data->isoc) + usb_set_intfdata(data->isoc, NULL); hci_unregister_dev(hdev); + if (intf == data->isoc) + usb_driver_release_interface(&btusb_driver, data->intf); + else if (data->isoc) + usb_driver_release_interface(&btusb_driver, data->isoc); + + __hci_dev_put(hdev); + hci_free_dev(hdev); } +#ifdef CONFIG_PM +static int btusb_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int btusb_resume(struct usb_interface *intf) +{ + return 0; +} +#endif static struct usb_driver btusb_driver = { .name = "btusb", .probe = btusb_probe, .disconnect = btusb_disconnect, .id_table = btusb_table, +#ifdef CONFIG_PM + .suspend = btusb_suspend, + .resume = btusb_resume, +#endif }; static int __init btusb_init(void) @@ -558,6 +986,24 @@ static void __exit btusb_exit(void) module_init(btusb_init); module_exit(btusb_exit); +module_param(ignore_dga, bool, 0644); +MODULE_PARM_DESC(ignore_dga, "Ignore devices with id 08fd:0001"); + +module_param(ignore_csr, bool, 0644); +MODULE_PARM_DESC(ignore_csr, "Ignore devices with id 0a12:0001"); + +module_param(ignore_sniffer, bool, 0644); +MODULE_PARM_DESC(ignore_sniffer, "Ignore devices with id 0a12:0002"); + +module_param(disable_scofix, bool, 0644); +MODULE_PARM_DESC(disable_scofix, "Disable fixup of wrong SCO buffer size"); + +module_param(force_scofix, bool, 0644); +MODULE_PARM_DESC(force_scofix, "Force fixup of wrong SCO buffers size"); + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Generic Bluetooth USB driver ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c index dae45cd..901bdd9 100644 --- a/drivers/bluetooth/dtl1_cs.c +++ b/drivers/bluetooth/dtl1_cs.c @@ -297,6 +297,7 @@ static irqreturn_t dtl1_interrupt(int irq, void *dev_inst) unsigned char msr; int boguscount = 0; int iir, lsr; + irqreturn_t r = IRQ_NONE; BUG_ON(!info->hdev); @@ -307,6 +308,7 @@ static irqreturn_t dtl1_interrupt(int irq, void *dev_inst) iir = inb(iobase + UART_IIR) & UART_IIR_ID; while (iir) { + r = IRQ_HANDLED; /* Clear interrupt */ lsr = inb(iobase + UART_LSR); @@ -343,11 +345,12 @@ static irqreturn_t dtl1_interrupt(int irq, void *dev_inst) info->ri_latch = msr & UART_MSR_RI; clear_bit(XMIT_WAITING, &(info->tx_state)); dtl1_write_wakeup(info); + r = IRQ_HANDLED; } spin_unlock(&(info->lock)); - return IRQ_HANDLED; + return r; } @@ -568,7 +571,7 @@ static int dtl1_probe(struct pcmcia_device *link) link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; link->io.NumPorts1 = 8; - link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.Attributes = IRQ_TYPE_DYNAMIC_SHARING | IRQ_HANDLE_PRESENT; link->irq.IRQInfo1 = IRQ_LEVEL_ID; link->irq.Handler = dtl1_interrupt; @@ -590,75 +593,40 @@ static void dtl1_detach(struct pcmcia_device *link) kfree(info); } -static int get_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) +static int dtl1_confcheck(struct pcmcia_device *p_dev, + cistpl_cftable_entry_t *cf, + cistpl_cftable_entry_t *dflt, + unsigned int vcc, + void *priv_data) { - int i; - - i = pcmcia_get_tuple_data(handle, tuple); - if (i != CS_SUCCESS) - return i; - - return pcmcia_parse_tuple(handle, tuple, parse); -} - -static int first_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) -{ - if (pcmcia_get_first_tuple(handle, tuple) != CS_SUCCESS) - return CS_NO_MORE_ITEMS; - return get_tuple(handle, tuple, parse); -} - -static int next_tuple(struct pcmcia_device *handle, tuple_t *tuple, cisparse_t *parse) -{ - if (pcmcia_get_next_tuple(handle, tuple) != CS_SUCCESS) - return CS_NO_MORE_ITEMS; - return get_tuple(handle, tuple, parse); + if ((cf->io.nwin == 1) && (cf->io.win[0].len > 8)) { + p_dev->io.BasePort1 = cf->io.win[0].base; + p_dev->io.NumPorts1 = cf->io.win[0].len; /*yo */ + p_dev->io.IOAddrLines = cf->io.flags & CISTPL_IO_LINES_MASK; + if (!pcmcia_request_io(p_dev, &p_dev->io)) + return 0; + } + return -ENODEV; } static int dtl1_config(struct pcmcia_device *link) { dtl1_info_t *info = link->priv; - tuple_t tuple; - u_short buf[256]; - cisparse_t parse; - cistpl_cftable_entry_t *cf = &parse.cftable_entry; int i; - tuple.TupleData = (cisdata_t *)buf; - tuple.TupleOffset = 0; - tuple.TupleDataMax = 255; - tuple.Attributes = 0; - tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; - /* Look for a generic full-sized window */ link->io.NumPorts1 = 8; - i = first_tuple(link, &tuple, &parse); - while (i != CS_NO_MORE_ITEMS) { - if ((i == CS_SUCCESS) && (cf->io.nwin == 1) && (cf->io.win[0].len > 8)) { - link->conf.ConfigIndex = cf->index; - link->io.BasePort1 = cf->io.win[0].base; - link->io.NumPorts1 = cf->io.win[0].len; /*yo */ - link->io.IOAddrLines = cf->io.flags & CISTPL_IO_LINES_MASK; - i = pcmcia_request_io(link, &link->io); - if (i == CS_SUCCESS) - break; - } - i = next_tuple(link, &tuple, &parse); - } - - if (i != CS_SUCCESS) { - cs_error(link, RequestIO, i); + if (!pcmcia_loop_config(link, dtl1_confcheck, NULL)) goto failed; - } i = pcmcia_request_irq(link, &link->irq); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestIRQ, i); link->irq.AssignedIRQ = 0; } i = pcmcia_request_configuration(link, &link->conf); - if (i != CS_SUCCESS) { + if (i != 0) { cs_error(link, RequestConfiguration, i); goto failed; } diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c index 696f752..7938062 100644 --- a/drivers/bluetooth/hci_bcsp.c +++ b/drivers/bluetooth/hci_bcsp.c @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include @@ -124,27 +126,6 @@ static void bcsp_crc_update(u16 *crc, u8 d) *crc = reg; } -/* - Get reverse of generated crc - - Implementation note - The crc generator (bcsp_crc_init() and bcsp_crc_update()) - creates a reversed crc, so it needs to be swapped back before - being passed on. -*/ -static u16 bcsp_crc_reverse(u16 crc) -{ - u16 b, rev; - - for (b = 0, rev = 0; b < 16; b++) { - rev = rev << 1; - rev |= (crc & 1); - crc = crc >> 1; - } - - return (rev); -} - /* ---- BCSP core ---- */ static void bcsp_slip_msgdelim(struct sk_buff *skb) @@ -235,10 +216,10 @@ static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data, } if (hciextn && chan == 5) { - struct hci_command_hdr *hdr = (struct hci_command_hdr *) data; + __le16 opcode = ((struct hci_command_hdr *)data)->opcode; /* Vendor specific commands */ - if (hci_opcode_ogf(__le16_to_cpu(hdr->opcode)) == 0x3f) { + if (hci_opcode_ogf(__le16_to_cpu(opcode)) == 0x3f) { u8 desc = *(data + HCI_COMMAND_HDR_SIZE); if ((desc & 0xf0) == 0xc0) { data += HCI_COMMAND_HDR_SIZE + 1; @@ -296,7 +277,7 @@ static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data, /* Put CRC */ if (bcsp->use_crc) { - bcsp_txmsg_crc = bcsp_crc_reverse(bcsp_txmsg_crc); + bcsp_txmsg_crc = bitrev16(bcsp_txmsg_crc); bcsp_slip_one_byte(nskb, (u8) ((bcsp_txmsg_crc >> 8) & 0x00ff)); bcsp_slip_one_byte(nskb, (u8) (bcsp_txmsg_crc & 0x00ff)); } @@ -371,14 +352,14 @@ static int bcsp_flush(struct hci_uart *hu) /* Remove ack'ed packets */ static void bcsp_pkt_cull(struct bcsp_struct *bcsp) { + struct sk_buff *skb, *tmp; unsigned long flags; - struct sk_buff *skb; int i, pkts_to_be_removed; u8 seqno; spin_lock_irqsave(&bcsp->unack.lock, flags); - pkts_to_be_removed = bcsp->unack.qlen; + pkts_to_be_removed = skb_queue_len(&bcsp->unack); seqno = bcsp->msgq_txseq; while (pkts_to_be_removed) { @@ -392,19 +373,19 @@ static void bcsp_pkt_cull(struct bcsp_struct *bcsp) BT_ERR("Peer acked invalid packet"); BT_DBG("Removing %u pkts out of %u, up to seqno %u", - pkts_to_be_removed, bcsp->unack.qlen, (seqno - 1) & 0x07); + pkts_to_be_removed, skb_queue_len(&bcsp->unack), + (seqno - 1) & 0x07); - for (i = 0, skb = ((struct sk_buff *) &bcsp->unack)->next; i < pkts_to_be_removed - && skb != (struct sk_buff *) &bcsp->unack; i++) { - struct sk_buff *nskb; + i = 0; + skb_queue_walk_safe(&bcsp->unack, skb, tmp) { + if (i++ >= pkts_to_be_removed) + break; - nskb = skb->next; __skb_unlink(skb, &bcsp->unack); kfree_skb(skb); - skb = nskb; } - if (bcsp->unack.qlen == 0) + if (skb_queue_empty(&bcsp->unack)) del_timer(&bcsp->tbcsp); spin_unlock_irqrestore(&bcsp->unack.lock, flags); @@ -566,6 +547,11 @@ static void bcsp_complete_rx_pkt(struct hci_uart *hu) bcsp->rx_skb = NULL; } +static u16 bscp_get_crc(struct bcsp_struct *bcsp) +{ + return get_unaligned_be16(&bcsp->rx_skb->data[bcsp->rx_skb->len - 2]); +} + /* Recv data */ static int bcsp_recv(struct hci_uart *hu, void *data, int count) { @@ -624,14 +610,10 @@ static int bcsp_recv(struct hci_uart *hu, void *data, int count) continue; case BCSP_W4_CRC: - if (bcsp_crc_reverse(bcsp->message_crc) != - (bcsp->rx_skb->data[bcsp->rx_skb->len - 2] << 8) + - bcsp->rx_skb->data[bcsp->rx_skb->len - 1]) { - + if (bitrev16(bcsp->message_crc) != bscp_get_crc(bcsp)) { BT_ERR ("Checksum failed: computed %04x received %04x", - bcsp_crc_reverse(bcsp->message_crc), - (bcsp->rx_skb-> data[bcsp->rx_skb->len - 2] << 8) + - bcsp->rx_skb->data[bcsp->rx_skb->len - 1]); + bitrev16(bcsp->message_crc), + bscp_get_crc(bcsp)); kfree_skb(bcsp->rx_skb); bcsp->rx_state = BCSP_W4_PKT_DELIMITER; diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index e68821d..4426bb5 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -143,7 +143,7 @@ restart: int len; set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); - len = tty->driver->write(tty, skb->data, skb->len); + len = tty->ops->write(tty, skb->data, skb->len); hdev->stat.byte_tx += len; skb_pull(skb, len); @@ -190,8 +190,7 @@ static int hci_uart_flush(struct hci_dev *hdev) /* Flush any pending characters in the driver and discipline. */ tty_ldisc_flush(tty); - if (tty->driver && tty->driver->flush_buffer) - tty->driver->flush_buffer(tty); + tty_driver_flush_buffer(tty); if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) hu->proto->flush(hu); @@ -208,6 +207,7 @@ static int hci_uart_close(struct hci_dev *hdev) return 0; hci_uart_flush(hdev); + hdev->flush = NULL; return 0; } @@ -282,11 +282,9 @@ static int hci_uart_tty_open(struct tty_struct *tty) /* FIXME: why is this needed. Note don't use ldisc_ref here as the open path is before the ldisc is referencable */ - if (tty->ldisc.flush_buffer) - tty->ldisc.flush_buffer(tty); - - if (tty->driver && tty->driver->flush_buffer) - tty->driver->flush_buffer(tty); + if (tty->ldisc.ops->flush_buffer) + tty->ldisc.ops->flush_buffer(tty); + tty_driver_flush_buffer(tty); return 0; } @@ -372,9 +370,7 @@ static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, char *f hu->hdev->stat.byte_rx += count; spin_unlock(&hu->rx_lock); - if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) && - tty->driver->unthrottle) - tty->driver->unthrottle(tty); + tty_unthrottle(tty); } static int hci_uart_register_dev(struct hci_uart *hu) @@ -488,7 +484,7 @@ static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file, return -EUNATCH; default: - err = n_tty_ioctl(tty, file, cmd, arg); + err = n_tty_ioctl_helper(tty, file, cmd, arg); break; }; @@ -518,7 +514,7 @@ static unsigned int hci_uart_tty_poll(struct tty_struct *tty, static int __init hci_uart_init(void) { - static struct tty_ldisc hci_uart_ldisc; + static struct tty_ldisc_ops hci_uart_ldisc; int err; BT_INFO("HCI UART driver ver %s", VERSION); @@ -581,7 +577,7 @@ module_exit(hci_uart_exit); module_param(reset, bool, 0644); MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); -MODULE_AUTHOR("Maxim Krasnyansky , Marcel Holtmann "); +MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Bluetooth HCI UART driver ver " VERSION); MODULE_VERSION(VERSION); MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/hci_usb.c b/drivers/bluetooth/hci_usb.c index 98a9cde..3c45392 100644 --- a/drivers/bluetooth/hci_usb.c +++ b/drivers/bluetooth/hci_usb.c @@ -62,19 +62,18 @@ #define URB_ZERO_PACKET 0 #endif -static int ignore = 0; -static int ignore_dga = 0; -static int ignore_csr = 0; -static int ignore_sniffer = 0; -static int disable_scofix = 0; -static int force_scofix = 0; -static int reset = 0; +static int ignore_dga; +static int ignore_csr; +static int ignore_sniffer; +static int disable_scofix; +static int force_scofix; +static int reset; #ifdef CONFIG_BT_HCIUSB_SCO static int isoc = 2; #endif -#define VERSION "2.9" +#define VERSION "2.10" static struct usb_driver hci_usb_driver; @@ -111,10 +110,12 @@ static struct usb_device_id blacklist_ids[] = { { USB_DEVICE(0x0a5c, 0x2033), .driver_info = HCI_IGNORE }, /* Broadcom BCM2035 */ + { USB_DEVICE(0x0a5c, 0x2035), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, { USB_DEVICE(0x0a5c, 0x200a), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, { USB_DEVICE(0x0a5c, 0x2009), .driver_info = HCI_BCM92035 }, /* Broadcom BCM2045 */ + { USB_DEVICE(0x0a5c, 0x2039), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, { USB_DEVICE(0x0a5c, 0x2101), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, /* IBM/Lenovo ThinkPad with Broadcom chip */ @@ -132,6 +133,13 @@ static struct usb_device_id blacklist_ids[] = { /* Dell laptop with Broadcom chip */ { USB_DEVICE(0x413c, 0x8126), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, + /* Dell Wireless 370 */ + { USB_DEVICE(0x413c, 0x8156), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, + /* Dell Wireless 410 */ + { USB_DEVICE(0x413c, 0x8152), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, + + /* Broadcom 2046 */ + { USB_DEVICE(0x0a5c, 0x2151), .driver_info = HCI_RESET }, /* Microsoft Wireless Transceiver for Bluetooth 2.0 */ { USB_DEVICE(0x045e, 0x009c), .driver_info = HCI_RESET }, @@ -147,6 +155,9 @@ static struct usb_device_id blacklist_ids[] = { { USB_DEVICE(0x0400, 0x0807), .driver_info = HCI_BROKEN_ISOC }, { USB_DEVICE(0x0400, 0x080a), .driver_info = HCI_BROKEN_ISOC }, + /* CONWISE Technology based adapters with buggy SCO support */ + { USB_DEVICE(0x0e5e, 0x6622), .driver_info = HCI_BROKEN_ISOC }, + /* Belkin F8T012 and F8T013 devices */ { USB_DEVICE(0x050d, 0x0012), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, { USB_DEVICE(0x050d, 0x0013), .driver_info = HCI_RESET | HCI_WRONG_SCO_MTU }, @@ -260,7 +271,7 @@ static int hci_usb_intr_rx_submit(struct hci_usb *husb) BT_ERR("%s intr rx submit failed urb %p err %d", husb->hdev->name, urb, err); _urb_unlink(_urb); - _urb_free(_urb); + kfree(_urb); kfree(buf); } return err; @@ -297,7 +308,7 @@ static int hci_usb_bulk_rx_submit(struct hci_usb *husb) BT_ERR("%s bulk rx submit failed urb %p err %d", husb->hdev->name, urb, err); _urb_unlink(_urb); - _urb_free(_urb); + kfree(_urb); kfree(buf); } return err; @@ -348,7 +359,7 @@ static int hci_usb_isoc_rx_submit(struct hci_usb *husb) BT_ERR("%s isoc rx submit failed urb %p err %d", husb->hdev->name, urb, err); _urb_unlink(_urb); - _urb_free(_urb); + kfree(_urb); kfree(buf); } return err; @@ -426,7 +437,7 @@ static void hci_usb_unlink_urbs(struct hci_usb *husb) husb->hdev->name, _urb, _urb->type, urb); kfree(urb->setup_packet); kfree(urb->transfer_buffer); - _urb_free(_urb); + kfree(_urb); } } } @@ -485,7 +496,7 @@ static inline int hci_usb_send_ctrl(struct hci_usb *husb, struct sk_buff *skb) dr = kmalloc(sizeof(*dr), GFP_ATOMIC); if (!dr) { - _urb_free(_urb); + kfree(_urb); return -ENOMEM; } } else @@ -789,7 +800,7 @@ static int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id id = match; } - if (ignore || id->driver_info & HCI_IGNORE) + if (id->driver_info & HCI_IGNORE) return -ENODEV; if (ignore_dga && id->driver_info & HCI_DIGIANSWER) @@ -1096,9 +1107,6 @@ static void __exit hci_usb_exit(void) module_init(hci_usb_init); module_exit(hci_usb_exit); -module_param(ignore, bool, 0644); -MODULE_PARM_DESC(ignore, "Ignore devices from the matching table"); - module_param(ignore_dga, bool, 0644); MODULE_PARM_DESC(ignore_dga, "Ignore devices with id 08fd:0001"); @@ -1122,7 +1130,7 @@ module_param(isoc, int, 0644); MODULE_PARM_DESC(isoc, "Set isochronous transfers for SCO over HCI support"); #endif -MODULE_AUTHOR("Maxim Krasnyansky , Marcel Holtmann "); +MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Bluetooth HCI USB driver ver " VERSION); MODULE_VERSION(VERSION); MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/hci_usb.h b/drivers/bluetooth/hci_usb.h index 56cd3a9..8e65991 100644 --- a/drivers/bluetooth/hci_usb.h +++ b/drivers/bluetooth/hci_usb.h @@ -60,11 +60,6 @@ struct _urb { struct urb urb; }; -static inline void _urb_free(struct _urb *_urb) -{ - kfree(_urb); -} - static inline void _urb_queue_init(struct _urb_queue *q) { INIT_LIST_HEAD(&q->head); @@ -75,7 +70,8 @@ static inline void _urb_queue_head(struct _urb_queue *q, struct _urb *_urb) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); - list_add(&_urb->list, &q->head); _urb->queue = q; + /* _urb_unlink needs to know which spinlock to use, thus smp_mb(). */ + _urb->queue = q; smp_mb(); list_add(&_urb->list, &q->head); spin_unlock_irqrestore(&q->lock, flags); } @@ -83,19 +79,23 @@ static inline void _urb_queue_tail(struct _urb_queue *q, struct _urb *_urb) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); - list_add_tail(&_urb->list, &q->head); _urb->queue = q; + /* _urb_unlink needs to know which spinlock to use, thus smp_mb(). */ + _urb->queue = q; smp_mb(); list_add_tail(&_urb->list, &q->head); spin_unlock_irqrestore(&q->lock, flags); } static inline void _urb_unlink(struct _urb *_urb) { - struct _urb_queue *q = _urb->queue; + struct _urb_queue *q; unsigned long flags; - if (q) { - spin_lock_irqsave(&q->lock, flags); - list_del(&_urb->list); _urb->queue = NULL; - spin_unlock_irqrestore(&q->lock, flags); - } + + smp_mb(); + q = _urb->queue; + /* If q is NULL, it will die at easy-to-debug NULL pointer dereference. + No need to BUG(). */ + spin_lock_irqsave(&q->lock, flags); + list_del(&_urb->list); _urb->queue = NULL; + spin_unlock_irqrestore(&q->lock, flags); } struct hci_usb { diff --git a/drivers/bluetooth/hci_vhci.c b/drivers/bluetooth/hci_vhci.c index 0638730..7320a71 100644 --- a/drivers/bluetooth/hci_vhci.c +++ b/drivers/bluetooth/hci_vhci.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -263,9 +264,11 @@ static int vhci_open(struct inode *inode, struct file *file) skb_queue_head_init(&data->readq); init_waitqueue_head(&data->read_wait); + lock_kernel(); hdev = hci_alloc_dev(); if (!hdev) { kfree(data); + unlock_kernel(); return -ENOMEM; } @@ -286,10 +289,12 @@ static int vhci_open(struct inode *inode, struct file *file) BT_ERR("Can't register HCI device"); kfree(data); hci_free_dev(hdev); + unlock_kernel(); return -EBUSY; } file->private_data = data; + unlock_kernel(); return nonseekable_open(inode, file); } @@ -313,18 +318,21 @@ static int vhci_release(struct inode *inode, struct file *file) static int vhci_fasync(int fd, struct file *file, int on) { struct vhci_data *data = file->private_data; - int err; + int err = 0; + lock_kernel(); err = fasync_helper(fd, file, on, &data->fasync); if (err < 0) - return err; + goto out; if (on) data->flags |= VHCI_FASYNC; else data->flags &= ~VHCI_FASYNC; - return 0; +out: + unlock_kernel(); + return err; } static const struct file_operations vhci_fops = { @@ -369,7 +377,7 @@ module_exit(vhci_exit); module_param(minor, int, 0444); MODULE_PARM_DESC(minor, "Miscellaneous minor device number"); -MODULE_AUTHOR("Maxim Krasnyansky , Marcel Holtmann "); +MODULE_AUTHOR("Marcel Holtmann "); MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION); MODULE_VERSION(VERSION); MODULE_LICENSE("GPL"); diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 5696162..ecdea3c 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -1055,5 +1055,18 @@ config S3C_MEM If unsure, say Y. +choice + prompt "HHTECH MID GPIO" + help + Select LCD either 4.3'' or 7 '' + config LCD_4 + bool "Support for 4.3 inch LCD" + help + for HHTECH 4.3 inch, provide sysfs interface to APP + config LCD_7 + bool "Support for 7 inch LCD" + help + for HHTECH 7 inch LCD +endchoice endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 8846ea7..7e08964 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -7,7 +7,7 @@ # FONTMAPFILE = cp437.uni -obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o +obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o @@ -112,6 +112,8 @@ obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o obj-$(CONFIG_S3C_MEM) += s3c_mem.o +obj-$(CONFIG_LCD_4) += hhtech_gpio.o +obj-$(CONFIG_LCD_7) += hhtech_gpio.o # Files generated that shall be removed upon make clean clean-files := consolemap_deftbl.c defkeymap.c diff --git a/drivers/char/apm-emulation.c b/drivers/char/apm-emulation.c index 17d5431..fe9f638 100644 --- a/drivers/char/apm-emulation.c +++ b/drivers/char/apm-emulation.c @@ -108,6 +108,7 @@ static DEFINE_MUTEX(state_lock); static const char driver_version[] = "1.13"; /* no spaces */ +extern unsigned int s3c_adc_value(unsigned int s3c_adc_port); /* * Compatibility cruft until the IPAQ people move over to the new @@ -115,6 +116,13 @@ static const char driver_version[] = "1.13"; /* no spaces */ */ static void __apm_get_power_status(struct apm_power_info *info) { + int battery_current_charge = s3c_adc_value(0); + int battery_reference_charge = s3c_adc_value(1); + while(battery_reference_charge > 3050 || battery_reference_charge < 3020) { + battery_current_charge = s3c_adc_value(0); + battery_reference_charge = s3c_adc_value(1); + } + info->battery_life = battery_current_charge * 100 / battery_reference_charge; } /* diff --git a/drivers/char/hhtech_gpio.c b/drivers/char/hhtech_gpio.c new file mode 100644 index 0000000..b3a2fd8 --- /dev/null +++ b/drivers/char/hhtech_gpio.c @@ -0,0 +1,883 @@ +/* + * S3C64XX GPIO Driver + * + * Copyright (C) 2008 - 2009 HHTECH. + * + * Author: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this progSoftwareram; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(CONFIG_LCD_4) +static unsigned int system_flag = 0; +#else +static unsigned int system_flag = 1; +#endif + +static struct delayed_work headp_detect_work; +static struct delayed_work battery_work; +int current_battery = 0; +EXPORT_SYMBOL(current_battery); +static int user_disable_speaker = 0; + +unsigned long rtc_wakeup_time = 30 * 60; +EXPORT_SYMBOL(rtc_wakeup_time); + +unsigned int wakeup_status = 0; // 0: waked up by external IRQ; 1: waked up by RTC +EXPORT_SYMBOL(wakeup_status); + +/* ################# setting ################## */ + +// HHTECH set system power enable +static void set_sys_power(int sw) +{ + if (sw) + gpio_set_value(GPIO_PWR_EN, 0); //power on + else + gpio_set_value(GPIO_PWR_EN, 1); //pwoer off +} + +// HHTECH set Charging mode +static void set_charge_mode(int sw) +{ + if (sw) + gpio_set_value(GPIO_CHARGER_EN, 1); //860ma + else + gpio_set_value(GPIO_CHARGER_EN, 0); //200ma +} + +// HHTECH set backlight MP1530 enable +static void set_backlight_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_LCD_BLIGHT_EN, 1); //enable + else + gpio_set_value(GPIO_LCD_BLIGHT_EN, 0); //disable +} + + +// HHTECH set Vidoe amplifier switch +static void set_video_amp(int sw) +{ + if (sw) + gpio_set_value(GPIO_VIDEOAMP_EN, 1); //open + else + gpio_set_value(GPIO_VIDEOAMP_EN, 0); //close +} + +// HHTECH set USB system (HOST and OTG) power enable +static void set_usb_syspwr_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_USB_EN, 1); //open + else + gpio_set_value(GPIO_USB_EN, 0); //close +} + +// HHTECH set USB HOST power enable +static void set_usb_hostpwr_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_USB_HOSTPWR_EN, 1); //open + else + gpio_set_value(GPIO_USB_HOSTPWR_EN, 0); //close +} + +// HHTECH set USB OTG DRV enable +static void set_usb_otgdrv_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_USB_OTGDRV_EN, 1); //open + else + gpio_set_value(GPIO_USB_OTGDRV_EN, 0); //close +} + +// HHTECH set power hold enable +static void set_pwr_key(int sw) +{ + if (sw) + gpio_set_value(GPIO_PWR_HOLD, 1); + else + gpio_set_value(GPIO_PWR_HOLD, 0); +} + +// HHTECH set speaker +static void set_speaker_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_SPEAKER_EN, 1); //open + else + gpio_set_value(GPIO_SPEAKER_EN, 0); //close +} + +// HHTECH set wifi +static void set_wifi_en(int sw) +{ + if (sw) { + gpio_set_value(GPIO_WIFI_EN, 1); //open + gpio_direction_output(GPIO_WIFI_RESET, 0); + mdelay(100); + gpio_set_value(GPIO_WIFI_RESET, 1); + //Release reset + gpio_direction_input(GPIO_WIFI_RESET); + } else + gpio_set_value(GPIO_WIFI_EN, 0); //close +} + +// HHTECH set led1 and led2 +static void set_led1_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_LED1_EN, 0); //turn on + else + gpio_set_value(GPIO_LED1_EN, 1); //turn off +} + +static void set_led2_en(int sw) +{ + if (sw) + gpio_set_value(GPIO_LED2_EN, 0); + else + gpio_set_value(GPIO_LED2_EN, 1); +} + +/*@@@@@@@@@@@@@@@@@@@@@@@ sysfs interface @@@@@@@@@@@*/ + +#ifdef CONFIG_SYSFS +static ssize_t hhtech_sysfs_show_dc(struct device *dev, + struct device_attribute *attr, char *buf) +{ +#if defined (CONFIG_LCD_4) + strcpy(buf, gpio_get_value(GPIO_DC_DETE) ? "on" : "off"); +#else + strcpy(buf, gpio_get_value(GPIO_DC_DETE) ? "off" : "on"); +#endif + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_show_sd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, gpio_get_value(GPIO_SD_WP) ? "off" : "on"); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_show_system_flag(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, system_flag ? "1" : "0"); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_show_headp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, gpio_get_value(GPIO_HEADPHONE_S) ? "on" : "off"); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_show_blight_s(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, gpio_get_value(GPIO_LCD_BLIGHT_S) ? "on" : "off"); + strcat(buf, "\n"); + return strlen(buf); +} + +extern unsigned int s3c_adc_value(unsigned int s3c_adc_port); +static int read_battery(void) +{ + int battery_life = 0, reference_value = 0; + static int old_battery_life = 0, old_reference_value = 0; + + //ref voltage:2.4V,battery max :4.2V + if ((battery_life = s3c_adc_value(0)) == 0) { + if (old_battery_life == 0) { + while (!(battery_life = s3c_adc_value(0))); + old_battery_life = battery_life; + } else + battery_life = old_battery_life; + } + if ((reference_value = s3c_adc_value(1)) == 0) { + if (old_reference_value == 0) { + while (!(reference_value = s3c_adc_value(1))); + old_reference_value = reference_value; + } else + reference_value = old_reference_value; + } + battery_life = (battery_life * 24000) / (reference_value * 42); + + return battery_life; +} + +static int get_battery_life(void) +{ + int i; + int count = 0; + int battery_life = 1000;//Default max power + int battery_life_sum = 0; + for (i = 0; i < 10; i++) { + int tmp = read_battery(); + if (tmp < 700 || tmp > 1000) + continue; + battery_life_sum += tmp; + count++; + } + if (count) + battery_life = battery_life_sum / count; + return battery_life; +} + +extern void battery_update_work(struct work_struct* work) +{ + int dc_status; + +#if defined (CONFIG_LCD_4) + dc_status = gpio_get_value(GPIO_DC_DETE) ? 1 : 0; +#else + dc_status = gpio_get_value(GPIO_DC_DETE) ? 0 : 1; +#endif + + current_battery = get_battery_life(); + if (current_battery < 785 && dc_status == 0) + set_sys_power(0); + schedule_delayed_work(&battery_work, 30 * HZ); +} + +static ssize_t hhtech_sysfs_show_battery(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char s[20]; + + // printk("ADC voltage value=%u,conference voltage =%u \n",s3c_adc_value(0),s3c_adc_value(1));//HHTECH wk + sprintf(s,"%d", current_battery); + strcpy(buf, s); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_show_usbhost(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, gpio_get_value(GPIO_USB_HOST_STATUS) ? "on" : "off"); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_show_usbotg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + strcpy(buf, gpio_get_value(GPIO_USB_OTG_STATUS) ? "on" : "off"); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t hhtech_sysfs_charge_s(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int s=(gpio_get_value(GPIO_CHARG_S1)<<1) + gpio_get_value(GPIO_CHARG_S2); + switch (s) { + case 0: + strcpy(buf,"0"); + break; + case 1: + strcpy(buf,"1"); + break; + case 2: + strcpy(buf,"2"); + break; + case 3: + strcpy(buf,"3"); + break; + } + strcat(buf, "\n"); + return strlen(buf); +} + +static int hhtech_sysfs_show_power(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_PWR_EN)); +} + +static ssize_t hhtech_sysfs_store_power(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ +// int val = simple_strtoul(buf, NULL, 0); + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_sys_power(1); //poweron + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_sys_power(0); //poweroff + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_charge(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_CHARGER_EN)); +} + +static ssize_t hhtech_sysfs_store_charge(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_charge_mode(1); //860ma + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_charge_mode(0); //200ma + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_blight(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_LCD_BLIGHT_EN)); +} + +static ssize_t hhtech_sysfs_store_blight(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_backlight_en(1); // enable MP1530 + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_backlight_en(0); //disable + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_amp(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_VIDEOAMP_EN)); +} + +static ssize_t hhtech_sysfs_store_amp(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_video_amp(1); // open Vidoe amplifier output + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_video_amp(0); // close + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_usbpwr(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_USB_EN)); +} + +static ssize_t hhtech_sysfs_store_usbpwr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_usb_syspwr_en(1); // USB system power enable + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_usb_syspwr_en(0); // disable + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_usbhostpwr(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_USB_HOSTPWR_EN)); +} + +static ssize_t hhtech_sysfs_store_usbhostpwr(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_usb_hostpwr_en(1); // USB system power enable + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_usb_hostpwr_en(0); // disable + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_usbotgdrv(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_USB_OTGDRV_EN)); +} + +static ssize_t hhtech_sysfs_store_usbotgdrv(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_usb_otgdrv_en(1); + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_usb_otgdrv_en(0); + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_speaker(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_SPEAKER_EN)); +} + +static ssize_t hhtech_sysfs_store_speaker(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) { + if (!gpio_get_value(GPIO_HEADPHONE_S)) + set_speaker_en(1); // speaker enable + user_disable_speaker = 0; + } else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) { + set_speaker_en(0); // disable + user_disable_speaker = 1; + } else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_wifi(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_WIFI_EN)); +} + +static ssize_t hhtech_sysfs_store_wifi(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_wifi_en(1); // wifi enable + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_wifi_en(0); // disable + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_led1(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_LED1_EN)); +} + +static ssize_t hhtech_sysfs_store_led1(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_led1_en(1); + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_led1_en(0); + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_led2(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(GPIO_LED2_EN)); +} + +static ssize_t hhtech_sysfs_store_led2(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + if (len < 1) + return -EINVAL; + + if (strnicmp(buf, "on", 2) == 0 || strnicmp(buf, "1", 1) == 0) + set_led2_en(1); + else if (strnicmp(buf, "off", 3) == 0 || strnicmp(buf, "0", 1) == 0) + set_led2_en(0); + else + return -EINVAL; + return len; +} + +static int hhtech_sysfs_show_rtc_wk_time(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", rtc_wakeup_time); +} + +static ssize_t hhtech_sysfs_store_rtc_wk_time(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + unsigned long value = simple_strtoul(buf, NULL, 10); + + if (len < 1) + return -EINVAL; + + if (value < 0 || value > 2 * 60 * 60) + return -ERANGE; + else + rtc_wakeup_time = value; + return len; +} + +static int hhtech_sysfs_show_wk_status(struct device *dev, struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", wakeup_status); +} + + #if defined (CONFIG_LCD_7) +/* USB host protect status change Interrupt handle */ +static irqreturn_t usbhost_status_irq(int irq, void *dev_id) +{ + int pin_level = gpio_get_value(GPIO_USB_HOST_STATUS); + __raw_writel(__raw_readl(S3C_EINTMASK)&(~(0x1 << 18)), S3C_EINTMASK); // mask the irq + printk(KERN_DEBUG "Usb host status: %d\n", pin_level); + if (0 == pin_level) { + gpio_direction_output(GPIO_USB_OTGDRV_EN, 0); + gpio_direction_output(GPIO_USB_EN, 0); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0xf << 4)) | + (S3C_EXTINT_FALLEDGE << 4), S3C_EINTCON1); // enable the irq again + } + + return IRQ_HANDLED; +} + #endif + +/* USB otg protect status change Interrupt handle */ +static irqreturn_t usbotg_status_irq(int irq, void *dev_id) +{ + int pin_level = gpio_get_value(GPIO_USB_OTG_STATUS); + __raw_writel(__raw_readl(S3C_EINTMASK)&(~(0x1 << 19)), S3C_EINTMASK); // mask the irq + printk(KERN_DEBUG "Usb otg status: %d\n", pin_level); + if (0 == pin_level) { + gpio_direction_output(GPIO_USB_HOSTPWR_EN, 0); + gpio_direction_output(GPIO_USB_EN, 0); + __raw_writel((__raw_readl(S3C_EINTCON1) & ~(0xf << 4)) | + (S3C_EXTINT_FALLEDGE << 4), S3C_EINTCON1); // enable the irq again + } + + return IRQ_HANDLED; +} + +extern void headp_update_volume(struct work_struct* work); + +/* headphone plug in and out Interrupt handle */ +static irqreturn_t headp_irq(int irq, void *dev_id) +{ + int pin_level = gpio_get_value(GPIO_HEADPHONE_S); + __raw_writel(__raw_readl(S3C_EINTMASK)&(~(0x1 << 20)), S3C_EINTMASK); + if (user_disable_speaker == 0) { + if (pin_level) + gpio_set_value(GPIO_SPEAKER_EN, 0); + else + gpio_set_value(GPIO_SPEAKER_EN, 1); + } + cancel_delayed_work(&headp_detect_work); + schedule_delayed_work(&headp_detect_work, msecs_to_jiffies(80)); + return IRQ_HANDLED; +} + +static DEVICE_ATTR(system_flag, 0444, hhtech_sysfs_show_system_flag, NULL); +static DEVICE_ATTR(headphone_s, 0444, hhtech_sysfs_show_headp, NULL); +static DEVICE_ATTR(dc_s, 0444, hhtech_sysfs_show_dc, NULL); +static DEVICE_ATTR(sd_s, 0444, hhtech_sysfs_show_sd, NULL); +static DEVICE_ATTR(charge_s, 0444,hhtech_sysfs_charge_s , NULL); +static DEVICE_ATTR(backlight_s, 0444, hhtech_sysfs_show_blight_s, NULL); +static DEVICE_ATTR(usbhost_s, 0444, hhtech_sysfs_show_usbhost, NULL); +static DEVICE_ATTR(usbotg_s, 0444, hhtech_sysfs_show_usbotg, NULL); +static DEVICE_ATTR(battery_s, 0444, hhtech_sysfs_show_battery, NULL); +static DEVICE_ATTR(pwr_en, 0666, hhtech_sysfs_show_power, hhtech_sysfs_store_power); +static DEVICE_ATTR(charge_en, 0666, hhtech_sysfs_show_charge, hhtech_sysfs_store_charge); +static DEVICE_ATTR(backlight_en, 0666,hhtech_sysfs_show_blight, hhtech_sysfs_store_blight); +static DEVICE_ATTR(amp_en, 0666,hhtech_sysfs_show_amp, hhtech_sysfs_store_amp); +static DEVICE_ATTR(usbpwr_en, 0666, hhtech_sysfs_show_usbpwr, hhtech_sysfs_store_usbpwr); +static DEVICE_ATTR(usbhostpwr_en, 0666, hhtech_sysfs_show_usbhostpwr, hhtech_sysfs_store_usbhostpwr); +static DEVICE_ATTR(usbotgdrv_en, 0666, hhtech_sysfs_show_usbotgdrv, hhtech_sysfs_store_usbotgdrv); +static DEVICE_ATTR(speaker_en, 0666, hhtech_sysfs_show_speaker, hhtech_sysfs_store_speaker); +static DEVICE_ATTR(wifi_en, 0666, hhtech_sysfs_show_wifi, hhtech_sysfs_store_wifi); +static DEVICE_ATTR(led1_en, 0666, hhtech_sysfs_show_led1, hhtech_sysfs_store_led1); +static DEVICE_ATTR(led2_en, 0666, hhtech_sysfs_show_led2, hhtech_sysfs_store_led2); +static DEVICE_ATTR(rtc_wk_time, 0666, hhtech_sysfs_show_rtc_wk_time, hhtech_sysfs_store_rtc_wk_time); +static DEVICE_ATTR(wk_status, 0666, hhtech_sysfs_show_wk_status, NULL); + +static struct attribute *attrs[] = { + &dev_attr_system_flag.attr, + &dev_attr_headphone_s.attr, + &dev_attr_dc_s.attr, + &dev_attr_sd_s.attr, + &dev_attr_charge_s.attr, + &dev_attr_backlight_s.attr, + &dev_attr_usbhost_s.attr, + &dev_attr_usbotg_s.attr, + &dev_attr_battery_s.attr, + &dev_attr_pwr_en.attr, + &dev_attr_charge_en.attr, + &dev_attr_backlight_en.attr, + &dev_attr_amp_en.attr, + &dev_attr_usbpwr_en.attr, + &dev_attr_usbhostpwr_en.attr, + &dev_attr_usbotgdrv_en.attr, + &dev_attr_speaker_en.attr, + &dev_attr_wifi_en.attr, + &dev_attr_led1_en.attr, + &dev_attr_led2_en.attr, + &dev_attr_rtc_wk_time.attr, + &dev_attr_wk_status.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +static int hhtech_gpio_sysfs_register(struct device* dev) +{ + return sysfs_create_group(&dev->kobj, &attr_group); +} +#else +static int hhtech_gpio_sysfs_register(struct device* dev) +{ + return 0; +} +#endif + +static int hhtech_gpio_probe(struct platform_device *pdev) +{ + int retval; + + retval = hhtech_gpio_sysfs_register(&pdev->dev); + if (retval < 0) { + printk(KERN_ERR "Create sys fs fail\n"); + return -1; + } + gpio_direction_input(GPIO_SD_WP); //S3C_GPK0 +#if defined (CONFIG_LCD_7) + gpio_direction_input(GPIO_USB_HOST_STATUS); //S3C_GPL10 + gpio_set_pin(GPIO_USB_HOST_STATUS, S3C_GPL10_EXTINT18); +#endif + gpio_direction_input(GPIO_USB_OTG_STATUS); //S3C_GPL11 + gpio_set_pin(GPIO_USB_OTG_STATUS, S3C_GPL11_EXTINT19); + gpio_direction_input(GPIO_HEADPHONE_S); //S3C_GPL12 + gpio_set_pin(GPIO_HEADPHONE_S, S3C_GPL12_EXTINT20); + + gpio_direction_input(GPIO_LCD_BLIGHT_S); //S3C_GPM4 + gpio_direction_input(GPIO_DC_DETE); //S3C_GPL13 + gpio_direction_input(GPIO_CHARG_S1); //S3C_GPK4 + gpio_direction_input(GPIO_CHARG_S2); //S3C_GPK5 + gpio_direction_input(GPIO_PWR_HOLD); //S3C_GPL14 +#if defined (CONFIG_LCD_4) + gpio_direction_output(GPIO_USB_EN, 0); //S3C_GPL0 : close + gpio_direction_output(GPIO_USB_HOSTPWR_EN, 0); //S3C_GPL1 : close +#else + gpio_direction_output(GPIO_USB_EN, 1); //S3C_GPL0 : open + gpio_direction_output(GPIO_USB_HOSTPWR_EN, 1); //S3C_GPL1 : open +#endif + //gpio_direction_output(GPIO_LCD_BLIGHT_EN, 1); //S3C_GPM3 : open + gpio_direction_output(GPIO_CHARGER_EN, 0); //S3C_GPK6 : 200ma + gpio_direction_output(GPIO_PWR_EN, 0); //S3C_GPK15 : open normal it's low + gpio_direction_output(GPIO_VIDEOAMP_EN, 0); //S3C_GPK13 : close + gpio_direction_output(GPIO_SPEAKER_EN, 0); //S3C_GPK12 : close + gpio_direction_output(GPIO_WIFI_EN, 1); //S3C_GPK1 : open + gpio_direction_output(GPIO_WIFI_RESET, 0); //S3C_GPK2 : open + mdelay(100); + gpio_set_value(GPIO_WIFI_RESET, 1); + gpio_direction_input(GPIO_WIFI_RESET); + gpio_direction_output(GPIO_USB_OTGDRV_EN, 0); //S3C_GPL8 : close + gpio_direction_output(GPIO_LED1_EN, 0); //S3C_GPN8 : turn on + if (NULL == strstr(saved_command_line, "rdinit")) + gpio_direction_output(GPIO_LED2_EN, 1); //S3C_GPN9 : turn off + else + gpio_direction_output(GPIO_LED2_EN, 0); //S3C_GPN9 : turn on + udelay(10); + + //retval = request_irq(gpio_to_irq(GPIO_HEADPHONE_S), headp_irq, + retval = request_irq(gpio_to_irq(GPIO_HEADPHONE_S), headp_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "Headphone detect", NULL); + if (retval) { + printk(KERN_ERR "Request headphone detect fail\n"); + goto error1; + } + + retval = request_irq(gpio_to_irq(GPIO_USB_OTG_STATUS), usbotg_status_irq, + IRQF_TRIGGER_FALLING, "Usb otg status detect", NULL); + if (retval) { + printk(KERN_ERR "Request usb otg status detect fail\n"); + goto error2; + } + +#if defined (CONFIG_LCD_7) + /*retval = request_irq(gpio_to_irq(GPIO_USB_HOST_STATUS), usbhost_status_irq, + IRQF_TRIGGER_FALLING, "Usb host status detect", NULL); + if (retval) { + printk(KERN_ERR "Request usb host status detect fail\n"); + goto error3; + }*/ +#endif + INIT_DELAYED_WORK(&headp_detect_work, headp_update_volume); + INIT_DELAYED_WORK(&battery_work, battery_update_work); + battery_update_work(NULL); + + return 0; + + error3: + free_irq(gpio_to_irq(GPIO_USB_HOST_STATUS), NULL); + error2: + free_irq(gpio_to_irq(GPIO_HEADPHONE_S), NULL); + error1: + return retval; +} + +static int hhtech_gpio_remove(struct platform_device *dev) +{ + free_irq(gpio_to_irq(GPIO_HEADPHONE_S), NULL); +#if defined (CONFIG_LCD_7) + free_irq(gpio_to_irq(GPIO_USB_HOST_STATUS), NULL); +#endif + free_irq(gpio_to_irq(GPIO_USB_OTG_STATUS), NULL); + + return 0; +} + +#ifdef CONFIG_PM +static struct sleep_save_phy save_gpio[] = { + SAVE_ITEM(GPIO_USB_HOSTPWR_EN), + SAVE_ITEM(GPIO_USB_OTGDRV_EN), + SAVE_ITEM(GPIO_USB_EN), +}; + +static void gpio_do_save(struct sleep_save_phy *ptr, int count) +{ + for (; count > 0; count--, ptr++) + ptr->val = gpio_get_value(ptr->reg); +} + +static void gpio_do_load(struct sleep_save_phy *ptr, int count) +{ + for (; count > 0; count--, ptr++) + gpio_set_value(ptr->reg, ptr->val); +} + +static int hhtech_gpio_suspend(struct platform_device *dev, pm_message_t state) +{ + /* Becareful: MMC must be suspended after this driver */ + set_wifi_en(0); + /* Close USB Power */ + gpio_do_save(save_gpio, ARRAY_SIZE(save_gpio)); + set_usb_hostpwr_en(0); + set_usb_otgdrv_en(0); + set_usb_syspwr_en(0); + return 0; +} +extern void __iomem *s3c_rtc_base; + +static int hhtech_gpio_resume(struct platform_device *dev) +{ + int dc_status; + +#if defined (CONFIG_LCD_4) + dc_status = gpio_get_value(GPIO_DC_DETE) ? 1 : 0; +#else + dc_status = gpio_get_value(GPIO_DC_DETE) ? 0 : 1; +#endif + wakeup_status = 0; + + if (0 == dc_status) { + if (get_battery_life() < 833) // battery volume < 3.5 V + set_sys_power(0); + if (readb(s3c_rtc_base + S3C_INTP) & S3C_INTP_ALM) + wakeup_status = 1; + } + + gpio_do_load(save_gpio, ARRAY_SIZE(save_gpio)); + set_wifi_en(1); + return 0; +} +#else +#define hhtech_gpio_suspend NULL +#define hhtech_gpio_resume NULL +#endif + + +static struct platform_driver hhtech_gpio = { + .probe = hhtech_gpio_probe, + .remove = hhtech_gpio_remove, + .suspend = hhtech_gpio_suspend, + .resume = hhtech_gpio_resume, + .driver = { + .name = "hhtech_gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init hhtech_gpio_init(void) +{ + user_disable_speaker = 0; + return platform_driver_register(&hhtech_gpio); +} + +static void __exit hhtech_gpio_exit(void) +{ + platform_driver_unregister(&hhtech_gpio); +} + +module_init(hhtech_gpio_init); +module_exit(hhtech_gpio_exit); + +//EXPORT_SYMBOL(set_sys_power); + +MODULE_AUTHOR("Wangkang"); +MODULE_DESCRIPTION("S3C64xx GPIO driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 6a9ca4b..8e224fe 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -9,7 +9,6 @@ */ #include -#include #include #include @@ -26,25 +25,47 @@ #include -static irqreturn_t gpio_keys_isr(int irq, void *dev_id) +struct gpio_button_data { + struct gpio_keys_button *button; + struct input_dev *input; + struct timer_list timer; +}; + +struct gpio_keys_drvdata { + struct input_dev *input; + struct gpio_button_data data[0]; +}; + +static void gpio_keys_report_event(struct gpio_button_data *bdata) { - int i; - struct platform_device *pdev = dev_id; - struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; - struct input_dev *input = platform_get_drvdata(pdev); + struct gpio_keys_button *button = bdata->button; + struct input_dev *input = bdata->input; + unsigned int type = button->type ?: EV_KEY; + int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low; - for (i = 0; i < pdata->nbuttons; i++) { - struct gpio_keys_button *button = &pdata->buttons[i]; - int gpio = button->gpio; + input_event(input, type, button->code, !!state); + input_sync(input); +} - if (irq == gpio_to_irq(gpio)) { - unsigned int type = button->type ?: EV_KEY; - int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low; +static void gpio_check_button(unsigned long _data) +{ + struct gpio_button_data *data = (struct gpio_button_data *)_data; - input_event(input, type, button->code, !!state); - input_sync(input); - } - } + gpio_keys_report_event(data); +} + +static irqreturn_t gpio_keys_isr(int irq, void *dev_id) +{ + struct gpio_button_data *bdata = dev_id; + struct gpio_keys_button *button = bdata->button; + + BUG_ON(irq != gpio_to_irq(button->gpio)); + + if (button->debounce_interval) + mod_timer(&bdata->timer, + jiffies + msecs_to_jiffies(button->debounce_interval)); + else + gpio_keys_report_event(bdata); return IRQ_HANDLED; } @@ -52,17 +73,20 @@ static irqreturn_t gpio_keys_isr(int irq, void *dev_id) static int __devinit gpio_keys_probe(struct platform_device *pdev) { struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + struct gpio_keys_drvdata *ddata; struct input_dev *input; int i, error; - int wakeup = 0; + ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + + pdata->nbuttons * sizeof(struct gpio_button_data), + GFP_KERNEL); input = input_allocate_device(); - if (!input) - return -ENOMEM; - - platform_set_drvdata(pdev, input); + if (!ddata || !input) { + error = -ENOMEM; + goto fail1; + } - input->evbit[0] = BIT_MASK(EV_KEY); + platform_set_drvdata(pdev, ddata); input->name = pdev->name; input->phys = "gpio-keys/input0"; @@ -73,16 +97,28 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) input->id.product = 0x0001; input->id.version = 0x0100; + /* Enable auto repeat feature of Linux input subsystem */ + if (pdata->rep) + __set_bit(EV_REP, input->evbit); + + ddata->input = input; + for (i = 0; i < pdata->nbuttons; i++) { struct gpio_keys_button *button = &pdata->buttons[i]; + struct gpio_button_data *bdata = &ddata->data[i]; int irq; unsigned int type = button->type ?: EV_KEY; + bdata->input = input; + bdata->button = button; + setup_timer(&bdata->timer, + gpio_check_button, (unsigned long)bdata); + error = gpio_request(button->gpio, button->desc ?: "gpio_keys"); if (error < 0) { pr_err("gpio-keys: failed to request GPIO %d," " error %d\n", button->gpio, error); - goto fail; + goto fail2; } error = gpio_direction_input(button->gpio); @@ -91,7 +127,7 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) " direction for GPIO %d, error %d\n", button->gpio, error); gpio_free(button->gpio); - goto fail; + goto fail2; } irq = gpio_to_irq(button->gpio); @@ -101,24 +137,21 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) " for GPIO %d, error %d\n", button->gpio, error); gpio_free(button->gpio); - goto fail; + goto fail2; } error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, button->desc ? button->desc : "gpio_keys", - pdev); + bdata); if (error) { pr_err("gpio-keys: Unable to claim irq %d; error %d\n", irq, error); gpio_free(button->gpio); - goto fail; + goto fail2; } - if (button->wakeup) - wakeup = 1; - input_set_capability(input, type, button->code); } @@ -126,21 +159,23 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) if (error) { pr_err("gpio-keys: Unable to register input device, " "error: %d\n", error); - goto fail; + goto fail2; } - device_init_wakeup(&pdev->dev, wakeup); - return 0; - fail: + fail2: while (--i >= 0) { - free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev); + free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]); + if (pdata->buttons[i].debounce_interval) + del_timer_sync(&ddata->data[i].timer); gpio_free(pdata->buttons[i].gpio); } platform_set_drvdata(pdev, NULL); + fail1: input_free_device(input); + kfree(ddata); return error; } @@ -148,14 +183,15 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) static int __devexit gpio_keys_remove(struct platform_device *pdev) { struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; - struct input_dev *input = platform_get_drvdata(pdev); + struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; int i; - device_init_wakeup(&pdev->dev, 0); - for (i = 0; i < pdata->nbuttons; i++) { int irq = gpio_to_irq(pdata->buttons[i].gpio); - free_irq(irq, pdev); + free_irq(irq, &ddata->data[i]); + if (pdata->buttons[i].debounce_interval) + del_timer_sync(&ddata->data[i].timer); gpio_free(pdata->buttons[i].gpio); } @@ -165,54 +201,17 @@ static int __devexit gpio_keys_remove(struct platform_device *pdev) } -#ifdef CONFIG_PM -static int gpio_keys_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; - int i; - - if (device_may_wakeup(&pdev->dev)) { - for (i = 0; i < pdata->nbuttons; i++) { - struct gpio_keys_button *button = &pdata->buttons[i]; - if (button->wakeup) { - int irq = gpio_to_irq(button->gpio); - enable_irq_wake(irq); - } - } - } - - return 0; -} - -static int gpio_keys_resume(struct platform_device *pdev) -{ - struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; - int i; - - if (device_may_wakeup(&pdev->dev)) { - for (i = 0; i < pdata->nbuttons; i++) { - struct gpio_keys_button *button = &pdata->buttons[i]; - if (button->wakeup) { - int irq = gpio_to_irq(button->gpio); - disable_irq_wake(irq); - } - } - } - - return 0; -} -#else #define gpio_keys_suspend NULL #define gpio_keys_resume NULL -#endif -struct platform_driver gpio_keys_device_driver = { +static struct platform_driver gpio_keys_device_driver = { .probe = gpio_keys_probe, .remove = __devexit_p(gpio_keys_remove), .suspend = gpio_keys_suspend, .resume = gpio_keys_resume, .driver = { .name = "gpio-keys", + .owner = THIS_MODULE, } }; @@ -226,9 +225,10 @@ static void __exit gpio_keys_exit(void) platform_driver_unregister(&gpio_keys_device_driver); } -module_init(gpio_keys_init); +late_initcall(gpio_keys_init); module_exit(gpio_keys_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Phil Blundell "); MODULE_DESCRIPTION("Keyboard driver for CPU GPIOs"); +MODULE_ALIAS("platform:gpio-keys"); diff --git a/drivers/input/keyboard/s3c-keypad.c b/drivers/input/keyboard/s3c-keypad.c index 7bd73a2..e12b83a 100644 --- a/drivers/input/keyboard/s3c-keypad.c +++ b/drivers/input/keyboard/s3c-keypad.c @@ -51,7 +51,7 @@ static int keypad_scan(u32 *keymask_low, u32 *keymask_high) for (i=0; idev; writel(KEYIFCON_CLEAR, key_base+S3C_KEYIFCON); input_unregister_device(input_dev); - kfree(pdev->dev.platform_data); + kfree(s3c_keypad); free_irq(IRQ_KEYPAD, (void *) pdev); - del_timer(&keypad_timer); + del_timer(&keypad_timer); printk(DEVICE_NAME " Removed.\n"); return 0; } diff --git a/drivers/input/keyboard/s3c-keypad.h b/drivers/input/keyboard/s3c-keypad.h index 4207bfa..e1e4113 100644 --- a/drivers/input/keyboard/s3c-keypad.h +++ b/drivers/input/keyboard/s3c-keypad.h @@ -9,12 +9,12 @@ static void __iomem *key_base; #if defined(CONFIG_CPU_S3C6400) || defined (CONFIG_CPU_S3C6410) -#define KEYPAD_COLUMNS 8 -#define KEYPAD_ROWS 8 -#define MAX_KEYPAD_NR 64 /* 8*8 */ +#define KEYPAD_COLUMNS 2 +#define KEYPAD_ROWS 8 +#define MAX_KEYPAD_NR 16 /* 8*8 */ #define MAX_KEYMASK_NR 32 -int keypad_keycode[] = { +/*int keypad_keycode[] = { 1, 2, KEY_1, KEY_Q, KEY_A, 6, 7, KEY_LEFT, 9, 10, KEY_2, KEY_W, KEY_S, KEY_Z, KEY_RIGHT, 16, 17, 18, KEY_3, KEY_E, KEY_D, KEY_X, 23, KEY_UP, @@ -24,22 +24,39 @@ int keypad_keycode[] = { KEY_M, KEY_L, KEY_7, KEY_U, KEY_J, KEY_N, 55, KEY_ENTER, KEY_LEFTSHIFT, KEY_9, KEY_8, KEY_I, KEY_K, KEY_B, 63, KEY_COMMA }; +*/ +int keypad_keycode[] = { + 1,2,3,4,5,6,7,8, + 9,10,11,12,13,14,15,16, + 17,18,19,20,21,22,23,24, + 25,26,27,28,29,30,31,32, + 33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56, + 57,58,59,60,61,62,63,64 + }; #define KEYPAD_DELAY (50) -#define KEYPAD_ROW_GPIOCON S3C_GPK1CON -#define KEYPAD_ROW_GPIOPUD S3C_GPKPU +//#define KEYPAD_ROW_GPIOCON S3C_GPK1CON //HHTECH wk +//#define KEYPAD_ROW_GPIOPUD S3C_GPKPU //HHTECH wk +#define KEYPAD_ROW_GPIOCON S3C_GPNCON //HHTECH wk +#define KEYPAD_ROW_GPIOPUD S3C_GPNPU //HHTECH wk #define KEYPAD_COL_GPIOCON S3C_GPL0CON #define KEYPAD_COL_GPIOPUD S3C_GPLPU #define KEYPAD_ROW_GPIO_SET \ - ((readl(KEYPAD_ROW_GPIOCON) & ~(0xfffffff)) | 0x33333333) + ((readl(KEYPAD_ROW_GPIOCON) & ~(0xffff)) | 0xffff) + //((readl(KEYPAD_ROW_GPIOCON) & ~(0xfffffff)) | 0x33333333) #define KEYPAD_COL_GPIO_SET \ - ((readl(KEYPAD_COL_GPIOCON) & ~(0xfffffff)) | 0x33333333) + ((readl(KEYPAD_COL_GPIOCON) & ~(0xff000000)) | 0x33000000) +// ((readl(KEYPAD_COL_GPIOCON) & ~(0xfffffff)) | 0x33333333) -#define KEYPAD_ROW_GPIOPUD_DIS (readl(KEYPAD_ROW_GPIOPUD) & ~(0xffff<<16)) -#define KEYPAD_COL_GPIOPUD_DIS (readl(KEYPAD_COL_GPIOPUD) & ~0xffff) +//#define KEYPAD_ROW_GPIOPUD_DIS (readl(KEYPAD_ROW_GPIOPUD) & ~(0xffff<<16)) +#define KEYPAD_ROW_GPIOPUD_DIS (readl(KEYPAD_ROW_GPIOPUD) & ~(0xffff)) //GPN0,GPN1,...GPN7 +//#define KEYPAD_COL_GPIOPUD_DIS (readl(KEYPAD_COL_GPIOPUD) & ~0xffff) +#define KEYPAD_COL_GPIOPUD_DIS (readl(KEYPAD_COL_GPIOPUD) & ~0xf000) //GPL6,GPL7 //HHTECH wk #define KEYIFCOL_CLEAR (readl(key_base+S3C_KEYIFCOL) & ~0xffff) #define KEYIFCON_CLEAR (readl(key_base+S3C_KEYIFCON) & ~0x1f) diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c index be83516..5484774 100644 --- a/drivers/input/mousedev.c +++ b/drivers/input/mousedev.c @@ -131,6 +131,7 @@ static void mousedev_touchpad_event(struct input_dev *dev, { int size, tmp; enum { FRACTION_DENOM = 128 }; +return; switch (code) { @@ -318,6 +319,7 @@ static void mousedev_notify_readers(struct mousedev *mousedev, static void mousedev_touchpad_touch(struct mousedev *mousedev, int value) { +return; if (!value) { if (mousedev->touch && time_before(jiffies, @@ -378,6 +380,7 @@ static void mousedev_event(struct input_handle *handle, break; case EV_SYN: +if (test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) return; if (code == SYN_REPORT) { if (mousedev->touch) { mousedev->pkt_count++; diff --git a/drivers/input/touchscreen/s3c-ts.c b/drivers/input/touchscreen/s3c-ts.c index e04228f..c16d441 100644 --- a/drivers/input/touchscreen/s3c-ts.c +++ b/drivers/input/touchscreen/s3c-ts.c @@ -82,10 +82,10 @@ /* Touchscreen default configuration */ struct s3c_ts_mach_info s3c_ts_default_cfg __initdata = { - .delay = 10000, + .delay = 50000, .presc = 49, - .oversampling_shift = 2, - .resol_bit = 10, + .oversampling_shift = 4, + .resol_bit = 12, }; /* @@ -98,6 +98,70 @@ static struct resource *ts_irq; static struct clk *ts_clock; static struct s3c_ts *ts; +static unsigned long data_for_ADCCON; +static unsigned long data_for_ADCTSC; +static int ts_pressed = 0; + +static void touch_timer_fire(unsigned long data); + +static struct timer_list touch_timer = + TIMER_INITIALIZER(touch_timer_fire, 0, 0); + +static void s3c_adc_save_SFR_on_ADC(void) { + + data_for_ADCCON = readl(ts_base+S3C2410_ADCCON); + data_for_ADCTSC = readl(ts_base+S3C2410_ADCTSC); +} + +static void s3c_adc_restore_SFR_on_ADC(void) { + + writel(data_for_ADCCON, ts_base+S3C2410_ADCCON); + writel(data_for_ADCTSC, ts_base+S3C2410_ADCTSC); +} + +unsigned int s3c_adc_value(unsigned int s3c_adc_port) +{ + unsigned int adc_return = 0; + unsigned long data0; + unsigned long data1; + + if (ts_pressed) + return 0; + + disable_irq(IRQ_ADC); + disable_irq(IRQ_TC); + + if (timer_pending(&touch_timer) || ts_pressed) + goto out; + + s3c_adc_save_SFR_on_ADC(); + + writel(0x58, ts_base+S3C2410_ADCTSC); + + writel(readl(ts_base+S3C2410_ADCCON)|S3C2410_ADCCON_SELMUX(s3c_adc_port), ts_base+S3C2410_ADCCON); + udelay(10); + + writel(readl(ts_base+S3C2410_ADCCON)|S3C2410_ADCCON_ENABLE_START, ts_base+S3C2410_ADCCON); + + do { + data0 = readl(ts_base+S3C2410_ADCCON); + } while(!(data0 & S3C2410_ADCCON_ECFLG)); + + data1 = readl(ts_base+S3C2410_ADCDAT0); + + s3c_adc_restore_SFR_on_ADC(); + + adc_return = data1 & S3C_ADCDAT0_XPDATA_MASK_12BIT; + __raw_writel(0x0, ts_base+S3C6400_ADCCLRWK); + __raw_writel(0x0, ts_base+S3C6400_ADCCLRINT); +out: + enable_irq(IRQ_TC); + enable_irq(IRQ_ADC); + + return adc_return; +} +EXPORT_SYMBOL(s3c_adc_value); + static void touch_timer_fire(unsigned long data) { unsigned long data0; @@ -120,8 +184,8 @@ static void touch_timer_fire(unsigned long data) } #endif - input_report_abs(ts->dev, ABS_X, ts->xp); - input_report_abs(ts->dev, ABS_Y, ts->yp); + input_report_abs(ts->dev, ABS_X, ts->xp / ts->count); + input_report_abs(ts->dev, ABS_Y, ts->yp / ts->count); input_report_key(ts->dev, BTN_TOUCH, 1); input_report_abs(ts->dev, ABS_PRESSURE, 1); @@ -144,12 +208,10 @@ static void touch_timer_fire(unsigned long data) input_sync(ts->dev); writel(WAIT4INT(0), ts_base+S3C2410_ADCTSC); + ts_pressed = 0; } } -static struct timer_list touch_timer = - TIMER_INITIALIZER(touch_timer_fire, 0, 0); - static irqreturn_t stylus_updown(int irqno, void *param) { unsigned long data0; @@ -184,6 +246,7 @@ static irqreturn_t stylus_action(int irqno, void *param) { unsigned long data0; unsigned long data1; + ts_pressed = 1; data0 = readl(ts_base+S3C2410_ADCDAT0); data1 = readl(ts_base+S3C2410_ADCDAT1); @@ -210,6 +273,7 @@ static irqreturn_t stylus_action(int irqno, void *param) writel(WAIT4INT(1), ts_base+S3C2410_ADCTSC); } +end: #if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410) __raw_writel(0x0, ts_base+S3C6400_ADCCLRWK); __raw_writel(0x0, ts_base+S3C6400_ADCCLRINT); @@ -268,6 +332,8 @@ static int __init s3c_ts_probe(struct platform_device *pdev) clk_enable(ts_clock); s3c_ts_cfg = s3c_ts_get_platdata(&pdev->dev); + + //writel(S3C2410_ADCCON_STDBM, ts_base+S3C2410_ADCCON); mdelay(10); if ((s3c_ts_cfg->presc&0xff) > 0) writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(s3c_ts_cfg->presc&0xFF),\ @@ -303,6 +369,7 @@ static int __init s3c_ts_probe(struct platform_device *pdev) ts->dev->evbit[0] = ts->dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); ts->dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); +ts->dev->keybit[BIT_WORD(BTN_TOOL_FINGER)] = BIT_MASK(BTN_TOOL_FINGER); #if defined(CONFIG_CPU_S3C2450) || defined(CONFIG_CPU_S3C6410) || defined(CONFIG_CPU_S3C2416) input_set_abs_params(ts->dev, ABS_X, 0, 0xFFF, 0, 0); @@ -332,14 +399,14 @@ static int __init s3c_ts_probe(struct platform_device *pdev) goto err_clk; } - ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts); + ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts->dev); if (ret != 0) { printk(KERN_ERR "s3c_ts.c: Could not allocate ts IRQ_TC !\n"); ret = -EIO; goto err_clk; } - ret = request_irq(ts_irq->end, stylus_action, IRQF_SAMPLE_RANDOM, "s3c_action", ts); + ret = request_irq(ts_irq->end, stylus_action, IRQF_SAMPLE_RANDOM, "s3c_action", ts->dev); if (ret != 0) { printk(KERN_ERR "s3c_ts.c: Could not allocate ts IRQ_ADC !\n"); ret = -EIO; @@ -397,6 +464,9 @@ static int s3c_ts_remove(struct platform_device *dev) input_unregister_device(ts->dev); iounmap(ts_base); + kfree(ts); + release_resource(ts_mem); + kfree(ts_mem); return 0; } diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b5e67c0..2a060ce 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -13,6 +13,11 @@ menuconfig MISC_DEVICES if MISC_DEVICES +config SMARTQ5_ENCRYPT + tristate "AT88SC5211C CryptoMemory Device driver for SmartQ5" + depends on MACH_SMDK6410 + ---help--- + config IBM_ASM tristate "Device driver for IBM RSA service processor" depends on X86 && PCI && INPUT && EXPERIMENTAL diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 87f2685..71f4d76 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -3,6 +3,7 @@ # obj- := misc.o # Dummy rule to force built-in.o to be made +obj-$(CONFIG_SMARTQ5_ENCRYPT) += smartq5_encrypt/ obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/ obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o diff --git a/drivers/misc/smartq5_encrypt/Makefile b/drivers/misc/smartq5_encrypt/Makefile new file mode 100644 index 0000000..3dd6858 --- /dev/null +++ b/drivers/misc/smartq5_encrypt/Makefile @@ -0,0 +1,3 @@ + +obj-$(CONFIG_SMARTQ5_ENCRYPT) := smartq5_encrypt.o + diff --git a/drivers/misc/smartq5_encrypt/lib_Crypto.h b/drivers/misc/smartq5_encrypt/lib_Crypto.h new file mode 100644 index 0000000..9e6b634 --- /dev/null +++ b/drivers/misc/smartq5_encrypt/lib_Crypto.h @@ -0,0 +1,1426 @@ +/** \file + * \brief Definitions and declarations for external reference into CrytoMemory + * and Companion CryptoMemory Library. + */ +/* +******************************************************************************* +* | +* File Name | lib_Crypto.h +*------------------------------------------------------------------------------ +* Project | CryptoMemory and Companion CryptoMemory +*------------------------------------------------------------------------------ +* Created | October 02, 2006 +*-----------------|----------------------------------------------------------- +* Description | This file contains all the definitions and function which +* | are used to reference to/from CryptoMemory and its +* | Companion library. +*-----------------|------------------------------------------------------------ +* | Copyright (c) 2006 Atmel Corp. +* | All Rights Reserved. +* | +****************************************************************************** +*/ +#ifndef LIB_CRYPTO_H +#define LIB_CRYPTO_H + +typedef unsigned char uchar; + +/** \mainpage Crypto-Memory Library Documentation + * + * \section intro_sec Introduction + * + * The CryptoMemory family is a high performance secure memory devices + * providing 1K to 256K bits of user memory with advanced security and + * cryptographic features built in. The memory is divided into 4, 8 or 16 + * user zones each of which may be individually set with different security + * access rights or used together to provide space for one or multiple data + * files. A configuration zone contains registers to define the security + * rights for each user zone and space for passwords and secret keys used by + * the security logic of CryptoMemory. + * + * The CryptoRF family integrates a 13.56 MHz RFinterface into a CryptoMemory, + * resulting in a Contactless Smart Card with advanced security and + * cryptographic features. This device is optimized as a contactless secure + * memory, for multi-application RF smart card, and secure identification for + * electronic data transfer, without the requirement of an interal + * microprocessor. The CryptoRF devices provide 1K to 64K bits of user memory. + * + * The CryptoCompanion Chip is designed as the mate to Atmel`s CryptoRF and + * CryptoMemory chips. + * + * This Library supports the synchronous interface most useful in embedded + * systems. While similar to the Two Wire Interface, it is not 100% compatable. + * + * \section embedded_app Embedded Applications + * + * Through dynamic, symmetric-mutual authentication, data encryption, and + * the use of encrypted checksums, CryptoMemory provides a secure place for + * storage of sensitive information within an embedded system. + * + * A 2-wire serial interface running at 1.0 MHz is used for fast and efficient + * communications with up to 15 devices that may be individually addressed. + * + * \section rf_app RF Applications + * + * For communications, the RF interface utilizes the ISO 14443-2 and -3 Type B + * bit timing and signal modulation schemes, and the ISO 14443-3 Slot-MARKER + * Anticollision Protocol. Data is exchanged half duplex at a 106-kbit/s rate, + * with a two-byte CRC_B providing error detection capability. The maximum + * communication range between the reader antenna and contactless card is + * approximately 10 cm when used with an RFID reader that transmits the maximum + * ISO 14443-2 RF power level. + * + * \section detail_sec Details + * + * To enable the security features of CryptoMemory, the device must first be + * personalized to set up registers and load in the appropriate passwords + * and keys. This is accomplished though programming the configuration zone + * of CryptoMemory using simple write and read commands. To gain access to + * the configuration zone, the secure code (write 7 password) must + * be successfully presented. After writing and verifying data in the + * configuration zone, the security fuses must be blown to lock this + * information in the device. + * + * \subsection subsection1 Configuration Memory + * + * The configuration memory consists of 2048 bits of EEPROM memory used for + * storing passwords, keys, codes and defining security levels to be used for + * each User Zone. Access rights to the configuration memory are defined in + * the control logic and may not be altered by the user. These access rights + * include the ability to program certain portions of the Configuration Zone + * and then lock the data written through use of the Security Fuses. The + * configuration memory for each CryptoMemory device is identical with the + * exception of the number of Access Registers and Password/Key Registers used. + * + * Unused registers become reserved space to ensure the other components of + * the configuration memory remain at the same address location regardless of + * the number of User Zones in a device. + * + * \subsection subsection2 Communication Security Modes + * + * Communication between the device and host operates in three basic modes. + * Standard mode is the default mode for the device after power-up. + * Authentication mode is activated by a successful authentication sequence. + * Encryption mode is activated by a successful encryption activation following + * a successful authentication. + * + * \subsection subsection3 Security Operations + * + * \subsubsection subsubsection1 Password Verification + * + * Passwords may be used to protect read and write access to the user zones. + * Any one of 8 password sets may be assigned to any user zone through the + * access registers. Separate 24 bit read and write passwords are provided. + * A read password will only allow data to be read from the protected zone, + * a write password allows both read and write access. When a password is + * successfully presented using the verify password command it remains active + * until another verify password command is issued or the device is reset. + * Only one password may be active at a time. When an incorrect password is + * presented the password attempts counter (PAC) will decrement. When the + * PAC reaches a value of 0x00 the associated password is permanently disabled + * and the protected user zone(s) cannot be accessed. + * + * \subsubsection subsubsection2 Authentication Protocol + * + * An authentication protocol may be used to protect access to the user zones. + * Any one of 4 key sets may be assigned to any user zone through the access + * registers. Each key set consists of a secret seed, cryptogram and session + * key. When the authentication communication mode is successfully entered + * with the verify authentication command the specific key set remains active + * until another verify authentication command is issued or the device is + * reset. An unsuccessful verify authentication command will deactivate the + * key set. Only one key set may be active at a time. When an incorrect + * authentication attempt is made the authentication attempts counter (AAC) + * will decrement. When the AAC reaches a value of 0x00 the associated key set + * is permanently disabled and the protected user zone(s) cannot be accessed. + * + * \subsubsection subsubsection3 Data Encryption + * + * The data exchanged between the CryptoMemory device and the host logic + * during read, write and verify password commands may be encrypted to ensure + * data confidentiality. This may be accomplished at the option of the host + * logic by executing the verify encryption command after a successful + * authentication using the same key set. Encryption may be required for any + * data exchange to a specific user zone by the proper setting of the access + * registers. In this case the successful completion of verify authentication + * and verify encryption commands is required before any data may be read + * from or written to the protected user zone. Data read from the + * configuration zone is never encrypted. + * + * When the encryption communication mode is successfully entered with the + * verify encryption command the specific key set remains active until another + * verify authentication command is issued or the device is reset. An + * unsuccessful verify encryption command will deactivate the key set and + * reset device security. Only one key set may be active at a time. When an + * incorrect encryption attempt is made the authentication attempts counter + * (AAC) will decrement. When the AAC reaches a value of 0x00 the associated + * key set is permanently disabled and the protected user zone(s) cannot be + * accessed. The process to activate encryption is very similar to that used + * to verify authentication, a calculated session key replaces the secret + * seed in the sequence. + * + * \subsubsection subsubsection4 Encrypted Checksum + * + * CryptoMemory implements a data validity check function in the form of an + * encrypted checksum. This checksum provides a bi-directional data integrity + * check and data origin authentication capability in the form of a Message + * Authentication Code (MAC): only the host/device that carried out a valid + * authentication is capable of computing a valid MAC. When writing data to + * the CryptoMemory device in authentication or encryption communication modes + * a valid checksum must be sent to the device immediately following the write + * command for data to actually be written. If the checksum sent is not + * correct, data will not be written to the device and security will be reset + * requiring a new verify authentication command to be issued to continue. + * When reading data the use of a checksum is optional. The read checksum + * command will reset device security so it is recommended to only be used + * after all data has been read from the device. + * + * \subsection subsection_default CryptoMemory Default Values + * + * \htmlonly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device ATRFab Code Lot History CodeWrite 7 Password (Secure Code)
AT88SC0104C3B B2 11 00 10 80 00 0110 10 Variable, lockedDD 42 97
AT88SC0204C3B B2 11 00 10 80 00 0220 20 Variable, lockedE5 47 47
AT88SC0404C3B B2 11 00 10 80 00 0440 40 Variable, locked60 57 34
AT88SC0808C3B B2 11 00 10 80 00 0880 60 Variable, locked22 E8 3F
AT88SC1616C3B B2 11 00 10 80 00 1616 80 Variable, locked20 0C E0
AT88SC3216C3B B3 11 00 00 00 00 3232 10 Variable, lockedCB 28 50
AT88SC6416C3B B3 11 00 00 00 00 6464 40 Variable, lockedF7 62 0B
AT88SC12816C3B B3 11 00 00 00 01 2828 60 Variable, locked22 EF 67
AT88SC25616C3B B3 11 00 00 00 02 5658 60 Variable, locked17 C3 3A
\endhtmlonly + * + * \subsection subsection_default1 CryptoRF Default Values + * + * \htmlonly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device Density CodeRBmax Code Lot History CodeWrite 7 Password (Secure Code)
AT88SC0104CRF0210 Variable, locked10 14 7C
AT88SC0204CRF1210 Variable, locked20 C2 8B
AT88SC0404CRF2210 Variable, locked30 1D D2
AT88SC0808CRF3310 Variable, locked40 7F AB
AT88SC1616CRF4410 Variable, locked50 44 72
AT88SC3216CRF5430 Variable, locked60 78 AF
AT88SC6416CRF6430 Variable, locked70 BA 2E
\endhtmlonly + * + * \section integrate-sec Integration Guide + * The supplied CryptoMemory Library supports the APIs list + * below. The following describes how to build a simple + * application using this library. + * + * \subsection subsection5 Files To Include. + * Below are files must add to system software.\n + * + * lib_Crypto.h: + * + * In your top level source file (probably the one that + * interface to lib_CMC.a or lib_CM.a), you will need to include lib_Crypto.h. + * This header file contained all external refernce APIs, + * defines and enumerations which are used by library or + * application. This file must be retained as is and no + * modification should be made.\n + * + * lib_CMC.a or lib_CM.a: + * + * In your IDE or makefile, you need to add lib_CMC.a or lib_CM.a for + * linker to link this library into your system software. This + * library archived from all object files generated by GNU + * compiler for AVR processor. If you use different + * processor other than AVR, you need request Atmel to + * generate a library to fit your environment and processor. + * + * lib_Support.c + * + * This file contains all APIs which library refers at compiler + * time and calls at run-time. Since this file is used as a + * reference file, you might change logic within these API but + * must retain all API names, input and output parameters as + * defined. Below are definitions, tables and routines which + * defined and implemented by Atmel. You can use them as + * implemented or modify to fit your system. Below are + * sections in the lib_Support.c file which needed to examine + * to fit your system software environment. + * + * \subsection subsection6 Definitions (lib_Support.c) + * Below are defintions must define to fit system enviroment. + * + * CM_NUM_DEVICE\n + * This definition is defined how many CM devices are in system + * \if CMC_DOXY + * CMC_NUM_DEVICE\n + * This definition is defined how many CMC devices are in + * system + * \endif + * \subsection subsection7 Tables (lib_Support.c) + * Below are tables must define to fit system enviroment.\n + * + * cm_device_addresses[]\n + * This table defined all CM device addresses in system with + * last entry must be "0". For CM, default address is 0x0B. + * Each device on the bus has a unique address programmed into + * its DCR register. CryptoMemory device always response to + * address "0x0B" no matter what address is programmed into the + * DCR. The CM device address is defined to program from bit + * 0-3 only. + * \if CMC_DOXY + * cmc_device_addresses[]\n + * This table defined all CMC device addresses in system with + * last entry must be "0". The CMC device address is defined to + * program from bit 1-7. + * \endif + * sDeviceTypeInfoTBL[]\n + * This table defined all possible CM device types which are + * supported by Atmel. + * + * \subsection subsection8 Routines (lib_Support.c) + * Below are routines that can be used as implemented or + * modified to meet system software needed. These routines + * will call by library at run-time.\n + * + * cm_FindDeviceIndex(uchar ucCmDevAddr)\n + * This routine returns a CM index from CM device address table + * that based from CM device address. + * + * getCMDevAddr(uchar ucIndex)\n + * This rotuine returns a CM device address from CM device + * address table that based from CM device index. + * + * getCMDevType(uchar ucIndex)\n + * This routine returns a CM device type from CM device type + * table that based from CM device index. + * + * cmc_FindDeviceIndex(uchar ucDevAddr)\n + * This routine returns a CMC index from CMC device address + * table that based from CMC device address. + * + * getCMCDevAddr(uchar ucIndex)\n + * This rotuine returns a CMC device address from CMC device + * address table that based from CMC device index. + * + * WaitForNotBusy(uchar ucDevAddr)\n + * This routine waits for the CMC "BUSY" bit to CLEAR. + * + * WaitForData(uchar ucDevAddr)\n + * This routine waits for the CMC "DATA AVAILABLE" bit to SET. + * + * WaitForStartupDone(uchar ucDevAddr)\n + * This routine waits for the CMC "STARTUPDONE" bit to SET. + * + * lib_memcpy(puchar pucDesMem, puchar pucSrcMem, uint uiCnt)\n + * This routine calls standard C library to copy number of + * bytes from source to destination memory. + * + * lib_memcmp(puchar pucMem1, puchar pucMem2, uint ucCnt)\n + * This routine calls standard C library to compare two + * strings. + * + * lib_malloc(uchar ucBytes)\n + * This rouitne calls standard C library to allocate a buffer + * from memeory pool. + * + * lib_free(puchar pucBuff)\n + * This rouitne calls standard C library to deallocate a buffer + * back to memeory pool. + * + * lib_rand(void)\n + * This rouitne calls standard C library to get a random + * byte.\n + * + * \subsection subsection9 APIs in library (lib_CMC.a or lib_CM.a) + * Below are APIs in library which avialable for other layers + * to call.\n + * + * getLibVersNum(void)\n + * Get library version + * + * cm_Init(void)\n + * Initializes Crypto devices. + * + * cm_WriteUserZone(uchar ucDevAddr, uint uiCryptoAddr, puchar pucBuffer, uchar ucCount)\n + * Writes data into the (previously set) CM user zone + * + * cm_ReadUserZone(uchar ucDevAddr, uint uiCryptoAddr, puchar pucBuffer, uchar ucCount)\n + * Reads data from the (previously set) CM user + * zone + * + * cm_WriteConfigZone(uchar ucDevAddr, uchar ucCryptoAddr, puchar pucBuffer, uchar ucCount, uchar ucAntiTearing)\n + * Write data into the CM Configuration Memory + * + * cm_WriteFuse(uchar ucDevAddr, uchar ucFuze)\n + * Writes Fuse Byte to the specified CM device address + * + * cm_SetUserZone(uchar ucDevAddr, uchar ucZoneNumber, uchar ucAntiTearing)\n + * Sets CM user zone + * + * cm_ReadConfigZone(uchar ucDevAddr, uchar ucCryptoAddr, puchar pucBuffer, uchar ucCount)\n + * Reads data from the CM Configuration Zone + * + * cm_ReadFuse(uchar ucDevAddr, puchar pucFuze)\n + * Reads Fuse Byte from CM device + * + * cm_ReadChecksum(uchar ucDevAddr, puchar pucChkSum)\n + * Reads Checksum + * + * cm_SendChecksum(uchar ucDevAddr, puchar pucChkSum)\n + * Sends checksum + * + * cm_VerifyCrypto(uchar ucDevAddr, uchar ucKeySet, puchar pucBuff, puchar pucBuff2, uchar ucEncrypt)\n + * Activates Authentication and (optionally) Encryption mode + * + * cm_ResetCrypto(uchar ucDevAddr)\n + * Reset authentication and encryption and resets the + * cryptoengine + * + * cm_VerifyPassword(uchar ucDevAddr, puchar pucPassword, uchar ucSet, uchar ucRW)\n + * Verifies Password. + * \if CMC_DOXY + * cmc_VerifyFlash(uchar ucCmcDevAddr, puchar pucExtDigest, puchar pucIntDigest)\n + * Verifies System Digest + * + * cmc_StartChallenge(uchar ucCmcDevAddr, puchar pucCmcChallenge, puchar pucCmcSeed, puchar pucSystemSeed)\n + * Verify Start Challenge + * + * cmc_GetRandom(uchar ucCmcDevAddr, puchar pucData)\n + * Gets a 20 byte random number from the Companion Chip + * + * cmc_IncrementCounter(uchar ucCmcDevAddr, uchar ucSelector)\n + * Increments the value of the specified counter by 1 + * + * cmc_ReadCounter(uchar ucCmcDevAddr, uchar ucSelector, puchar pucData)\n + * Reads 32 bits of the specified counter + * + * cmc_WriteMemory(uchar ucCmcDevAddr, uint uiAddress, uchar ucCount, puchar pucData)\n + * Writes the contents of the specified address + * + * cmc_WriteMemoryAuthorized(uchar ucCmcDevAddr, uint uiAddress, uchar ucData, puchar pucF0)\n + * Authorizes to writes a single byte in the first 16 bytes of + * the ReadOnly section at the specified address + * + * cmc_WriteMemoryEncrypted(uchar ucCmcDevAddr, uint uiAddress,puchar pucData, puchar pucNonce)\n + * Writes 16 byte page of EEPROM with encryption. + * + * cmc_ReadMemory(uchar ucCmcDevAddr, uint uiAddress, uchar ucCount, puchar pucData)\n + * Reads the contents of the specified address. + * + * cmc_ReadMemoryDigest(uchar ucCmcDevAddr, uint uiAddress, puchar pucData)\n + * Reads the specified 32 byte block and computes the SHA-1 + * digest. + * + * cmc_ReadManufID(uchar ucCmcDevAddr, puchar pucData)\n + * Reads the contents of the Manufacturing ID and Lock Byte + * + * cmc_Lock(uchar ucCmcDevAddr)\n + * Locks the current memory values into the Companion Chip. + * + * cmc_Status(uchar ucCmcDevAddr)\n + * Reads Status byte from CMC + * + * cmc_SHA1(puchar pucInData, uchar ucLen, puchar pucOutData)\n + * Calculates digest with SHA-1 algorithm\n + * \endif + */ + + +/* +**************************************************** +* Defintions for CryptoMemory and Companion +**************************************************** +*/ +// Basic Datatypes +typedef unsigned char *puchar; +typedef signed char schar; +typedef signed char *pschar; +typedef unsigned short *pushort; +typedef signed short sushort; +typedef signed short *pshort; +typedef unsigned int *puint; +typedef signed int sint; +typedef signed int *psint; + +#ifdef __AVR__ + #ifdef __GNUC__ + #include + #include + #define ROM_READ(addr) pgm_read_word(addr) +///< Read of program space (flash) in AVR is different from RAM read + #define ROM_READ_BYTE(addr) pgm_read_byte(addr) +///< Read of program space (flash) in AVR is different from RAM read + #define ROM_READ_DWORD(addr) pgm_read_dword(addr) +///< Read of program space (flash) in AVR is different from RAM read + #else + #define ROM_READ(addr) *addr +///< Read of program space (flash) in AVR is different from RAM read + #define ROM_READ_BYTE(addr) *addr +///< Read of program space (flash) in AVR is different from RAM read + #define ROM_READ_DWORD(addr) *addr +///< Read of program space (flash) in AVR is different from RAM read + #define PROGMEM ///< Needed in GCC to differentiate ROM from RAM + #endif +#else + #define ROM_READ(addr) *addr +///< Read of program space (flash) in AVR is different from RAM read + #define ROM_READ_BYTE(addr) *addr +///< Read of program space (flash) in AVR is different from RAM read + #define ROM_READ_DWORD(addr) *addr +///< Read of program space (flash) in AVR is different from RAM read + #define PROGMEM ///< Needed in GCC to differentiate ROM from RAM +#endif + + + +// Basic Definations (if not available elsewhere) +#ifndef FALSE +#define FALSE (0) +#define TRUE (!FALSE) +#endif +#ifndef NULL +#define NULL ((void *)0) +#endif + +/** \brief Status Return Codes */ +typedef enum { + SUCCESS = 0x00, ///< General OK, no error +#ifdef CMC + CMC_RST_LOCKED = 0x01, ///< CMC Reset Locked, disabled until power or reset cycle + CMC_BAD_CMD = 0x02, ///< CMC Command format or operand bad + CMC_TIME_DELAY = 0x03, ///< CMC Disabled for a time delay (Power or Security) + CMC_AUTH_FAIL = 0x04, ///< CMC Authentication required or failed + CMC_PERM_LOCKED = 0x07, ///< CMC Permanently locked + CMC_RESPONSE_FAILED = 0x0A, ///< CMC and System fail StartUp State + CMC_BUSY = 0x0B, ///< CMC Busy + CMC_DATA_NOT_AVAIL = 0x0C, ///< CMC Data Not Available + CMC_STARTUP_NOT_DONE =0x0D, ///< CMC and Ssytem fail Challengence State +#endif + FAILED = 0x14, ///< General failure + FAIL_CMDSTART = 0x15, ///< Command start failed + FAIL_CMDSEND = 0x16, ///< Failed to send a command + FAIL_WRDATA = 0x17, ///< Failed to write data + FAIL_RDDATA = 0x18, ///< Failed to read data + UNKNOWN_DEVICE = 0x19, ///< Invalid device number + WRITE_CMD_ERROR = 0x1A, ///< Write Command Error + READ_CMD_ERROR = 0x1B, ///< Read Command Error + PASSWORD_NOT_ACCEPTED = 0x1C, ///< Write password not accepted + INVALID_USER_ZONE = 0x1D, ///< Wrong user zone + INVALID_NUM_BYTES_2WRITE = 0x1E,///< Number of bytes to write too large + INVALID_NUM_BYTES_2READ = 0x1F, ///< Number of bytes to read too large + ERROR_MAX = 255 ///< Maximum error + +} RETURN_CODE; + + +/** \brief Below are defined of Crypto Memory device type */ + +typedef enum { + UNKNOWN_DEVICE_TYPE, + AT88SC0104C, /// < 1 KBits User Zone + AT88SC0204C, /// < 2 KBits User Zone + AT88SC0404C, /// < 4 KBits User Zone + AT88SC0808C, /// < 8 KBits User Zone + AT88SC1616C, /// < 16 KBits User Zone + AT88SC3216C, /// < 32 KBits User Zone + AT88SC6416C, /// < 64 KBits User Zone + AT88SC12816C, /// < 128 KBits User Zone + AT88SC25616C, /// < 256 KBits User Zone + AT88SCRF, /// < CM for RF + MAX_CM_DEVICE_TYPE /// < Maximum device type +} CM_DEVICE_TYPE; + +/** \brief Maximum of CM device addresses +*/ +#define CM_MAX_DEV_ADDR 0x0F + +#ifdef CMC +/** \if CMC_DOXY + * \brief Maximum of CMC device addresses + * \endif +*/ +#define CMC_MAX_DEV_ADDR 0xFE +#endif + +/* +**************************************************** +* CryptoMemory Function Prototypes +**************************************************** +*/ +/** \brief Initializes the CrytpoMemory + * + * Resets the crypto engines and parameters associated with them + * for each device in the cm_device_addresses table. + * This function should be called immediately after calling the low level + * function ll_PowerOn(). If the return value is not SUCCESS, use of + * the CryptoMemory device(s) may not continue. + * If system runs with CryptoMemory Companion chip then this cm_Init will + * initialize them in this init state. + * + * \return Returns SUCCESS if device_address is in cm_device_addresses, + * UNKNOWN_DEVICE otherwise. + * + * \remarks Example: if (cm_Init() != SUCCESS) return 0; \n + * Initializes all devices in the cm_device_addresses table. + */ +extern RETURN_CODE cm_Init (void); + +/** \brief Writes data into the (previously set) user zone + * + * This function is the main memory write function, and will automatically + * include encryption or anti-tearing (if enabled). + * + * \param ucDevAddr - Device address (a nibble) corresponding to the default + * value (0xb) or the device configuration register lower nibble. + * \param uiCryptoAddr - 2 byte address within the zone to write + * \param pucBuffer - pointer to the buffer with the write data + * \param ucCount - how many bytes to write + * + * \return Returns the results of ll_SendCommand() and ll_SendData(). + * + * \remarks Example: ucReturn = cm_WriteUserZone(DEFAULT_ADDRESS, 0, ucData, + * 16);\n + * Writes 16 bytes from ucData at address 0 to device at DEFAULT_ADDRESS.\n + * Return SUCCESS or the result of ll_SendCommand() or + * ll_SendData(). + */ +extern RETURN_CODE cm_WriteUserZone(uchar ucDevAddr, uint uiCryptoAddr, \ + puchar pucBuffer, uchar ucCount); + +/** \brief Reads data from the (previously set) user zone + * + * This function is the main memory read function, and will automatically + * include encryption (if enabled). + * + * \param ucDevAddr - Device address (a nibble) corresponding to the default + * value (0xb) or the device configuration register lower nibble. + * \param uiCryptoAddr - 2 byte address within the zone to read + * \param pucBuffer - pointer to a buffer to receive data + * \param ucCount - the number of bytes to read + * + * \return Returns the results of ll_SendCommand() and ll_ReceiveData(). + * + * \remarks Example: ucReturn = cm_ReadUserZone(DEFAULT_ADDRESS, 0, ucData, 16);\n + * Reads 16 bytes into ucData from address 0 in device at DEFAULT_ADDRESS.\n + * Return SUCCESS or the result of ll_SendCommand() or + * ll_ReceiveData(). + */ +extern RETURN_CODE cm_ReadUserZone(uchar ucDevAddr, uint uiCryptoAddr, \ + puchar pucBuffer, uchar ucCount); + +/** \brief Write data into the Configuration Memory + * + * The input parameters determine which device address to write, which + * starting address in the configuration memory to write to, how many + * bytes to write, where the data to write is, and whether to use + * the anti-tearing option. + * + * \param ucDevAddr- Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \param ucCryptoAddr - Configuration Memory address - see the Configuration + * Memory map for the specific CryptoMemory Device to determine the address + * of the appropriate parameter. + * \param pucBuffer - Pointer to buffer containing write data. + * \param ucCount - the number of bytes to write + * \param ucAntiTearing - antitearing flag. If TRUE, anti-tearing is + * enabled. Anti-tearing should not be used unless required, as it + * causes more write cycles than necessary. + * + * \return Return value of ll_SendCommand() or ll_SendData(). + * + * \remarks + * Example: \n + * ucReturn = cm_WriteConfigZone(DEFAULT_ADDRESS, 0x0A, ucData, 2, FALSE);\n + * Writes 2 bytes from ucData at CryptoMemory address 0x0A to the CryptoMemory + * device at DEFAULT_ADDRESS (0xb). + * Anti-tearing is not enabled. \n + * Return SUCCESS or the result of ll_SendCommand() or + * ll_SendData(). + * + */ +extern RETURN_CODE cm_WriteConfigZone(uchar ucDevAddr, uchar ucCryptoAddr, \ + puchar pucBuffer, uchar ucCount, \ + uchar ucAntiTearing); + +/** \brief Writes Fuse Byte to the specified device address + * + * Fuse values are (in bit order from 3 to 0): + * - SEC (blown by Atmel before shipment, locks Lot History Code) + * - FAB (locks Answer to Reset and Fab Code) + * - CMA (locks Card Manufacturer Code) + * - PER (locks the remaining portions of the System Zone) + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \param ucFuze - A byte holding the fuse data + * \return SUCCESS or ll_SendCommand() result. + * + * \remarks Example: ucReturn = WriteFuse(DEFAULT_ADDRESS, ucData);\n + * Writes fuse data in ucData to device at DEFAULT_ADDRESS. + * Return SUCCESS or the result of ll_SendCommand() + */ +extern RETURN_CODE cm_WriteFuse(uchar ucDevAddr, uchar ucFuze); + +/** \brief System Write to set the user zone for following commands + * + * When reading or writing the main EEPROM memory of the CryptoMemory device, + * the supplied address refers to an offset within a user zone (refer to + * the User Zone map for the specific CryptoMemory Device to determine the + * number of zones and zone size.) This function sets the user zone. + * + * \param ucDevAddr - Device address (a nibble) corresponding to the default + * value (0xb) or the device configuration register lower nibble. + * \param ucZoneNumber - user zone number to set + * \param ucAntiTearing - antitearing flag. If TRUE, anti-tearing is enabled. + * Anti-tearing should not be used unless required, as it causes more write + * cycles than necessary. + * + * \return Result of ll_SendCommand(). + * + * \remarks Example: ucReturn = cm_SetUserZone(DEFAULT_ADDRESS, 3, FALSE);\n + * Sets the device at the default address (0xb) to user zone 3; anti-tearing + * is not enabled. + * Return SUCCESS or the result of ll_SendCommand() + */ +extern RETURN_CODE cm_SetUserZone(uchar ucDevAddr, uchar ucZoneNumber, \ + uchar ucAntiTearing); + +/** \brief Read data from the Configuration Zone + * + * The input parameters determine which device address to read, which + * starting address in the configuration zone to read from, how many + * bytes to read, and where to place the returned data. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \param ucCryptoAddr - Configuration Zone address - see the Configuration + * Zone map for the specific CryptoMemory Device to determine the address + * of the appropriate parameter. + * \param pucBuffer - Pointer to buffer to contain returned data, + * which must be large enough to hold ucCount bytes. + * \param ucCount - the number of bytes to read + * \return SUCCESS or the result of ll_SendCommand() or ll_ReceiveData() + * + * \remarks + * Example: ucReturn = cm_ReadConfigZone(DEFAULT_ADDRESS, 0x0A, ucData, 2);\n + * Reads 2 bytes at address 0x0A from the DEFAULT_ADDRESS (0xb) into ucData.\n + * Return SUCCESS or the result of ll_SendCommand() or + * ll_ReceiveData(). + */ +extern RETURN_CODE cm_ReadConfigZone(uchar ucDevAddr, uchar ucCryptoAddr, \ + puchar pucBuffer, uchar ucCount); + +/** \brief Read Fuse Byte from the device specified by ucDevAddr + * + * Fuse values are (in bit order from 3 to 0): + * - SEC (blown by Atmel before shipment, locks Lot History Code) + * - FAB (locks Answer to Reset and Fab Code) + * - CMA (locks Card Manufacturer Code) + * - PER (locks the remaining portions of the Configuration Zone) + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \param pucFuze - A pointer to a byte to hold the fuse data + * \return SUCCESS or ll_SendCommand() or ll_ReceiveData() result. + * + * \remarks Example: ucReturn = ReadFuse(DEFAULT_ADDRESS, ucData);\n + * Reads fuse data from device at DEFAULT_ADDRESS into + * ucData. + * Return SUCCESS or the result of ll_SendCommand() or + * ll_ReceiveData(). + */ +extern RETURN_CODE cm_ReadFuse(uchar ucDevAddr, puchar pucFuze); + +/** \brief Read Checksum + * + * If the UCR bit in the Device Configuration Register is 1, the cryptographic + * engine will be reset. If UCR = 0, it will not be reset.\n + * This function provides a method to check on the synchronicity of the host + * and device cryptoengines without affecting the attempts counters. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \param pucChkSum - pointer to 2 bytes to store calculated CheckSum + * \return SUCCESS or the results of ll_SendCommand() or ll_ReceiveData(). + * + * \remarks Example: ucReturn = cm_ReadChecksum(DEFAULT_ADDRESS, ucData);\n + * Reads the checksum value into ucData for the device at + * DEFAULT_ADDRESS. + * Return SUCCESS or the result of ll_SendCommand() or or + * ll_ReceiveData(). + */ +extern RETURN_CODE cm_ReadChecksum(uchar ucDevAddr, puchar pucChkSum); + +/** \brief Send checksum. + * + * If device is in authentication mode, any user zone write must be followed + * with a cm_SendChecksum command with the proper checksum. Otherwise the + * device will not complete the write. An invalid checksum will also clear + * authentication. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \param pucChkSum - pointer to 2 byte array holding checksum. + * Always pass NULL so that function will calculate the appropriate checksum. + * + * \return Results of ll_SendCommand() or ll_SendData() + * + * \remarks Example: ucReturn = cm_SendChecksum(DEFAULT_ADDRESS, NULL);\n + * Sends the correct checksum to the device at the DEFAULT_ADDRESS. + * Return SUCCESS or the result of ll_SendCommand() or or + * ll_SendData(). + */ +extern RETURN_CODE cm_SendChecksum(uchar ucDevAddr, puchar pucChkSum); + +/** \brief This routine activates Authentication and (optionally) + * Encryption modes. + * + * When called the function: + * - reads the current cryptogram (Ci) of the specified key set, + * \if CMC_DOXY + * - computes the next cryptogram (CA) based on the secret + * KID pucKey (KID) and the random number (Q). + * \else + * - computes the next cryptogram (CA) based on the secret + * seed pucKey (G) and the random number (Q). + * \endif + * - sends the (CHA) (crypto challenge) and the random number to the + * CryptoMemory device, + * - Reads the chip's newly calculated cryptogram (CiA) + * and compares it with CA. + * + * If CA matches CiA then + * authentication is successful AND: + * - both host (function) and device save the new Cryptogram and + * calculate new Session Keys. + * + * In addition, if ucEncrypt is TRUE the function, using the new Session Key + * and a new random number + * - computes a new challenge (CHE), a new cryptogram + * (CE) and a new Session Key (SE), + * - sends the challenge (CHE) and random number (Q) to the + * CryptoMemory device, + * - reads the device's newly calculated cryptogram (CiE) + * and compares it with CE. + * + * If the two match then encryption is activated. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \if CMC_DOXY + * \param ucKeySet - index number of the current key set to be used for + * authentication / encryption. Upper nible is contained F index and lower + * is contained G index. + * \param pucBuff1 - pointer to 16 bytes Secret KID. + * \param pucBuff2 - pointer to 8 bytes uniqui serial or + * identification number of CM. + * \else + * \param ucKeySet - index number of the current key set to be used for + * authentication / encryption. Upper nible is ignored and + * lower is contained G index. + * \param pucBuff1 - pointer to the Secret Seed (G) associated + * with this key set. + * \param pucBuff2 - random number. If NULL, the function will + * calculate new random numbers for both actions.) + * \endif + * \param ucEncrypt - if TRUE command the device to enter encryption mode also + * (with the same key set). + * + * \return SUCCESS or the results of ll_SendCommand(), ll_ReceiveData() + * or ll_SendData() result. + * + * \remarks Example: \n + * ucReturn = cm_VerifyCrypto(DEFAULT_ADDRESS, 1, ucData, NULL, TRUE);\n + * Enter authentication mode with key set 1, secret seed in ucData, + * calculate a random number for the user, and enter the encryption mode.\n + * Return SUCCESS or the result of ll_SendCommand() or + * ll_SendData() or ll_ReceiveData(). + */ +extern RETURN_CODE cm_VerifyCrypto(uchar ucDevAddr, uchar ucKeySet, \ + puchar pucBuff1, puchar pucBuff2, \ + uchar ucEncrypt); + +/** \brief This routine reset active password + * + * When called the function: + * - sends the Verify Password Command with the second byte set to 0xFF. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * + * \return SUCCESS or the results of ll_SendCommand() or ll_SendData(). + * + * \remarks Example: \n + * ucReturn = cm_ResetPassword(DEFAULT_ADDRESS);\n + * Reset active password for device at address + * DEFAULT_ADDRESS. + * Return SUCCESS or the result of ll_SendCommand() or or + * ll_SendData(). + */ +extern RETURN_CODE cm_ResetPassword(uchar ucDevAddr); + +/** \brief This routine reset authentication and encryption + * and resets the cryptoengine. + * + * When called the function: + * - sends the Verify Crypto Command with no arguments and + * - Resets the CryptoEngine in firmware. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * + * \return SUCCESS or the results of ll_SendCommand() or ll_SendData(). + * + * \remarks Example: \n + * ucReturn = cm_ResetCrypto(DEFAULT_ADDRESS);\n + * Leave authentication and encryption mode for device at address + * DEFAULT_ADDRESS. + * Return SUCCESS or the result of ll_SendCommand() or or + * ll_SendData(). + */ +extern RETURN_CODE cm_ResetCrypto(uchar ucDevAddr); + +/** \brief Verifies Password. + * + * Using the verify password command, send either the "write" or "read" + * password to the appropriate address. Then read the password location + * in the config zone and see if the password attempts counter = 0xff + * (passed).\n + * If Authentication mode is active, then this function encrypts passwords + * automatically. + * + * \param ucDevAddr - Device Address (a nibble) corresponding to the + * default value (0xb) or the device configuration register lower + * nibble. + * \if CMC_DOXY + * \param pucPassword - pointer to password to verify and only valid if authentication + * is not required. + * \param ucSet - index number of the password set + * system with Companion devie: bit 0-3: CM password index + * bit 4-7: companion pwd index. + * \else + * \param pucPassword - pointer to password to verify. + * \param ucSet - index number of the password set + * system without Companion device, bit 0-3: CM password index + * bit 4-7 are ignore. + * system with Companion devie: bit 0-3: CM password index + * bit 4-7: companion pwd index. + * \endif + * \param ucRW - whether it's the read or write password. Read = 1, Write = 0. + * + * \return SUCCESS or the results of ll_SendCommand() or ll_SendData() or + * ll_ReceiveData() + * + * \remarks + * Example: ucReturn = cm_VerifyPassword(DEFAULT_ADDRESS, ucData, 7, 0);\n + * Verify the write password number 7 at the DEFAULT_ADDRESS.\n + * Return SUCCESS or the result of ll_SendCommand() or + * ll_SendData() or ll_ReceiveData(). + */ +extern RETURN_CODE cm_VerifyPassword(uchar ucDevAddr, puchar pucPassword, \ + uchar ucSet, uchar ucRW); + +#ifdef CMC +/* +**************************************************** +* CryptoMemory Companion (CMC) Function Prototypes +**************************************************** +*/ + +/**\if CMC_DOXY + * \brief Verifies System Digest + * + * If Mode bit 0 = 0, simply verifies that *pucDigest matches FlashDigest. + * If Mode bit 0 = 1, verifies that SHA-1(Digest, FlashDigest) = *pucSignature. + * + * \param ucCmcDevAddr - CMC device address + * \param pucExtDigest - pointer to 20 bytes of external Digest + * \param pucIntDigest - pointer to 20 bytes of internal EEPROM Flash Digest. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_VerifyFlash(uchar ucCmcDevAddr, puchar pucExtDigest, \ + puchar pucIntDigest); + +/** \if CMC_DOXY + * \brief Start Challenge + * + * This API will use to verify the mutually authenticate between system processor and + * compnaion device. If either CMC or System secret seeds is not the same as in EEPROM, + * then CMC will return FAIL. If both secret seeds are the same as in EEPROM, CMC will + * return SUCCESS. + * + * \param ucCmcDevAddr - CMC device address + * \param pucCmcChallenge - pointer to 20 bytes of CMC Challenge + * \param pucCmcSeed - pointer to 16 bytes of CMC secret seed. This CMC secret seed bytes + * must be the same as CMCSeed in EEPROM + * \param pucSystemSeed - pointer to 16 bytes of System secret seed. This System secret + * seed bytes must be the same as SystemSeed in EEPROM. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_StartChallenge(uchar ucCmcDevAddr, puchar pucCmcChallenge, \ + puchar pucCmcSeed, puchar pucSystemSeed); + +/** \if CMC_DOXY + * \brief Gets a 20 byte random number from the Companion Chip + * + * \param ucCmcDevAddr - CMC device address + * \param pucData - pointer to returned 20 byte random number. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_GetRandom(uchar ucCmcDevAddr, puchar pucData) ; + +/** \if CMC_DOXY + * \brief Increments the value of the specified counter by 1 + * + * \param ucCmcDevAddr - CMC device address + * \param ucSelector - Counter to increment (0 - 3). + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_IncrementCounter(uchar ucCmcDevAddr, uchar ucSelector) ; + +/** \if CMC_DOXY + * \brief Returns the least 32 bits of the specified counter. + * + * \param ucCmcDevAddr - CMC device address + * \param ucSelector - Counter to read (0 - 3). + * \param pucData - pointer to least significant 4 bytes of counter data. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_ReadCounter(uchar ucCmcDevAddr, uchar ucSelector, \ + puchar pucData) ; + +/** \if CMC_DOXY + * \brief Writes the contents of the specified address. + * + * Writes up to the end of the read/write memory space. + * After locking, only the read/write space can be written. + * Up to 16 bytes may be written with a single operation + * + * \param ucCmcDevAddr - CMC device address + * \param uiAddress - Address of first byte to write. 7 most sig. bits ignored. + * \param ucCount - Write ucCount bytes to EEPROM (data + * follows). + * \param pucData - pointer to clear text data to write. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_WriteMemory(uchar ucCmcDevAddr, uint uiAddress, \ + uchar ucCount, puchar pucData) ; + +/** \if CMC_DOXY + * \brief Authorize to writes a single byte in the first 16 + * bytes of the ReadOnly section at the specified address. + * + * Address must point to one of the first 16 bytes within the ReadOnly + * section of memory. This command only works if Mode:Bit2 = 1. + * Only one byte may be written with a each operation + * + * \param ucCmcDevAddr - CMC ddevice address + * \param uiAddress - Address of byte to write. 7 most sig. bits ignored. + * \param ucData - clear text data to write. + * \param pucF0 - pointer to buffer that contained 8 bytes + * of F0 + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_WriteMemoryAuthorized(uchar ucCmcDevAddr, uint uiAddress, \ + uchar ucData, puchar pucF0) ; + +/** \if CMC_DOXY + * \brief Writes 16 byte page of EEPROM with encryption. + * + * Writes 16 byte pages up to the end of the read/write memory space. + * Uses the SHA-1 of (Address || EncKey || Nonce). + * Smaller blocks than 16 bytes cannot be written with this command. + * Cannot be executed after Companion Chip is locked. + * + * \param ucCmcDevAddr - CMC ddevice address + * \param uiAddress - Address of the 16 byte page to write. 7 most significant + * and 4 least significant bits ignored. + * \param pucData - pointer to 16 bytes of clear data to write. + * \param pucNonce - Random value encryption seed. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_WriteMemoryEncrypted(uchar ucCmcDevAddr, uint uiAddress, \ + puchar pucData, puchar pucNonce) ; + +/** \if CMC_DOXY + * \brief Read the contents of the specified address. + * + * Reads up to the end of the read/write memory space. + * After locking, only the read only and the read/write space can be read. + * Up to 16 bytes may be read with a single operation + * + * \param ucCmcDevAddr - CMC ddevice address + * \param uiAddress - Address of first byte to read. 7 most sig. bits ignored. + * \param ucCount - Read ucCount+1 bytes from EEPROM. + * \param pucData - pointer to clear text data read from Companion Chip. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_ReadMemory(uchar ucCmcDevAddr, uint uiAddress, \ + uchar ucCount, puchar pucData) ; + +/** \if CMC_DOXY + * \brief Read the specified 32 byte block and computes the + * SHA-1 digest. + * + * Provides a mechanism of verifying that the personalization of the chip + * completed correctly before locked. + * Note that verifying the digest of 0 requires knowing EncKey. + * Cannot be executed after Companion Chip is locked. + * + * \param ucCmcDevAddr - CMC ddevice address + * \param uiAddress - Address of the 32 byte page to read. 7 most significant + * and 5 least significant bits ignored. + * \param pucData - pointer to 20 byte digest + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_ReadMemoryDigest(uchar ucCmcDevAddr, uint uiAddress, \ + puchar pucData) ; + +/** \if CMC_DOXY + * \brief Read the contents of the Manufacturing ID and Lock + * Byte. + * + * \param ucCmcDevAddr - CMC ddevice address + * + * \param pucData - pointer to 16 byte Manufacturing ID and Lock Byte. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_ReadManufID(uchar ucCmcDevAddr, puchar pucData) ; + +/** \if CMC_DOXY + * \brief Lock the current memory values. + * + * \param ucCmcDevAddr - CMC ddevice address. + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern RETURN_CODE cmc_Lock(uchar ucCmcDevAddr) ; + +/** \if CMC_DOXY + * \brief Returns the status byte from a CMC device. + * + * \param ucCmcDevAddr - CMC ddevice address + * + * \return uchar - status from a CMC device + * \endif +*/ +extern uchar cmc_Status(uchar ucCmcDevAddr) ; + +#endif /* CMC */ + +/** + * \brief Send command bytes to a device. + * + * This routine is external reference by library + * + * \param pucInsBuf - pointer to buffer of command bytes to + * send. + * \param ucLen - number of command bytes to send. + * + * \return SUCCESS or the result of cmc_Status() + */ +extern RETURN_CODE ll_SendCommand(puchar pucInsBuf, uchar ucLen); + +/** \if CMC_DOXY + * \brief Receive data bytes from a CMC device. + * + * This routine is external reference by library. + * + * \param pucRecBuf - pointer to buffer of receving data bytes + * \param ucLen - number of data bytes to receive + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +#ifdef CMC +extern RETURN_CODE ll_CmcReceiveData(puchar pucRecBuf, uchar ucLen); +#endif + +/** \brief Receive data bytes from a CM device. + * + * This routine is external reference by library. + * + * \param pucRecBuf - pointer to buffer of receving data bytes + * \param ucLen - number of data bytes to receive + * + * \return SUCCESS or the result of cmc_Status() + */ +extern RETURN_CODE ll_ReceiveData(puchar pucRecBuf, uchar ucLen); + +/** \brief Send data bytes to a device. + * + * This routine is external reference by library. + * + * \param pucSendBuf - pointer to buffer of data bytes to send + * \param ucLen - number of data bytes to send + * + * \return SUCCESS or the result of cmc_Status() + */ +extern RETURN_CODE ll_SendData(puchar pucSendBuf, uchar ucLen); + +/** + * \brief Wait for number of clock ticks + * This routine is external reference by library. + * + * \param ucLoop - number of (Start + 15 clocks + Stop) to loop + */ +extern void ll_WaitClock(uchar ucLoop); + +/** + * \brief Copy number of bytes from source to destination + * locations. + * + * This routine is external reference by library. + * + * \param pucDestMem - destination buffer + * \param pucSrcMem - source buffer + * \param uiCnt - number of bytes to copy + */ +extern void lib_memcpy(puchar pucDestMem, puchar pucSrcMem, uint uiCnt); + +/** + * \brief Compare two strings. + * + * This routine is external reference by library. + * + * \param pucMem1 - string 1 buffer + * \param pucMem2 - string 2 buffer + * \param uiCnt - number of bytes to compare + */ +extern uchar lib_memcmp(puchar pucMem1, puchar pucMem2, uint uiCnt); + +/** + * \brief Allocate memory from memory pool. + * + * This routine is external reference by library. + * + * \param ucBytes - number of bytes to allocate + * + * \return puchar - pointer to allocated buffer + * + */ +extern puchar lib_malloc(uchar ucBytes); + +/** \brief Deallocate memory back to memory pool. + * + * This routine is external reference by library. + * + * \param pucBuff - pointer to deallocated buffer + */ +extern void lib_free(puchar pucBuff); + +/** \brief Get random byte. + * + * This routine is external reference by library. + * + * \return uchar - random byte + */ +extern uchar lib_rand(void); + +/** \brief Get CM device address based from device index. + * + * This routine is external reference by library. + * + * \param ucIndex - CM device index + * + * \return uchar - CM device address + */ +extern uchar getCMDevAddr(uchar ucIndex); + +/** \brief Get CM device index based from device address. + * + * This routine is external reference by library. + * + * \param ucCmDevAddr - CM device address + * + * \return uchar - CM device index + */ +extern uchar cm_FindDeviceIndex(uchar ucCmDevAddr); + +/** \brief Get CM device type based from device index. + * + * This routine is external reference by library. + * + * \param ucIndex - CM device index + * + * \return CM_DEVICE_TYPE - CM device type + */ +extern CM_DEVICE_TYPE getCMDevType(uchar ucIndex); + +/** \brief Get number CM device. + * + * This routine is external reference by library. + * + * \return uchar - number of CM devices + */ +extern uchar getNumCmDev(void); + + +/** \brief Get library version number. + * + * \return puchar - pointer to string of characters + */ +extern puchar getLibVersNum(void); + +#ifdef CMC + +/** \if CMC_DOXY + * \brief Wait for BUSY bit from CMC status to CLEAR. + * + * This routine is external reference by library. + * + * \param ucCmcDevAddr - CMC device address + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern uchar WaitForNotBusy(uchar ucCmcDevAddr); + +/** \if CMC_DOXY + * \brief Wait for DATA bit from CMC status to SET. + * + * This routine is external reference by library. + * + * \param ucCmcDevAddr - CMC device address + * + * \return SUCCESS or the result of cmc_Status() + * \endif + */ +extern uchar WaitForData(uchar ucCmcDevAddr); + +/** \brief Wait for STARTUPDONE bit from CMC status to SET. + * + * This routine is external reference by library. + * + * \param ucCmcDevAddr - CMC device address + * + * \return SUCCESS or the result of cmc_Status() + */ +extern uchar WaitForStartupDone(uchar ucCmcDevAddr); + +/** \if CMC_DOXY + * \brief Get CMC device index based from device address. + * + * This routine is external reference by library. + * + * \param ucCmcDevAddr - CMC device address + * + * \return uchar - CMC device index + * \endif + */ +extern uchar cmc_FindDeviceIndex(uchar ucCmcDevAddr); + +/** \if CMC_DOXY + * \brief Get CMC device address based from device index. + * + * This routine is external reference by library. + * + * \param ucIndex - CMC device index + * + * \return uchar - CMC Cdevice address + * \endif + */ +extern uchar getCMCDevAddr(uchar ucIndex); + + +/** \if CMC_DOXY + * \brief Reset CMC device. + * + * This routine is external reference by library. + * \endif + */ +extern void cmc_Reset(void); + +/** \if CMC_DOXY + * \brief SHA-1 processing + * + * \param pucChallenge - First input to SHA-1 algorithm + * \param ucLen - Length of pucChallenge + * \param pucResponse - Output from SHA-1 algorithm + * \endif + */ +extern void cmc_SHA1(puchar pucInData, uchar ucLen, puchar pucOutData); +#endif + +#endif + diff --git a/drivers/misc/smartq5_encrypt/smartq5_encrypt.c b/drivers/misc/smartq5_encrypt/smartq5_encrypt.c new file mode 100644 index 0000000..3626fa7 --- /dev/null +++ b/drivers/misc/smartq5_encrypt/smartq5_encrypt.c @@ -0,0 +1,117 @@ +/**************************************************************** + * $ID: smartq5_encrypt.c Sun, 22 Mar 2009 15:48:25 +0800 root $ * + * * + * Description: * + * * + * Maintainer: (Meihui Fan) * + * * + * Copyright (C) 2009 HHTech * + * www.hhcn.com, www.hhcn.org * + * All rights reserved. * + * * + * This file is free software; * + * you are free to modify and/or redistribute it * + * under the terms of the GNU General Public Licence (GPL). * + * * + * Last modified: Wed, 25 Mar 2009 15:38:46 +0800 by root # + ****************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + SCL_HIGH, + SCL_LOW, + SDA_HIGH, + SDA_LOW, + SDA_DATA +} I2C_CMD; + +#define GPIO_SCL S3C_GPN0 +#define GPIO_SDA S3C_GPN1 + +/* + * The smartq5_encrypt device is one of the misc char devices. + * This is its minor number. + */ +#define SMARTQ5_ENCRYPT_DEV_MINOR 234 + +static int smartq5_encrypt_open(struct inode * inode, struct file * filp) +{ + return 0; +} + +static int smartq5_encrypt_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) +{ + int ret = 0; + switch (cmd) { + case SCL_HIGH: + gpio_direction_output(GPIO_SCL, 1); + break; + case SCL_LOW: + gpio_direction_output(GPIO_SCL, 0); + break; + case SDA_HIGH: + gpio_direction_input(GPIO_SDA); // important! + break; + case SDA_LOW: + gpio_direction_output(GPIO_SDA, 0); + break; + case SDA_DATA: + gpio_direction_input(GPIO_SDA); + ret = gpio_get_value(GPIO_SDA); + break; + } + return ret; +} + +static struct file_operations smartq5_encrypt_fops = { + .owner = THIS_MODULE, + .ioctl = smartq5_encrypt_ioctl, + .open = smartq5_encrypt_open, +}; + +static struct miscdevice smartq5_encrypt_device = { + .minor = SMARTQ5_ENCRYPT_DEV_MINOR, + .name = "smartq5_encrypt", + .fops = &smartq5_encrypt_fops +}; + +static int __init smartq5_encrypt_init(void) +{ + return misc_register(&smartq5_encrypt_device); +} + +static void __exit smartq5_encrypt_exit(void) +{ + misc_deregister(&smartq5_encrypt_device); +} + +module_init(smartq5_encrypt_init); +module_exit(smartq5_encrypt_exit); + +MODULE_AUTHOR("Guoqiang Wang"); +MODULE_DESCRIPTION("Encypt module for SmartQ5"); +MODULE_LICENSE("GPL"); + + +/**************** End Of File: smartq5_encrypt.c ****************/ +// vim:sts=4:ts=8: diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index aeb32a9..2816b81 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -424,7 +424,16 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) struct mmc_blk_data *md; int devidx, ret; +#ifdef CONFIG_MACH_SMDK6410 /* XXX: Make sure inand devidx = 0 */ + if (card->host->index == 0) { + devidx = 0; + } else { + __set_bit(0, dev_use); + devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS); + } +#else devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS); +#endif if (devidx >= MMC_NUM_MINORS) return ERR_PTR(-ENOSPC); __set_bit(devidx, dev_use); @@ -470,6 +479,10 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) md->disk->fops = &mmc_bdops; md->disk->private_data = md; md->disk->queue = md->queue.queue; +#ifdef CONFIG_MACH_SMDK6410 /* XXX: SD Card removable */ + if (devidx != 0) + md->disk->flags |= GENHD_FL_REMOVABLE; +#endif md->disk->driverfs_dev = &card->dev; /* diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index d5e51b1..8a92533 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -249,6 +249,10 @@ static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) printk(KERN_DEBUG "%s: queuing CIS tuple 0x%02x length %u\n", mmc_hostname(card->host), tpl_code, tpl_link); + /* HACK: break this loop */ + ret = -EINVAL; + kfree(this); + /* END HACK */ } else { const struct cis_tpl *tpl = cis_tpl_list + i; if (tpl_link < tpl->min_size) { diff --git a/drivers/mmc/host/s3c-hsmmc.c b/drivers/mmc/host/s3c-hsmmc.c index 3d8c68e..236878c 100644 --- a/drivers/mmc/host/s3c-hsmmc.c +++ b/drivers/mmc/host/s3c-hsmmc.c @@ -691,6 +691,7 @@ static irqreturn_t s3c_hsmmc_irq (int irq, void *dev_id) struct s3c_hsmmc_host *host = dev_id; struct mmc_request *mrq; u32 intsts; + int cardint = 0; #ifdef CONFIG_HSMMC_S3C_IRQ_WORKAROUND uint i, org_irq_sts; @@ -802,10 +803,10 @@ static irqreturn_t s3c_hsmmc_irq (int irq, void *dev_id) s3c_hsmmc_finish_command(host); s3c_hsmmc_data_irq(host, intsts & S3C_HSMMC_INT_DATA_MASK); } - - intsts &= ~(S3C_HSMMC_INT_CMD_MASK | S3C_HSMMC_INT_DATA_MASK); } + intsts &= ~(S3C_HSMMC_INT_CMD_MASK | S3C_HSMMC_INT_DATA_MASK); + intsts &= ~S3C_HSMMC_NIS_ERR; /* XXX: fix later by scsuh */ #if 0 if (intsts & S3C_HSMMC_INT_BUS_POWER) { @@ -815,6 +816,12 @@ static irqreturn_t s3c_hsmmc_irq (int irq, void *dev_id) } intsts &= S3C_HSMMC_INT_BUS_POWER; +#endif + + if (intsts & S3C_HSMMC_INT_CARD_INT) { + intsts &= ~S3C_HSMMC_INT_CARD_INT; + cardint = 1; + } if (intsts) { printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n", @@ -822,7 +829,6 @@ static irqreturn_t s3c_hsmmc_irq (int irq, void *dev_id) s3c_hsmmc_writel(intsts, S3C_HSMMC_NORINTSTS); } -#endif #ifdef CONFIG_HSMMC_S3C_IRQ_WORKAROUND for (i=0; i<0x1000; i++) { @@ -838,6 +844,9 @@ insert: out: spin_unlock(&host->lock); + if (cardint) + mmc_signal_sdio_irq(host->mmc); + return result; } @@ -868,7 +877,7 @@ static irqreturn_t s3c_hsmmc_irq_cd (int irq, void *dev_id) return IRQ_HANDLED; - tasklet_schedule(&host->card_tasklet); + tasklet_schedule(&host->card_tasklet); mmiowb(); spin_unlock(&host->lock); @@ -899,7 +908,8 @@ static void s3c_hsmmc_request (struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; - if ((s3c_hsmmc_readl(S3C_HSMMC_PRNSTS) & S3C_HSMMC_CARD_PRESENT) || card_detect) { + if ((s3c_hsmmc_readl(S3C_HSMMC_PRNSTS) & S3C_HSMMC_CARD_PRESENT) || card_detect + || host->plat_data->hwport == 2 || host->plat_data->hwport == 1) { s3c_hsmmc_send_command(host, mrq->cmd); } else { host->mrq->cmd->error = -ENOMEDIUM; @@ -1085,9 +1095,41 @@ static void s3c_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) spin_unlock_irqrestore(&host->lock, iflags); } +static void s3c_hsmmc_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct s3c_hsmmc_host *host; + unsigned long flags; + u16 stsen; + u16 sigen; + + host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + + stsen = s3c_hsmmc_readw(S3C_HSMMC_NORINTSTSEN); + sigen = s3c_hsmmc_readw(S3C_HSMMC_NORINTSIGEN); + + stsen &= ~S3C_HSMMC_INT_CARD_INT; + sigen &= ~S3C_HSMMC_INT_CARD_INT; + + if (enable) { + s3c_hsmmc_writew(S3C_HSMMC_INT_CARD_INT, S3C_HSMMC_NORINTSTS); + stsen |= S3C_HSMMC_INT_CARD_INT; + sigen |= S3C_HSMMC_INT_CARD_INT; + } + + s3c_hsmmc_writew(stsen, S3C_HSMMC_NORINTSTSEN); + s3c_hsmmc_writew(sigen, S3C_HSMMC_NORINTSIGEN); + + mmiowb(); + + spin_unlock_irqrestore(&host->lock, flags); +} + static struct mmc_host_ops s3c_hsmmc_ops = { .request = s3c_hsmmc_request, .set_ios = s3c_hsmmc_set_ios, + .enable_sdio_irq= s3c_hsmmc_sdio_irq, }; static int s3c_hsmmc_probe (struct platform_device *pdev) @@ -1142,6 +1184,7 @@ static int s3c_hsmmc_probe (struct platform_device *pdev) goto untasklet; } + #if 0 /* To detect a card inserted on channel 0, an external interrupt is used. */ if ((plat_data->enabled == 1) && (plat_data->hwport == 0)) { host->irq_cd = platform_get_irq(pdev, 1); @@ -1156,6 +1199,7 @@ static int s3c_hsmmc_probe (struct platform_device *pdev) set_irq_type(host->irq_cd, IRQT_LOW); #endif } + #endif host->flags |= S3C_HSMMC_USE_DMA; @@ -1196,7 +1240,7 @@ static int s3c_hsmmc_probe (struct platform_device *pdev) /* you must make sure that our hsmmc block can support * up to 52MHz. by scsuh */ - mmc->f_max = 100 * MHZ; + mmc->f_max = plat_data->max_clock; mmc->caps = plat_data->host_caps; DBG("mmc->caps: %08x\n", mmc->caps); @@ -1249,11 +1293,13 @@ static int s3c_hsmmc_probe (struct platform_device *pdev) if (ret) goto untasklet; + #if 0 if ((plat_data->enabled == 1) && (plat_data->hwport == 0)) { ret = request_irq(host->irq_cd, s3c_hsmmc_irq_cd, 0, DRIVER_NAME, host); if (ret) goto untasklet; } + #endif s3c_hsmmc_ios_init(host); diff --git a/drivers/net/dm9000.c b/drivers/net/dm9000.c index 3286d2a..aa93ba4 100644 --- a/drivers/net/dm9000.c +++ b/drivers/net/dm9000.c @@ -112,8 +112,8 @@ #define writesw outsw #define writesl outsl #define DM9000_IRQ_FLAGS (IRQF_SHARED | IRQF_TRIGGER_HIGH) -#else -#define DM9000_IRQ_FLAGS IRQF_SHARED +#else// XXX: for HHTech platform +#define DM9000_IRQ_FLAGS (IRQF_SHARED | IRQF_TRIGGER_HIGH) #endif /* @@ -588,9 +588,12 @@ dm9000_probe(struct platform_device *pdev) ndev->dev_addr[i] = ior(db, i+DM9000_PAR); } - if (!is_valid_ether_addr(ndev->dev_addr)) - printk("%s: Invalid ethernet MAC address. Please " - "set using ifconfig\n", ndev->name); + if (!is_valid_ether_addr(ndev->dev_addr)) { + random_ether_addr(ndev->dev_addr); + + //printk("%s: Invalid ethernet MAC address. Please " + // "set using ifconfig\n", ndev->name); + } else get_random_bytes(&ndev->dev_addr[5], 1); platform_set_drvdata(pdev, ndev); ret = register_netdev(ndev); diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index 2c08c0a..784e6c0 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -295,6 +295,28 @@ config LIBERTAS_DEBUG ---help--- Debugging support. +config MARVELL_8686_SDIO + tristate "Official driver for Marvell SDIO 8686" + depends on WLAN_80211 && MMC + select WIRELESS_EXT + select IEEE80211 + select FW_LOADER + ---help--- + Official driver for Marvell 8686 devices. + +config MARVELL_8686_PROC_FS + bool "Enable proc-fs support for official marvell 8686 module." + depends on MARVELL_8686_SDIO + ---help--- + Proc-fs support. + +config MARVELL_8686_DEBUG + bool "Enable full debugging output in the official marvell 8686 module." + depends on MARVELL_8686_SDIO + ---help--- + Debugging support. + + config AIRO tristate "Cisco/Aironet 34X/35X/4500/4800 ISA and PCI cards" depends on ISA_DMA_API && WLAN_80211 && (PCI || BROKEN) diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 6f32b53..867b829 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -47,6 +47,8 @@ obj-$(CONFIG_PCMCIA_WL3501) += wl3501_cs.o obj-$(CONFIG_USB_ZD1201) += zd1201.o obj-$(CONFIG_LIBERTAS) += libertas/ +obj-$(CONFIG_MARVELL_8686_SDIO) += marvell8686/ + rtl8187-objs := rtl8187_dev.o rtl8187_rtl8225.o obj-$(CONFIG_RTL8187) += rtl8187.o diff --git a/drivers/net/wireless/libertas/Makefile b/drivers/net/wireless/libertas/Makefile index 0e27876..e4c8cd3 100644 --- a/drivers/net/wireless/libertas/Makefile +++ b/drivers/net/wireless/libertas/Makefile @@ -2,7 +2,7 @@ libertas-objs := main.o wext.o \ rx.o tx.o cmd.o \ cmdresp.o scan.o \ join.o 11d.o \ - debugfs.o \ + ioctl.o debugfs.o \ ethtool.o assoc.o usb8xxx-objs += if_usb.o diff --git a/drivers/net/wireless/libertas/cmd.c b/drivers/net/wireless/libertas/cmd.c index be5cfd8..8b2c34c 100644 --- a/drivers/net/wireless/libertas/cmd.c +++ b/drivers/net/wireless/libertas/cmd.c @@ -1400,6 +1400,12 @@ int libertas_prepare_and_send_command(wlan_private * priv, S_DS_GEN); ret = 0; break; + case CMD_802_11_TX_RATE_QUERY: + cmdptr->command = cpu_to_le16(CMD_802_11_TX_RATE_QUERY); + cmdptr->size = cpu_to_le16(sizeof(struct cmd_tx_rate_query) + S_DS_GEN); + adapter->cur_rate = 0; + ret = 0; + break; default: lbs_deb_host("PREP_CMD: unknown command 0x%04x\n", cmd_no); ret = -1; diff --git a/drivers/net/wireless/libertas/cmdresp.c b/drivers/net/wireless/libertas/cmdresp.c index 8f90892..a413f88 100644 --- a/drivers/net/wireless/libertas/cmdresp.c +++ b/drivers/net/wireless/libertas/cmdresp.c @@ -194,6 +194,9 @@ static int wlan_ret_get_hw_spec(wlan_private * priv, lbs_pr_info("unidentified region code; using the default (USA)\n"); } + /* HACK IT MAKE regioncode always equals 0x30 */ + adapter->regioncode = 0x30; + if (adapter->current_addr[0] == 0xff) memmove(adapter->current_addr, hwspec->permanentaddr, ETH_ALEN); @@ -310,7 +313,7 @@ static int wlan_ret_802_11_key_material(wlan_private * priv, lbs_deb_enter(LBS_DEB_CMD); /* Copy the returned key to driver private data */ - if (action == CMD_ACT_GET) { + if (action == CMD_ACT_SET) { u8 * buf_ptr = (u8 *) &pkeymaterial->keyParamSet; u8 * resp_end = (u8 *) (resp + le16_to_cpu(resp->size)); diff --git a/drivers/net/wireless/libertas/ioctl.c b/drivers/net/wireless/libertas/ioctl.c new file mode 100644 index 0000000..369ffc4 --- /dev/null +++ b/drivers/net/wireless/libertas/ioctl.c @@ -0,0 +1,1046 @@ +/** + * This file contains ioctl functions + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "host.h" +#include "radiotap.h" +#include "decl.h" +#include "defs.h" +#include "dev.h" +#include "join.h" +#include "wext.h" + +#define MAX_SCAN_CELL_SIZE (IW_EV_ADDR_LEN + \ + IW_ESSID_MAX_SIZE + \ + IW_EV_UINT_LEN + IW_EV_FREQ_LEN + \ + IW_EV_QUAL_LEN + IW_ESSID_MAX_SIZE + \ + IW_EV_PARAM_LEN + 40) /* 40 for WPAIE */ + +#define WAIT_FOR_SCAN_RRESULT_MAX_TIME (10 * HZ) + +#define ENTER() +#define LEAVE() +#define lbs_pr_debug lbs_deb_ioctl + +static int wlan_set_region(wlan_private * priv, u16 region_code) +{ + int i; + + for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { + // use the region code to search for the index + if (region_code == libertas_region_code_to_index[i]) { + //priv->adapter->regiontableindex = (u16) i; + priv->adapter->regioncode = region_code; + break; + } + } + + // if it's unidentified region code + if (i >= MRVDRV_MAX_REGION_CODE) { + lbs_pr_debug(1, "region Code not identified\n"); + LEAVE(); + return -1; + } + + if (libertas_set_regiontable(priv, priv->adapter->regioncode, 0)) { + LEAVE(); + return -EINVAL; + } + + return 0; +} + +static inline int hex2int(char c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + return -1; +} + +/* Convert a string representation of a MAC address ("xx:xx:xx:xx:xx:xx") + into binary format (6 bytes). + + This function expects that each byte is represented with 2 characters + (e.g., 11:2:11:11:11:11 is invalid) + + */ +static char *eth_str2addr(char *ethstr, u8 * addr) +{ + int i, val, val2; + char *pos = ethstr; + + /* get rid of initial blanks */ + while (*pos == ' ' || *pos == '\t') + ++pos; + + for (i = 0; i < 6; i++) { + val = hex2int(*pos++); + if (val < 0) + return NULL; + val2 = hex2int(*pos++); + if (val2 < 0) + return NULL; + addr[i] = (val * 16 + val2) & 0xff; + + if (i < 5 && *pos++ != ':') + return NULL; + } + return pos; +} + +/* this writes xx:xx:xx:xx:xx:xx into ethstr + (ethstr must have space for 18 chars) */ +static int eth_addr2str(u8 * addr, char *ethstr) +{ + int i; + char *pos = ethstr; + + for (i = 0; i < 6; i++) { + sprintf(pos, "%02x", addr[i] & 0xff); + pos += 2; + if (i < 5) + *pos++ = ':'; + } + return 17; +} + +/** + * @brief Add an entry to the BT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_bt_add_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char ethaddrs_str[18]; + char *pos; + u8 ethaddr[ETH_ALEN]; + + ENTER(); + if (copy_from_user(ethaddrs_str, wrq->u.data.pointer, + sizeof(ethaddrs_str))) + return -EFAULT; + + if ((pos = eth_str2addr(ethaddrs_str, ethaddr)) == NULL) { + lbs_pr_info("BT_ADD: Invalid MAC address\n"); + return -EINVAL; + } + + lbs_pr_debug(1, "BT: adding %s\n", ethaddrs_str); + LEAVE(); + return (libertas_prepare_and_send_command(priv, CMD_BT_ACCESS, + CMD_ACT_BT_ACCESS_ADD, + CMD_OPTION_WAITFORRSP, 0, ethaddr)); +} + +/** + * @brief Delete an entry from the BT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_bt_del_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char ethaddrs_str[18]; + u8 ethaddr[ETH_ALEN]; + char *pos; + + ENTER(); + if (copy_from_user(ethaddrs_str, wrq->u.data.pointer, + sizeof(ethaddrs_str))) + return -EFAULT; + + if ((pos = eth_str2addr(ethaddrs_str, ethaddr)) == NULL) { + lbs_pr_info("Invalid MAC address\n"); + return -EINVAL; + } + + lbs_pr_debug(1, "BT: deleting %s\n", ethaddrs_str); + + return (libertas_prepare_and_send_command(priv, + CMD_BT_ACCESS, + CMD_ACT_BT_ACCESS_DEL, + CMD_OPTION_WAITFORRSP, 0, ethaddr)); + LEAVE(); + return 0; +} + +/** + * @brief Reset all entries from the BT table + * @param priv A pointer to wlan_private structure + * @return 0 --success, otherwise fail + */ +static int wlan_bt_reset_ioctl(wlan_private * priv) +{ + ENTER(); + + lbs_pr_alert( "BT: resetting\n"); + + return (libertas_prepare_and_send_command(priv, + CMD_BT_ACCESS, + CMD_ACT_BT_ACCESS_RESET, + CMD_OPTION_WAITFORRSP, 0, NULL)); + + LEAVE(); + return 0; +} + +/** + * @brief List an entry from the BT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_bt_list_ioctl(wlan_private * priv, struct ifreq *req) +{ + int pos; + char *addr1; + struct iwreq *wrq = (struct iwreq *)req; + /* used to pass id and store the bt entry returned by the FW */ + union { + int id; + char addr1addr2[2 * ETH_ALEN]; + } param; + static char outstr[64]; + char *pbuf = outstr; + int ret; + + ENTER(); + + if (copy_from_user(outstr, wrq->u.data.pointer, sizeof(outstr))) { + lbs_pr_debug(1, "Copy from user failed\n"); + return -1; + } + param.id = simple_strtoul(outstr, NULL, 10); + pos = sprintf(pbuf, "%d: ", param.id); + pbuf += pos; + + ret = libertas_prepare_and_send_command(priv, CMD_BT_ACCESS, + CMD_ACT_BT_ACCESS_LIST, + CMD_OPTION_WAITFORRSP, 0, + (char *)¶m); + + if (ret == 0) { + addr1 = param.addr1addr2; + + pos = sprintf(pbuf, "ignoring traffic from "); + pbuf += pos; + pos = eth_addr2str(addr1, pbuf); + pbuf += pos; + } else { + sprintf(pbuf, "(null)"); + pbuf += pos; + } + + wrq->u.data.length = strlen(outstr); + if (copy_to_user(wrq->u.data.pointer, (char *)outstr, + wrq->u.data.length)) { + lbs_pr_debug(1, "BT_LIST: Copy to user failed!\n"); + return -EFAULT; + } + + LEAVE(); + return 0; +} + +/** + * @brief Find the next parameter in an input string + * @param ptr A pointer to the input parameter string + * @return A pointer to the next parameter, or 0 if no parameters left. + */ +static char * next_param(char * ptr) +{ + if (!ptr) return NULL; + while (*ptr == ' ' || *ptr == '\t') ++ptr; + return (*ptr == '\0') ? NULL : ptr; +} + +/** + * @brief Add an entry to the FWT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_add_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char in_str[128]; + static struct cmd_ds_fwt_access fwt_access; + char *ptr; + + ENTER(); + if (copy_from_user(in_str, wrq->u.data.pointer, sizeof(in_str))) + return -EFAULT; + + if ((ptr = eth_str2addr(in_str, fwt_access.da)) == NULL) { + lbs_pr_alert( "FWT_ADD: Invalid MAC address 1\n"); + return -EINVAL; + } + + if ((ptr = eth_str2addr(ptr, fwt_access.ra)) == NULL) { + lbs_pr_alert( "FWT_ADD: Invalid MAC address 2\n"); + return -EINVAL; + } + + if ((ptr = next_param(ptr))) + fwt_access.metric = + cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + else + fwt_access.metric = FWT_DEFAULT_METRIC; + + if ((ptr = next_param(ptr))) + fwt_access.dir = (u8)simple_strtoul(ptr, &ptr, 10); + else + fwt_access.dir = FWT_DEFAULT_DIR; + + if ((ptr = next_param(ptr))) + fwt_access.ssn = + cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + else + fwt_access.ssn = FWT_DEFAULT_SSN; + + if ((ptr = next_param(ptr))) + fwt_access.dsn = + cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + else + fwt_access.dsn = FWT_DEFAULT_DSN; + + if ((ptr = next_param(ptr))) + fwt_access.hopcount = simple_strtoul(ptr, &ptr, 10); + else + fwt_access.hopcount = FWT_DEFAULT_HOPCOUNT; + + if ((ptr = next_param(ptr))) + fwt_access.ttl = simple_strtoul(ptr, &ptr, 10); + else + fwt_access.ttl = FWT_DEFAULT_TTL; + + if ((ptr = next_param(ptr))) + fwt_access.expiration = + cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + else + fwt_access.expiration = FWT_DEFAULT_EXPIRATION; + + if ((ptr = next_param(ptr))) + fwt_access.sleepmode = (u8)simple_strtoul(ptr, &ptr, 10); + else + fwt_access.sleepmode = FWT_DEFAULT_SLEEPMODE; + + if ((ptr = next_param(ptr))) + fwt_access.snr = + cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + else + fwt_access.snr = FWT_DEFAULT_SNR; + +#ifdef DEBUG + { + char ethaddr1_str[18], ethaddr2_str[18]; + eth_addr2str(fwt_access.da, ethaddr1_str); + eth_addr2str(fwt_access.ra, ethaddr2_str); + lbs_pr_debug(1, "FWT_ADD: adding (da:%s,%i,ra:%s)\n", ethaddr1_str, + fwt_access.dir, ethaddr2_str); + lbs_pr_debug(1, "FWT_ADD: ssn:%u dsn:%u met:%u hop:%u ttl:%u exp:%u slp:%u snr:%u\n", + fwt_access.ssn, fwt_access.dsn, fwt_access.metric, + fwt_access.hopcount, fwt_access.ttl, fwt_access.expiration, + fwt_access.sleepmode, fwt_access.snr); + } +#endif + + LEAVE(); + return (libertas_prepare_and_send_command(priv, CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_ADD, + CMD_OPTION_WAITFORRSP, 0, + (void *)&fwt_access)); +} + +/** + * @brief Delete an entry from the FWT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_del_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char in_str[64]; + static struct cmd_ds_fwt_access fwt_access; + char *ptr; + + ENTER(); + if (copy_from_user(in_str, wrq->u.data.pointer, sizeof(in_str))) + return -EFAULT; + + if ((ptr = eth_str2addr(in_str, fwt_access.da)) == NULL) { + lbs_pr_alert( "FWT_DEL: Invalid MAC address 1\n"); + return -EINVAL; + } + + if ((ptr = eth_str2addr(ptr, fwt_access.ra)) == NULL) { + lbs_pr_alert( "FWT_DEL: Invalid MAC address 2\n"); + return -EINVAL; + } + + if ((ptr = next_param(ptr))) + fwt_access.dir = (u8)simple_strtoul(ptr, &ptr, 10); + else + fwt_access.dir = FWT_DEFAULT_DIR; + +#ifdef DEBUG + { + char ethaddr1_str[18], ethaddr2_str[18]; + lbs_pr_debug(1, "FWT_DEL: line is %s\n", in_str); + eth_addr2str(fwt_access.da, ethaddr1_str); + eth_addr2str(fwt_access.ra, ethaddr2_str); + lbs_pr_debug(1, "FWT_DEL: removing (da:%s,ra:%s,dir:%d)\n", ethaddr1_str, + ethaddr2_str, fwt_access.dir); + } +#endif + + LEAVE(); + return (libertas_prepare_and_send_command(priv, + CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_DEL, + CMD_OPTION_WAITFORRSP, 0, + (void *)&fwt_access)); +} + + +/** + * @brief Print route parameters + * @param fwt_access struct cmd_ds_fwt_access with route info + * @param buf destination buffer for route info + */ +static void print_route(struct cmd_ds_fwt_access fwt_access, char *buf) +{ + buf += sprintf(buf, " "); + buf += eth_addr2str(fwt_access.da, buf); + buf += sprintf(buf, " "); + buf += eth_addr2str(fwt_access.ra, buf); + buf += sprintf(buf, " %u", le32_to_cpu(fwt_access.metric)); + buf += sprintf(buf, " %u", fwt_access.dir); + buf += sprintf(buf, " %u", le32_to_cpu(fwt_access.ssn)); + buf += sprintf(buf, " %u", le32_to_cpu(fwt_access.dsn)); + buf += sprintf(buf, " %u", fwt_access.hopcount); + buf += sprintf(buf, " %u", fwt_access.ttl); + buf += sprintf(buf, " %u", le32_to_cpu(fwt_access.expiration)); + buf += sprintf(buf, " %u", fwt_access.sleepmode); + buf += sprintf(buf, " %u", le32_to_cpu(fwt_access.snr)); +} + +/** + * @brief Lookup an entry in the FWT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_lookup_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char in_str[64]; + char *ptr; + static struct cmd_ds_fwt_access fwt_access; + static char out_str[128]; + int ret; + + ENTER(); + if (copy_from_user(in_str, wrq->u.data.pointer, sizeof(in_str))) + return -EFAULT; + + if ((ptr = eth_str2addr(in_str, fwt_access.da)) == NULL) { + lbs_pr_alert( "FWT_LOOKUP: Invalid MAC address\n"); + return -EINVAL; + } + +#ifdef DEBUG + { + char ethaddr1_str[18]; + lbs_pr_debug(1, "FWT_LOOKUP: line is %s\n", in_str); + eth_addr2str(fwt_access.da, ethaddr1_str); + lbs_pr_debug(1, "FWT_LOOKUP: looking for (da:%s)\n", ethaddr1_str); + } +#endif + + ret = libertas_prepare_and_send_command(priv, + CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_LOOKUP, + CMD_OPTION_WAITFORRSP, 0, + (void *)&fwt_access); + + if (ret == 0) + print_route(fwt_access, out_str); + else + sprintf(out_str, "(null)"); + + wrq->u.data.length = strlen(out_str); + if (copy_to_user(wrq->u.data.pointer, (char *)out_str, + wrq->u.data.length)) { + lbs_pr_debug(1, "FWT_LOOKUP: Copy to user failed!\n"); + return -EFAULT; + } + + LEAVE(); + return 0; +} + +/** + * @brief Reset all entries from the FWT table + * @param priv A pointer to wlan_private structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_reset_ioctl(wlan_private * priv) +{ + lbs_pr_debug(1, "FWT: resetting\n"); + + return (libertas_prepare_and_send_command(priv, + CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_RESET, + CMD_OPTION_WAITFORRSP, 0, NULL)); +} + +/** + * @brief List an entry from the FWT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_list_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char in_str[8]; + static struct cmd_ds_fwt_access fwt_access; + char *ptr = in_str; + static char out_str[128]; + char *pbuf = out_str; + int ret; + + ENTER(); + if (copy_from_user(in_str, wrq->u.data.pointer, sizeof(in_str))) + return -EFAULT; + + fwt_access.id = cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + +#ifdef DEBUG + { + lbs_pr_debug(1, "FWT_LIST: line is %s\n", in_str); + lbs_pr_debug(1, "FWT_LIST: listing id:%i\n", le32_to_cpu(fwt_access.id)); + } +#endif + + ret = libertas_prepare_and_send_command(priv, CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_LIST, + CMD_OPTION_WAITFORRSP, 0, (void *)&fwt_access); + + if (ret == 0) + print_route(fwt_access, pbuf); + else + pbuf += sprintf(pbuf, " (null)"); + + wrq->u.data.length = strlen(out_str); + if (copy_to_user(wrq->u.data.pointer, (char *)out_str, + wrq->u.data.length)) { + lbs_pr_debug(1, "FWT_LIST: Copy to user failed!\n"); + return -EFAULT; + } + + LEAVE(); + return 0; +} + +/** + * @brief List an entry from the FRT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_list_route_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char in_str[64]; + static struct cmd_ds_fwt_access fwt_access; + char *ptr = in_str; + static char out_str[128]; + char *pbuf = out_str; + int ret; + + ENTER(); + if (copy_from_user(in_str, wrq->u.data.pointer, sizeof(in_str))) + return -EFAULT; + + fwt_access.id = cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + +#ifdef DEBUG + { + lbs_pr_debug(1, "FWT_LIST_ROUTE: line is %s\n", in_str); + lbs_pr_debug(1, "FWT_LIST_ROUTE: listing id:%i\n", le32_to_cpu(fwt_access.id)); + } +#endif + + ret = libertas_prepare_and_send_command(priv, CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_LIST_route, + CMD_OPTION_WAITFORRSP, 0, (void *)&fwt_access); + + if (ret == 0) { + pbuf += sprintf(pbuf, " "); + pbuf += eth_addr2str(fwt_access.da, pbuf); + pbuf += sprintf(pbuf, " %u", le32_to_cpu(fwt_access.metric)); + pbuf += sprintf(pbuf, " %u", fwt_access.dir); + /* note that the firmware returns the nid in the id field */ + pbuf += sprintf(pbuf, " %u", le32_to_cpu(fwt_access.id)); + pbuf += sprintf(pbuf, " %u", le32_to_cpu(fwt_access.ssn)); + pbuf += sprintf(pbuf, " %u", le32_to_cpu(fwt_access.dsn)); + pbuf += sprintf(pbuf, " hop %u", fwt_access.hopcount); + pbuf += sprintf(pbuf, " ttl %u", fwt_access.ttl); + pbuf += sprintf(pbuf, " %u", le32_to_cpu(fwt_access.expiration)); + } else + pbuf += sprintf(pbuf, " (null)"); + + wrq->u.data.length = strlen(out_str); + if (copy_to_user(wrq->u.data.pointer, (char *)out_str, + wrq->u.data.length)) { + lbs_pr_debug(1, "FWT_LIST_ROUTE: Copy to user failed!\n"); + return -EFAULT; + } + + LEAVE(); + return 0; +} + +/** + * @brief List an entry from the FNT table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_list_neighbor_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *)req; + char in_str[8]; + static struct cmd_ds_fwt_access fwt_access; + char *ptr = in_str; + static char out_str[128]; + char *pbuf = out_str; + int ret; + + ENTER(); + if (copy_from_user(in_str, wrq->u.data.pointer, sizeof(in_str))) + return -EFAULT; + + memset(&fwt_access, 0, sizeof(fwt_access)); + fwt_access.id = cpu_to_le32(simple_strtoul(ptr, &ptr, 10)); + +#ifdef DEBUG + { + lbs_pr_debug(1, "FWT_LIST_NEIGHBOR: line is %s\n", in_str); + lbs_pr_debug(1, "FWT_LIST_NEIGHBOR: listing id:%i\n", le32_to_cpu(fwt_access.id)); + } +#endif + + ret = libertas_prepare_and_send_command(priv, CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_LIST_neighbor, + CMD_OPTION_WAITFORRSP, 0, + (void *)&fwt_access); + + if (ret == 0) { + pbuf += sprintf(pbuf, " ra "); + pbuf += eth_addr2str(fwt_access.ra, pbuf); + pbuf += sprintf(pbuf, " slp %u", fwt_access.sleepmode); + pbuf += sprintf(pbuf, " snr %u", le32_to_cpu(fwt_access.snr)); + pbuf += sprintf(pbuf, " ref %u", le32_to_cpu(fwt_access.references)); + } else + pbuf += sprintf(pbuf, " (null)"); + + wrq->u.data.length = strlen(out_str); + if (copy_to_user(wrq->u.data.pointer, (char *)out_str, + wrq->u.data.length)) { + lbs_pr_debug(1, "FWT_LIST_NEIGHBOR: Copy to user failed!\n"); + return -EFAULT; + } + + LEAVE(); + return 0; +} + +/** + * @brief Cleans up the route (FRT) and neighbor (FNT) tables + * (Garbage Collection) + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_cleanup_ioctl(wlan_private * priv, struct ifreq *req) +{ + static struct cmd_ds_fwt_access fwt_access; + int ret; + + ENTER(); + + lbs_pr_debug(1, "FWT: cleaning up\n"); + + memset(&fwt_access, 0, sizeof(fwt_access)); + + ret = libertas_prepare_and_send_command(priv, CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_CLEANUP, + CMD_OPTION_WAITFORRSP, 0, + (void *)&fwt_access); + + if (ret == 0) + req->ifr_data = (char *)(le32_to_cpu(fwt_access.references)); + else + return -EFAULT; + + LEAVE(); + return 0; +} + +/** + * @brief Gets firmware internal time (debug purposes) + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_fwt_time_ioctl(wlan_private * priv, struct ifreq *req) +{ + static struct cmd_ds_fwt_access fwt_access; + int ret; + + ENTER(); + + lbs_pr_debug(1, "FWT: getting time\n"); + + memset(&fwt_access, 0, sizeof(fwt_access)); + + ret = libertas_prepare_and_send_command(priv, CMD_FWT_ACCESS, + CMD_ACT_FWT_ACCESS_TIME, + CMD_OPTION_WAITFORRSP, 0, + (void *)&fwt_access); + + if (ret == 0) + req->ifr_data = (char *)(le32_to_cpu(fwt_access.references)); + else + return -EFAULT; + + LEAVE(); + return 0; +} + +/** + * @brief Gets mesh ttl from firmware + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_mesh_get_ttl_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct cmd_ds_mesh_access mesh_access; + int ret; + + ENTER(); + + memset(&mesh_access, 0, sizeof(mesh_access)); + + ret = libertas_prepare_and_send_command(priv, CMD_MESH_ACCESS, + CMD_ACT_MESH_GET_TTL, + CMD_OPTION_WAITFORRSP, 0, + (void *)&mesh_access); + + if (ret == 0) { + req->ifr_data = (char *)(le32_to_cpu(mesh_access.data[0])); + } + else + return -EFAULT; + + LEAVE(); + return 0; +} + +/** + * @brief Gets mesh ttl from firmware + * @param priv A pointer to wlan_private structure + * @param ttl New ttl value + * @return 0 --success, otherwise fail + */ +static int wlan_mesh_set_ttl_ioctl(wlan_private * priv, int ttl) +{ + struct cmd_ds_mesh_access mesh_access; + int ret; + + ENTER(); + + if( (ttl > 0xff) || (ttl < 0) ) + return -EINVAL; + + memset(&mesh_access, 0, sizeof(mesh_access)); + mesh_access.data[0] = ttl; + + ret = libertas_prepare_and_send_command(priv, CMD_MESH_ACCESS, + CMD_ACT_MESH_SET_TTL, + CMD_OPTION_WAITFORRSP, 0, + (void *)&mesh_access); + + if (ret != 0) + ret = -EFAULT; + + LEAVE(); + return ret; +} + +/** + * @brief Get/Set inactivity timeout + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return 0 --success, otherwise fail + */ +static int wlan_inactivity_timeout(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + int data = 0; + u16 timeout = 0; + + ENTER(); + if (wrq->u.data.length > 1) + return -ENOTSUPP; + + if (wrq->u.data.length == 0) { + /* Get */ + ret = libertas_prepare_and_send_command(priv, + CMD_802_11_INACTIVITY_TIMEOUT, + CMD_ACT_GET, + CMD_OPTION_WAITFORRSP, 0, + &timeout); + data = timeout; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + lbs_pr_debug(1, "Copy to user failed\n"); + return -EFAULT; + } + } else { + /* Set */ + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + lbs_pr_debug(1, "Copy from user failed\n"); + return -EFAULT; + } + + timeout = data; + ret = libertas_prepare_and_send_command(priv, + CMD_802_11_INACTIVITY_TIMEOUT, + CMD_ACT_SET, + CMD_OPTION_WAITFORRSP, 0, + &timeout); + } + + wrq->u.data.length = 1; + + LEAVE(); + return ret; +} + +/** + * @brief ioctl function - entry point + * + * @param dev A pointer to net_device structure + * @param req A pointer to ifreq structure + * @param cmd command + * @return 0--success, otherwise fail + */ +int libertas_do_ioctl(struct net_device *dev, struct ifreq *req, int cmd) +{ + int subcmd = 0; + int idata = 0; + int *pdata; + int ret = 0; + wlan_private *priv = dev->priv; + wlan_adapter *adapter = priv->adapter; + struct iwreq *wrq = (struct iwreq *)req; + + ENTER(); + + lbs_pr_debug(1, "libertas_do_ioctl: ioctl cmd = 0x%x\n", cmd); + switch (cmd) { + case WLAN_SETNONE_GETNONE: /* set WPA mode on/off ioctl #20 */ + switch (wrq->u.data.flags) { + case WLAN_SUBCMD_BT_RESET: /* bt_reset */ + wlan_bt_reset_ioctl(priv); + break; + case WLAN_SUBCMD_FWT_RESET: /* fwt_reset */ + wlan_fwt_reset_ioctl(priv); + break; + } /* End of switch */ + break; + + case WLAN_SETONEINT_GETNONE: + /* The first 4 bytes of req->ifr_data is sub-ioctl number + * after 4 bytes sits the payload. + */ + subcmd = wrq->u.data.flags; //from wpa_supplicant subcmd + + if (!subcmd) + subcmd = (int)req->ifr_data; //from iwpriv subcmd + + switch (subcmd) { + case WLANSETREGION: + idata = SUBCMD_DATA(wrq); + ret = wlan_set_region(priv, (u16) idata); + break; + case WLAN_SUBCMD_MESH_SET_TTL: + idata = SUBCMD_DATA(wrq); + ret = wlan_mesh_set_ttl_ioctl(priv, idata); + break; + + default: + ret = -EOPNOTSUPP; + break; + } + + break; + + case WLAN_SET128CHAR_GET128CHAR: + switch ((int)wrq->u.data.flags) { + case WLAN_SUBCMD_BT_ADD: + ret = wlan_bt_add_ioctl(priv, req); + break; + case WLAN_SUBCMD_BT_DEL: + ret = wlan_bt_del_ioctl(priv, req); + break; + case WLAN_SUBCMD_BT_LIST: + ret = wlan_bt_list_ioctl(priv, req); + break; + case WLAN_SUBCMD_FWT_ADD: + ret = wlan_fwt_add_ioctl(priv, req); + break; + case WLAN_SUBCMD_FWT_DEL: + ret = wlan_fwt_del_ioctl(priv, req); + break; + case WLAN_SUBCMD_FWT_LOOKUP: + ret = wlan_fwt_lookup_ioctl(priv, req); + break; + case WLAN_SUBCMD_FWT_LIST_NEIGHBOR: + ret = wlan_fwt_list_neighbor_ioctl(priv, req); + break; + case WLAN_SUBCMD_FWT_LIST: + ret = wlan_fwt_list_ioctl(priv, req); + break; + case WLAN_SUBCMD_FWT_LIST_ROUTE: + ret = wlan_fwt_list_route_ioctl(priv, req); + break; + } + break; + + case WLAN_SETNONE_GETONEINT: + switch ((int)req->ifr_data) { + case WLANGETREGION: + pdata = (int *)wrq->u.name; + *pdata = (int)adapter->regioncode; + break; + case WLAN_SUBCMD_FWT_CLEANUP: /* fwt_cleanup */ + ret = wlan_fwt_cleanup_ioctl(priv, req); + break; + + case WLAN_SUBCMD_FWT_TIME: /* fwt_time */ + ret = wlan_fwt_time_ioctl(priv, req); + break; + + case WLAN_SUBCMD_MESH_GET_TTL: + ret = wlan_mesh_get_ttl_ioctl(priv, req); + break; + + default: + ret = -EOPNOTSUPP; + + } + + break; + + case WLAN_SET_GET_SIXTEEN_INT: + switch ((int)wrq->u.data.flags) { + case WLAN_LED_GPIO_CTRL: + { + int i; + int data[16]; + + struct cmd_ds_802_11_led_ctrl ctrl; + struct mrvlietypes_ledgpio *gpio = + (struct mrvlietypes_ledgpio *) ctrl.data; + + memset(&ctrl, 0, sizeof(ctrl)); + if (wrq->u.data.length > MAX_LEDS * 2) + return -ENOTSUPP; + if ((wrq->u.data.length % 2) != 0) + return -ENOTSUPP; + if (wrq->u.data.length == 0) { + ctrl.action = + cpu_to_le16 + (CMD_ACT_GET); + } else { + if (copy_from_user + (data, wrq->u.data.pointer, + sizeof(int) * + wrq->u.data.length)) { + lbs_pr_debug(1, + "Copy from user failed\n"); + return -EFAULT; + } + + ctrl.action = + cpu_to_le16 + (CMD_ACT_SET); + ctrl.numled = cpu_to_le16(0); + gpio->header.type = + cpu_to_le16(TLV_TYPE_LED_GPIO); + gpio->header.len = wrq->u.data.length; + for (i = 0; i < wrq->u.data.length; + i += 2) { + gpio->ledpin[i / 2].led = + data[i]; + gpio->ledpin[i / 2].pin = + data[i + 1]; + } + } + ret = + libertas_prepare_and_send_command(priv, + CMD_802_11_LED_GPIO_CTRL, + 0, + CMD_OPTION_WAITFORRSP, + 0, (void *)&ctrl); + for (i = 0; i < gpio->header.len; i += 2) { + data[i] = gpio->ledpin[i / 2].led; + data[i + 1] = gpio->ledpin[i / 2].pin; + } + if (copy_to_user(wrq->u.data.pointer, data, + sizeof(int) * + gpio->header.len)) { + lbs_pr_debug(1, "Copy to user failed\n"); + return -EFAULT; + } + + wrq->u.data.length = gpio->header.len; + } + break; + case WLAN_INACTIVITY_TIMEOUT: + ret = wlan_inactivity_timeout(priv, wrq); + break; + } + break; + + default: + ret = -EINVAL; + break; + } + LEAVE(); + return ret; +} + + diff --git a/drivers/net/wireless/libertas/main.c b/drivers/net/wireless/libertas/main.c index 1823b48..47b0288 100644 --- a/drivers/net/wireless/libertas/main.c +++ b/drivers/net/wireless/libertas/main.c @@ -36,6 +36,7 @@ unsigned int libertas_debug = 0; module_param(libertas_debug, int, 0644); EXPORT_SYMBOL_GPL(libertas_debug); +int libertas_do_ioctl(struct net_device *dev, struct ifreq *req, int i); #define WLAN_TX_PWR_DEFAULT 20 /*100mW */ #define WLAN_TX_PWR_US_DEFAULT 20 /*100mW */ @@ -945,9 +946,8 @@ static int wlan_setup_firmware(wlan_private * priv) libertas_set_mac_packet_filter(priv); /* Get the supported Data rates */ - ret = libertas_prepare_and_send_command(priv, CMD_802_11_DATA_RATE, - CMD_ACT_GET_TX_RATE, - CMD_OPTION_WAITFORRSP, 0, NULL); + ret = libertas_prepare_and_send_command(priv, CMD_802_11_TX_RATE_QUERY, + CMD_ACT_GET, CMD_OPTION_WAITFORRSP, 0, NULL); if (ret) { ret = -1; @@ -1156,6 +1156,7 @@ wlan_private *libertas_add_card(void *card, struct device *dmdev) /* Setup the OS Interface to our functions */ dev->open = libertas_open; dev->hard_start_xmit = libertas_pre_start_xmit; + dev->do_ioctl = libertas_do_ioctl; dev->stop = libertas_close; dev->set_mac_address = libertas_set_mac_address; dev->tx_timeout = libertas_tx_timeout; @@ -1334,6 +1335,7 @@ int libertas_add_mesh(wlan_private *priv, struct device *dev) mesh_dev->open = libertas_mesh_open; mesh_dev->hard_start_xmit = libertas_mesh_pre_start_xmit; + mesh_dev->do_ioctl = libertas_do_ioctl; mesh_dev->stop = libertas_mesh_close; mesh_dev->get_stats = libertas_get_stats; mesh_dev->set_mac_address = libertas_set_mac_address; diff --git a/drivers/net/wireless/libertas/scan.c b/drivers/net/wireless/libertas/scan.c index ad1e67d..748358e 100644 --- a/drivers/net/wireless/libertas/scan.c +++ b/drivers/net/wireless/libertas/scan.c @@ -361,7 +361,7 @@ void libertas_scan_worker(struct work_struct *work) { wlan_private *priv = container_of(work, wlan_private, scan_work.work); - wlan_scan_networks(priv, NULL, 0); + wlan_scan_networks(priv, NULL, 1); } @@ -633,8 +633,10 @@ static int wlan_scan_channel_list(wlan_private * priv, /* Set the temp channel struct pointer to the start of the desired list */ ptmpchan = pscanchanlist; +#if 0 //We always do full scan if (priv->adapter->last_scanned_channel && !puserscanin) ptmpchan += priv->adapter->last_scanned_channel; +#endif /* Loop through the desired channel list, sending a new firmware scan * commands for each maxchanperscan channels (or for 1,6,11 individually diff --git a/drivers/net/wireless/libertas/wext.c b/drivers/net/wireless/libertas/wext.c index 395b788..a1acb7c 100644 --- a/drivers/net/wireless/libertas/wext.c +++ b/drivers/net/wireless/libertas/wext.c @@ -995,8 +995,14 @@ static int wlan_set_rate(struct net_device *dev, struct iw_request_info *info, adapter->auto_rate = 0; } - ret = libertas_prepare_and_send_command(priv, CMD_802_11_DATA_RATE, - action, CMD_OPTION_WAITFORRSP, 0, NULL); + /* Try the newer command first (Firmware Spec 5.1 and above) */ + ret = libertas_prepare_and_send_command(priv, CMD_802_11_RATE_ADAPT_RATESET, + CMD_ACT_SET, CMD_OPTION_WAITFORRSP, 0, NULL); + + /* Fallback to older version */ + if (ret) + ret = libertas_prepare_and_send_command(priv, CMD_802_11_DATA_RATE, + action, CMD_OPTION_WAITFORRSP, 0, NULL); out: lbs_deb_leave_args(LBS_DEB_WEXT, "ret %d", ret); @@ -2010,6 +2016,139 @@ void libertas_get_fwversion(wlan_adapter * adapter, char *fwversion, int maxlen) snprintf(fwversion, maxlen, fwver); } +static const iw_handler wlan_private_handler[] = { + NULL, /* SIOCIWFIRSTPRIV */ +}; + +static const struct iw_priv_args wlan_private_args[] = { + /* + * { cmd, set_args, get_args, name } + */ + /* Using iwpriv sub-command feature */ + { + WLAN_SETONEINT_GETNONE, /* IOCTL: 24 */ + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + ""}, + { + WLANSETREGION, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setregioncode"}, + { + WLAN_SUBCMD_MESH_SET_TTL, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "mesh_set_ttl"}, + { + WLAN_SETNONE_GETONEINT, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + ""}, + { + WLANGETREGION, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getregioncode"}, + { + WLAN_SUBCMD_FWT_CLEANUP, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "fwt_cleanup"}, + { + WLAN_SUBCMD_FWT_TIME, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "fwt_time"}, + { + WLAN_SUBCMD_MESH_GET_TTL, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "mesh_get_ttl"}, + { + WLAN_SETNONE_GETNONE, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + ""}, + { + WLAN_SUBCMD_FWT_RESET, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "fwt_reset"}, + { + WLAN_SUBCMD_BT_RESET, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "bt_reset"}, + { + WLAN_SET128CHAR_GET128CHAR, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + ""}, + /* BT Management */ + { + WLAN_SUBCMD_BT_ADD, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "bt_add"}, + { + WLAN_SUBCMD_BT_DEL, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "bt_del"}, + { + WLAN_SUBCMD_BT_LIST, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "bt_list"}, + /* FWT Management */ + { + WLAN_SUBCMD_FWT_ADD, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "fwt_add"}, + { + WLAN_SUBCMD_FWT_DEL, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "fwt_del"}, + { + WLAN_SUBCMD_FWT_LOOKUP, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "fwt_lookup"}, + { + WLAN_SUBCMD_FWT_LIST_NEIGHBOR, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "fwt_list_neigh"}, + { + WLAN_SUBCMD_FWT_LIST, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "fwt_list"}, + { + WLAN_SUBCMD_FWT_LIST_ROUTE, + IW_PRIV_TYPE_CHAR | 128, + IW_PRIV_TYPE_CHAR | 128, + "fwt_list_route"}, + { + WLAN_SET_GET_SIXTEEN_INT, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + ""}, + { + WLAN_LED_GPIO_CTRL, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "ledgpio"}, + { + WLAN_INACTIVITY_TIMEOUT, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "inactivityto"}, +}; + /* * iwconfig settable callbacks @@ -2131,12 +2270,20 @@ static const iw_handler mesh_wlan_handler[] = { }; struct iw_handler_def libertas_handler_def = { .num_standard = ARRAY_SIZE(wlan_handler), + .num_private = ARRAY_SIZE(wlan_private_handler), + .num_private_args = ARRAY_SIZE(wlan_private_args), .standard = (iw_handler *) wlan_handler, + .private = (iw_handler *) wlan_private_handler, + .private_args = (struct iw_priv_args *)wlan_private_args, .get_wireless_stats = wlan_get_wireless_stats, }; struct iw_handler_def mesh_handler_def = { .num_standard = ARRAY_SIZE(mesh_wlan_handler), + .num_private = ARRAY_SIZE(wlan_private_handler), + .num_private_args = ARRAY_SIZE(wlan_private_args), .standard = (iw_handler *) mesh_wlan_handler, + .private = (iw_handler *) wlan_private_handler, + .private_args = (struct iw_priv_args *)wlan_private_args, .get_wireless_stats = wlan_get_wireless_stats, }; diff --git a/drivers/net/wireless/libertas/wext.h b/drivers/net/wireless/libertas/wext.h index 6aa444c..6a8c1d4 100644 --- a/drivers/net/wireless/libertas/wext.h +++ b/drivers/net/wireless/libertas/wext.h @@ -4,6 +4,47 @@ #ifndef _WLAN_WEXT_H_ #define _WLAN_WEXT_H_ +#define SUBCMD_OFFSET 4 +#define SUBCMD_DATA(x) *((int *)(x->u.name + SUBCMD_OFFSET)) + +/** PRIVATE CMD ID */ +#define WLANIOCTL SIOCIWFIRSTPRIV + +#define WLAN_SETNONE_GETNONE (WLANIOCTL + 8) +#define WLAN_SUBCMD_BT_RESET 13 +#define WLAN_SUBCMD_FWT_RESET 14 + +#define WLAN_SETNONE_GETONEINT (WLANIOCTL + 15) +#define WLANGETREGION 1 + +#define WLAN_SUBCMD_FWT_CLEANUP 15 +#define WLAN_SUBCMD_FWT_TIME 16 +#define WLAN_SUBCMD_MESH_GET_TTL 17 + +#define WLAN_SETONEINT_GETNONE (WLANIOCTL + 24) +#define WLANSETREGION 8 +#define WLAN_SUBCMD_MESH_SET_TTL 18 + +#define WLAN_SET128CHAR_GET128CHAR (WLANIOCTL + 25) +#define WLAN_SUBCMD_BT_ADD 18 +#define WLAN_SUBCMD_BT_DEL 19 +#define WLAN_SUBCMD_BT_LIST 20 +#define WLAN_SUBCMD_FWT_ADD 21 +#define WLAN_SUBCMD_FWT_DEL 22 +#define WLAN_SUBCMD_FWT_LOOKUP 23 +#define WLAN_SUBCMD_FWT_LIST_NEIGHBOR 24 +#define WLAN_SUBCMD_FWT_LIST 25 +#define WLAN_SUBCMD_FWT_LIST_ROUTE 26 + +#define WLAN_SET_GET_SIXTEEN_INT (WLANIOCTL + 29) +#define WLAN_LED_GPIO_CTRL 5 +#define WLAN_INACTIVITY_TIMEOUT 9 + +#define WLAN_LINKMODE_802_3 0 +#define WLAN_LINKMODE_802_11 2 +#define WLAN_RADIOMODE_NONE 0 +#define WLAN_RADIOMODE_RADIOTAP 2 + /** wlan_ioctl_regrdwr */ struct wlan_ioctl_regrdwr { /** Which register to access */ diff --git a/drivers/net/wireless/marvell8686/Makefile b/drivers/net/wireless/marvell8686/Makefile new file mode 100644 index 0000000..e443661 --- /dev/null +++ b/drivers/net/wireless/marvell8686/Makefile @@ -0,0 +1,15 @@ +ifeq ($(CONFIG_MARVELL_8686_PROC_FS),y) + sd8686-objs += wlan_proc.o +endif + +ifeq ($(CONFIG_MARVELL_8686_DEBUG),y) + sd8686-objs += wlan_debug.o +endif + +sd8686-objs += wlan_main.o wlan_fw.o \ + wlan_wext.o wlan_rx.o wlan_tx.o \ + wlan_cmd.o wlan_cmdresp.o wlan_scan.o \ + wlan_join.o wlan_wmm.o wlan_11d.o wlan_fops.o \ + if_sdio.o +obj-$(CONFIG_MARVELL_8686_SDIO) += sd8686.o + diff --git a/drivers/net/wireless/marvell8686/README b/drivers/net/wireless/marvell8686/README new file mode 100644 index 0000000..5dca07e --- /dev/null +++ b/drivers/net/wireless/marvell8686/README @@ -0,0 +1,1587 @@ +=============================================================================== + U S E R M A N U A L + +1) FOR DRIVER BUILD + + Goto source code directory src_xxxx. + make [clean] build + The driver and utility binaries can be found in ../bin_xxxx directory. + +2) FOR DRIVER INSTALL + + a) Copy helper_sd.bin and sd8xxx.bin to /lib/firmware/mrvl/ directory, + create the directory if it doesn't exist. + b) insmod sdio.o (or sdio.ko) [clkrate=5|10|20] [intmode=0|1] [gpiopin=n] + The default SDIO clock frequency is 20 MHz. It can be changed to 10 MHz or 5 MHz. + intmode -- 0 for SDIO (default) + intmode -- 1 for GPIO, gpiopin for GPIO pin# has to be specified in this mode. + c) insmod sd8xxx.o (or sd8xxx.ko) helper_name=/lib/firmware/mrvl/helper_sd.bin \ + fw_name=/lib/firmware/mrvl/sd8xxx.bin + + To install SD8xxx Driver with user-specified helper file /lib/firmware/mrvl/helper_sd.bin and + MFG firmware file /lib/firmware/mrvl/sd8xxxmfg.bin, using the following command: + insmod sdio.o (or sdio.ko) + insmod sd8xxx.o (or sd8xxx.ko) mfgmode=1 helper_name=/lib/firmware/mrvl/helper_sd.bin \ + fw_name=/lib/firmware/mrvl/sd8xxxmfg.bin + +3) FOR DRIVER PROC & DEBUG + + The following info are provided in /proc/net/wlan/info, + + driver_name = "wlan" + driver_version = + InterfaceName = "ethX" + Mode = "Ad-hoc" | "Managed" | "Auto" | "Unknown" + State = "Disconnected" | "Connected" + MACAddress = <6-byte adapter MAC address> + MCCount = + ESSID = + Channel = + region_code = + MCAddr[n] = + num_tx_bytes = + num_rx_bytes = + num_tx_pkts = + num_rx_pkts = + num_tx_pkts_dropped = + num_rx_pkts_dropped = + num_tx_pkts_err = + num_rx_pkts_err = + carrier "on" | "off" + tx queue "stopped" | "started" + CurCmd "NULL" | + + The following debug info are provided in /proc/net/wlan/debug, + + IntCounter = + ConnectStatus = <0/1, disconnected/connected> + wmmQStp = <0/1, WMM queue started/stopped> + wmmPkts = + wmmAcVo = + wmmAcVi = + wmmAcBE = + wmmAcBK = + PSMode = <0/1, CAM mode/PS mode> + PSState = <0/1/2/3, full power state/awake state/pre-sleep state/sleep state> + IsDeepSleep = <0/1, not deep sleep state/deep sleep state> + IsAutoDeepSleepEnabled = <0/1, not auto deep sleep mode/auto deep sleep mode> + WakeupDevReq = <0/1, wakeup device not required/required> + WakeupTries = + HS_Configured = <0/1, host sleep not configured/configured> + HS_Activated = <0/1, extended host sleep not activated/activated> + num_tx_timeout = + num_cmd_timeout = + TimeoutCmdId = + TimeoutCmdAct = + LastCmdId = + LastCmdAct = + LastCmdIndex = <0 based last command index> + LastCmdRespId = + LastCmdRespIndex = <0 based last command response index> + LastEvent = + LastEventIndex = <0 based last event index> + num_cmd_h2c_fail = + num_cmd_sleep_cfm_fail = + num_tx_h2c_fail = + num_evt_deauth = + num_evt_disassoc = + num_evt_link_lost = + num_cmd_deauth = + num_cmd_assoc_ok = + num_cmd_assoc_fail = + dnld_sent = <0/1/2, send resources available/sending data to device/sending command to device> + + Use dmesg or cat /var/log/debug to check driver debug messages. + + Update /proc/sys/kernel/printk to change message log levels. + For example, + echo 6 > /proc/sys/kernel/printk (messages with a higher priority than 6 + will be printed to the console) + echo 15 > /proc/sys/kernel/printk (all messages will be printed to console) + +4) FOR IWPRIV COMMAND + +NAME + This manual describes the usage of private commands used in Marvell WLAN + Linux Driver. All the commands available in Wlanconfig will not be available + in the iwpriv. + + To use parameters as hex format, a'0x' must precede it for the parameters to + be parsed properly. + +SYNOPSIS + iwpriv [sub-command] ... + + iwpriv ethX version + iwpriv ethX verext + iwpriv ethX scantype [sub-command] + iwpriv ethX getSNR + iwpriv ethX getNF + iwpriv ethX getRSSI + iwpriv ethX setrxant + iwpriv ethX getrxant + iwpriv ethX settxant + iwpriv ethX gettxant + iwpriv ethX authalgs + iwpriv ethX encryptionmode + iwpriv ethX setregioncode + iwpriv ethX getregioncode + iwpriv ethX setbcnavg + iwpriv ethX getbcnavg + iwpriv ethX setdataavg + iwpriv ethX getdataavg + iwpriv ethX setlisteninter + iwpriv ethX getlisteninter + iwpriv ethX setmultipledtim + iwpriv ethX getmultipledtim + iwpriv ethX atimwindow + iwpriv ethX deepsleep + iwpriv ethX autodeepsleep + iwpriv ethX hscfg + iwpriv ethX hssetpara + iwpriv ethX deauth + iwpriv ethX adhocstop + iwpriv ethX radioon + iwpriv ethX radiooff + iwpriv ethX reasso-on + iwpriv ethX reasso-off + iwpriv ethX scanmode [sub-command] + iwpriv ethX setwpaie + iwpriv ethX setaeskey + iwpriv ethX getaeskey + iwpriv ethX rmaeskey + iwpriv ethX getcis + iwpriv ethX getlog + iwpriv ethX getadhocstatus + iwpriv ethX adhocgrate + +Version 4 Command: + iwpriv ethX inactivityto + iwpriv ethX sleeppd + iwpriv ethX enable11d + iwpriv ethX bgscan + iwpriv ethX wmm + iwpriv ethX tpccfg + iwpriv ethX sdioclock + +Version 5 Command: + iwpriv ethX ledgpio + iwpriv ethX wmm_qosinfo + iwpriv ethX scanprobes + iwpriv ethX lolisteninter + iwpriv ethX rateadapt + iwpriv ethX fwwakeupmethod + iwpriv ethX txcontrol + iwpriv ethX uapsdnullgen + iwpriv ethX psnullinterval + iwpriv ethX getrxinfo + iwpriv ethX gettxrate + iwpriv ethX bcninterval [n] + iwpriv ethX setcoalescing + iwpriv ethX bcnmisto + iwpriv ethX adhocawakepd + iwpriv ethX sdiopullctrl + iwpriv ethX scantime [s] [a] [p] + iwpriv ethX ldocfg [n] + iwpriv ethX dataevtcfg [a] [b] [c] [d] [e] [f] [g] [h] [i] + iwpriv ethX sdiomode [n] + iwpriv ethX rtsctsctrl [n] + iwpriv ethX drvdbg [n] [m] + iwpriv ethX adhocgprot [n] + iwpriv ethX getrate + iwpriv ethX associate + iwpriv ethX getdtim + +DESCRIPTION + Those commands are used to send additional commands to the Marvell WLAN + card via the Linux device driver. + + The ethX parameter specifies the network device that is to be used to + perform this command on. it could be eth0, eth1 etc. + +version + This is used to get the current version of the driver and the firmware. + +verext + Retrieve and display an extended version string from the firmware + + Usage: + iwpriv ethX verext [#] + + where [#] is an optional argument to retrieve a specific version string, + omission of the argument retrieves the 0 indexed string + +scantype + This command is used to set the scan type to be used by the driver in + the scan command. This setting will not be used while performing a scan + for a specific SSID, as it is always done with scan type being active. + + where the sub-commands are: - + active -- to set the scan type to active + passive -- to set the scan type to passive + get -- to get the scan type set in the driver + +getSNR + This command gets the average and non average value of Signal to Noise + Ratio of Beacon and Data. + + where value is: + 0 -- Beacon non-average. + 1 -- Beacon average. + 2 -- Data non-average. + 3 -- Data average. + + If no value is given, all four values are returned in the order mentioned + above. + + Note: This command is available only when STA is connected. + +getRSSI + This command gets the average and non average value os Receive Signal + Strength of Beacon and Data. + + where value is: + 0 -- Beacon non-average. + 1 -- Beacon average. + 2 -- Data non-average. + 3 -- Data average. + + Note: This command is available only when STA is connected. + +getNF + This command gets the average and non average value of Noise Floor of + Beacon and Data. + + where value is: + 0 -- Beacon non-average. + 1 -- Beacon average. + 2 -- Data non-average. + 3 -- Data average. + + Note: This command is available only when STA is connected. + +setrxant + This command is used to set the mode for Rx antenna. + + The options that can be sent are:- + 1 -- Antenna 1. + 2 -- Antenna 2. + 0xFFFF -- Diversity. + + Usage: + iwpriv ethX setrxant 0x01: select Antenna 1. + +getrxant + This command is used to get the mode for Rx antenna. + +settxant + This command is used to set the mode for Tx antenna. + The options that can be sent are:- + 1 -- Antenna 1. + 2 -- Antenna 2. + 0xFFFF -- Diversity. + Usage: + iwpriv ethX settxant 0x01: select Antenna 1. + +gettxant + This command is used to get the mode for Tx antenna. + +authalgs + This command is used by the WPA supplicant to set the authentication + algorithms in the station. + +encryptionmode + This command is used by the WPA supplicant to set the encryption algorithm. + + where values can be:- + 0 -- NONE + 1 -- WEP40 + 2 -- TKIP + 3 -- CCMP + 4 -- WEP104 + +setregioncode + This command is used to set the region code in the station. + where value is 'region code' for various regions like + USA FCC, Canada IC, France, Europe ETSI, Japan ... + + Usage: + iwpriv ethX setregioncode 0x10: set region code to USA (0x10). + +getregioncode + This command is used to get the region code information set in the + station. + +setbcnavg + Set the weighting factor for calculating beacon average RSSI and SNR. + where value can be: + 0 -- default beacon averaging factor (8) + 1-8 -- beacon averaging factor + Usage: + iwpriv ethX setbcnavg 0 + iwpriv ethX setbcnavg 8 + +getbcnavg + Get the weighting factor for calculating beacon average RSSI and SNR. + Usage: + iwpriv ethX getbcnavg + +setdataavg + Set the weighting factor for calculating data average RSSI and SNR. + where value can be: + 0 -- default data averaging factor (8) + 1-8 -- data averaging factor + Usage: + iwpriv ethX setdataavg 0 + iwpriv ethX setdataavg 8 + +getdataavg + Get the weighting factor for calculating data average RSSI and SNR. + Usage: + iwpriv ethX getdataavg + +setlisteninter + This command is used to set the listen interval in the + station. + + where the value ranges between 1 - 255 + +getlisteninter + This command is used to get the listen interval value set in the + station. + +setmultipledtim + This command is used to set the multiple dtim value in the + station. + where the value is 1,2,3,4,5,0xfffe + 65534 (0xfffe) means that the dtim will be ignored in firmware, + listen interval or local listen interval will be used. + +getmultipledtim + This command is used to get the multiple dtim value set in the station. + +atimwindow + This command is used to set atim value in the station when an argument is given, + return the atim value set by the user and the current atim value if adapter is in connected state. + The valid atimwindow is between 0 - 50. + + Usage: + iwpriv ethX atimwindow 0 (set atimwindow to 0) + + not connected: + iwpriv ethX atimwindow (get atimwindow value set by user) + + connected: + iwpriv ethX atimwindow (get atimwindow set by user previously + and current atimwindow) + +deauth + This command is used to send the de-authentication to the AP with which + the station is associated. This command is valid only when + station is in Infrastructure mode. + + Note: This command is available only when STA is connected. + +reasso-on + This command is used to enable re-association function in dirver. + +reasso-off + This command is used to disable re-association function in driver + +adhocstop + This command is used to stop beacon transmission from the station and + go into idle state in ad-hoc mode. + + Note: This command is available only when STA is connected. + +radioon + This command is used to turn on the RF antenna. + +radiooff + This command is sued to turn off the RF antenna. + +scanmode + This command is used to set the station to scan for either IBSS + networks or BSS networks or both BSS and IBSS networks. This + command can be used with sub commands, + + where the value for + bss -- Scan All the BSS networks. + ibss -- Scan All the IBSS networks. + any -- Scan both BSS and IBSS networks. + +deepsleep + This command is used to configure the station in deepsleep mode. + + where the option is: + 1 -- Enable deepsleep mode + 0 -- Disable deepsleep mode + 2 -- Display deepsleep setting + Usage: + iwpriv ethX deepsleep 1 + Enable deepsleep mode + iwpriv ethX deepsleep 0 + Disable deepsleep mode + iwpriv ethX deepsleep 2 + Display deepsleep setting + +autodeepsleep + This command is used to configure the station in auto deepsleep mode. + + where the option is: + 1 -- Enable auto deepsleep mode + 0 -- Disable auto deepsleep mode + Usage: + iwpriv ethX autodeepsleep + Read the current auto deepsleep setting + iwpriv ethX autodeepsleep 1 + Enable auto deepsleep mode + iwpriv ethX autodeepsleep 0 + Disable auto deepsleep mode + +hscfg + This command is used to configure the host sleep parameters. + + iwpriv ethX hscfg Condition [GPIO# [Gap]] + + Note: + + 1) This command takes one (Condition) or two (Condition and GPIO#) or three (Condition, GPIO# and gap) + parameters. + + where Condition is: + bit 0 = 1 -- broadcast data + bit 1 = 1 -- unicast data + bit 2 = 1 -- mac event + bit 3 = 1 -- multicast packet + + where GPIO is the pin number of GPIO used to wakeup the host. It could be any valid + GPIO pin# (e.g. 0-7) or 0xff (Interface, e.g. SDIO will be used instead). + + where Gap is the gap in milli seconds between wakeup signal and wakeup event + or 0xff for special setting. + + 2) the Host Sleep mode will be cancelled if condition is set to -1. + + 3) Usage: + iwpriv eth1 hscfg -1 # cancel host sleep mode + + iwpriv eth1 hscfg 3 # broadcast and unicast data + # use GPIO and GAP set previously + + iwpriv eth1 hscfg 2 0x3 # unicast data + # use GPIO 3 + # use GAP set previously + + iwpriv eth1 hscfg 2 1 0xa0 # unicast data + # use GPIO 1 + # gap: 160 ms + + iwpriv eth1 hscfg 2 0xff # unicast data + # use Interface (e.g. SDIO) + # use GAP set previously + + iwpriv eth1 hscfg 0x2 0x3 0xff # unicast data + # use GPIO 3 + # special host sleep mode + + iwpriv eth1 hscfg 0x2 0xff 0xff # unicast data + # use Interface (e.g. SDIO) + # special host sleep mode + +hssetpara + This command is used to set host sleep parameters. + + iwpriv ethX hssetpara Condition [GPIO# [Gap]] + + Note: + 1) The usages of parameters are the same as "hscfg" command. + 2) The parameters will be saved in the driver and be usded when host suspends. + +setwpaie + This command is used by WPA supplicant to send the WPA-IE to the driver. + +setaeskey + This command is used to set the AES key, when the station is in Ad-hoc + mode. + + where value can be any 16 byte value. + + Usage: + iwpriv ethX setaeskey 12345678901234567890123456789012 + +getaeskey + This command is used to get the AES key, when the station is in Ad-hoc + mode. + +rmaeskey + This command is used to remove the Ad-Hoc AES key that is previously set. + It will disable ad-hoc AES as well. + +getcis + This command is used to read the Card Info Structure Table. + +getlog + This command is used to get the 802.11 statistics available in the + station. + + Note: This command is available only when STA is connected. + +getadhocstatus + This command is used to get the ad-hoc Network Status. + + The various status codes are: + AdhocStarted + AdhocJoined + AdhocIdle + InfraMode + AutoUnknownMode + + Note: This command is available only when STA is connected. + +adhocgrate + This command is used to enable(1) g_rate, Disable(0) g_rate + and request(2) the status which g_rate is disabled/enabled, + for Ad-hoc creator. + + where value is: + 0 -- Disable + 1 -- Enable + 2 -- Get + +ledgpio + This command is used to set/get LED settings. + + iwpriv ethX ledgpio + will set the corresponding LED for the GPIO Line. + + iwpriv ethX ledgpio + will get the current LEDs settings. + + Usage: + iwpriv eth1 ledgpio 1 0 2 1 3 16 + LED 1 -> GPIO 0 + LED 2 -> GPIO 1 + LED 3 -> disable + + iwpriv eth1 ledgpio + shows LED information in the format as mentioned above. + + Note: LED 0 is invalid + Maximum Number of LEDs are 3. + +inactivityto + This command is used by the host to set/get the inactivity timeout value, + which specifies when WLAN device is put to sleep. + + Usage: + iwpriv ethX inactivityto [] + + where the parameter are: + timeout: timeout value in milliseconds. + + Example: + iwpriv eth1 inactivityto + "get the timeout value" + + iwpriv eth1 inactivityto X + "set timeout value to X ms" + +sleeppd + This command is used to configure the sleep period of the WLAN device. + + Usage: + iwpriv ethX sleeppd [] + + where the parameter are: + Period: sleep period in milliseconds. Range 10~60. + + Example: + iwpriv eth1 sleeppd 10 + "set period as 10 ms" + iwpriv eth1 sleeppd + "get the sleep period configuration" + +enable11d + This command is used to control 11d + where value is: + 1 -- Enable + 0 -- Disable + 2 -- Get + +wmm + This command is used to control WMM + + where value is: + 0 -- Disable + 1 -- Enable + 2 -- Get + + +bgscan + Enables or disables the Background scan. + + The configuration for bg scan must be set using wlanconfig + + Usage: + wlanconfig ethX bgscanconfig bg_scan_config.conf + iwpriv ethX bgscan 0 (disable) + iwpriv ethX bgscan 1 (enable) + iwpriv ethX bgscan 2 (display enable or disable) + +tpccfg + Enables or disables automatic transmit power control. + + The first parameter turns this feature on (1) or off (0). When turning + on, the user must also supply four more parameters in the following + order: + -UseSNR (Use SNR (in addition to PER) for TPC algorithm), + -P0 (P0 power level for TPC), + -P1 (P1 power level for TPC), + -P2 (P2 power level for TPC). + + Usage: + iwpriv ethX tpccfg: Get current configuration + iwpriv ethX tpccfg 0: disable auto TPC + iwpriv ethX tpccfg 0x01 0x00 0x05 0x0a 0x0d: enable auto TPC; do not use SNR; + P0=0x05; P1=0x0a; P2=0x0d; + iwpriv ethX tpccfg 0x01 0x01 0x05 0x0a 0x0d: enable auto TPC; use SNR; + P0=0x05; P1=0x0a; P2=0x0d. + +sdioclock + Turn On(1) or Off(0) the SDIO clock. + + Usage: + iwpriv ethX sdioclock 1 (on) + iwpriv ethX sdioclock 0 (off) + +wmm_qosinfo + This command sets WMM IE QOS info when an argument is given, and gets current WMM + IE QOS info when no argument is given. + + Usage: + iwpriv ethX wmm_qosinfo 0x0f (set WMM IE QOS info to 0x0f) + iwpriv ethX wmm_qosinfo (get WMM IE QOS info) + +scanprobes + This command sets number of probe requests per channel. + + Usage: + iwpriv ethX scanprobes 3 (set scan probes to 3) + iwpriv ethX scanprobes (get scan probes) + +lolisteninter + This command sets the value of listen interval. + + Usage: + iwpriv ethX lolisteninter 234 (set the lolisteninter to 234) + iwpriv ethX lolisteninter (get the lolisteninter value) + +rateadapt + This command sets the data rates bitmap. + Where + 0: no HW rate drop + 1: HW table rate drop + 2: HW single rate drop + + data rate bitmap + Bit Data rate + 0 1 Mbps + 1 2 Mbps + 2 5.5 Mbps + 3 11 Mbps + 4 Reserved + 5 6 Mbps + 6 9 Mbps + 7 12 Mbps + 8 18 Mbps + 9 24 Mbps + 10 36 Mbps + 11 48 Mbps + 12 54 Mbps + 12-15 Reserved + Threshold, Number of same rate retries before switch to Final rate. + Used only if HW single rate drop is selected. Typical values are from 3 to 6. + + Final Rate, This value is used only if HW single rate drop is selected. + value Data rate + 0 1 Mbps + 1 2 Mbps + 2 5.5 Mbps + 3 11 Mbps + 4 Reserved + 5 6 Mbps + 6 9 Mbps + 7 12 Mbps + 8 18 Mbps + 9 24 Mbps + 10 36 Mbps + 11 48 Mbps + 12 54 Mbps + 13-15 Reserved + Usage: + iwpriv ethX rateadapt + read the currect data rate setting + iwpriv ethX rateadapt 1 0x07 + enable hardware auto data rate adapt and + data rates are 1Mbps, 2Mbsp and 5.5Mbps + iwpriv ethX rateadapt 2 0x0f 6 2 + use HW single rate drop, data rates are 1Mbps, 2Mbsp and 5.5Mbps, 11Mbps + Threshold is 6, Final Rate is 5.5 Mbps + +fwwakeupmethod + This command is used to set the firmware wakeup method. + + where value is: + 0 -- Leave the current method to wakeup firmware unchanged + 1 -- Firmware wakeup through the interface command interrupt + -- (default setting for SDIO/GSPI) + 2 -- Firmware wakeup through the GPIO pin + -- (default setting for CF) + + Usage: + iwpriv ethX fwwakeupmethod + Read the current firmware wakeup method setting + iwpriv ethX fwwakeupmethod 0 + Leave the current method to wakeup firmware unchanged + iwpriv ethX fwwakeupmethod 1 + Firmware wakeup through the interface command interrupt + iwpriv ethX fwwakeupmethod 2 + Firmware wakeup through the GPIO pin + +txcontrol [DefaultFlags (if no UP setting)] [UserPriority] [UserPriorityFlags] + This command is used to set/get the TX rate, WMM ack policy, and + retry limit for all packets, or selectively the packets with a specific + user priority. + + The DefaultFlags setting is ignored for any command with 2 or more + arguments. + + The value of the u32 txcontrol flags returned and input for + DefaultFlags or UserPriorityFlags specific settings is given by the + following bitmap: + + bit[4:0], if bit[4] == 1: + bit[3:0] -- 0 1 2 3 4 5 6 7 8 9 10 11 12 13-16 + Data Rate(Mbps) -- 1 2 5.5 11 Rsv 6 9 12 18 24 36 48 54 Rsv + + bit[12:8] + if bit[12] == 1, bit[11:8] specifies the Tx retry limit. + + bit[14:13] specifies per packet ack policy: + if bit[14] == 1, bit[13] == 1 specifies No Ack Policy + + All unused and reserved bits should be set to zero for the entire + u32 field. + + Usage: + Number of arguments given: + + 0: Return the default setting for the txcontrol flags + > iwpriv eth1 txcontrol + + 1: Set the default value for the txcontrol flags + > iwpriv eth1 txcontrol 0x6013 - No ACK, 11Mbps + > iwpriv eth1 txcontrol 0x151A - 5 retries, 36Mbps + + 2: Return a specific User Priority setting. If the UP setting is zero, + the default value will be used and returned: + > iwpriv eth1 txcontrol 0 7 - Return UP 7 txcontrol value (UP7 = VO) + + 3: Set a User Priority specific value for the txcontrol flags. A value + of zero will revert to the default setting: + > iwpriv eth1 txcontrol 0 5 0x6013 - (UP5 = VI), No ACK 11Mbps. + +uapsdnullgen + This command is used to enable(1) UAPSD null package generation, + Disable(0) UAPSD null package generation, and request(2) the status + which null package generation is disabled/enabled, + for Ad-hoc creator. + + where value is: + 0 -- Disable + 1 -- Enable + 2 -- Get + +psnullinterval + This command is used to set/request NULL package interval for Power Save + under infrastructure mode. + + where value is: + -1 -- Disable + n>0 -- Set interval as n (seconds) + +getrxinfo + This command gets non average value of Signal to Noise Ratio of Data and rate index. + + The following table shows RateIndex and Rate + + RateIndex Data rate + 0 1 Mbps + 1 2 Mbps + 2 5.5 Mbps + 3 11 Mbps + 4 Reserved + 5 6 Mbps + 6 9 Mbps + 7 12 Mbps + 8 18 Mbps + 9 24 Mbps + 10 36 Mbps + 11 48 Mbps + 12 54 Mbps + 13-15 Reserved + +gettxrate + This command gets current Tx rate index of the first packet associated with Rate Adaptation. + + The following table shows RateIndex and Rate + + RateIndex Data rate + 0 1 Mbps + 1 2 Mbps + 2 5.5 Mbps + 3 11 Mbps + 4 Reserved + 5 6 Mbps + 6 9 Mbps + 7 12 Mbps + 8 18 Mbps + 9 24 Mbps + 10 36 Mbps + 11 48 Mbps + 12 54 Mbps + 13-15 Reserved + +bcninterval + This command is used to set beacon interval in adhoc mode when an argument is given, + return the value set by the user and the current adhoc beacon interval if adapter is in connected state. + The valid beacon interval is between 20 - 1000, default beacon interval is 100. + + Usage: + iwpriv ethX bcninterval 100 (set adhoc beacon interval to 100) + + not connected: + iwpriv ethX bcninterval (get adhoc beacon interval set by user) + + connected: + iwpriv ethX bcninterval (get adhoc beacon interval set by user previously + and current beacon interval) + +setcoalescing + This command is used to disable/enable IBSS coalescing function, and get IBSS coalescing status. + + where value is: + 0 -- Disable IBSS coalescing function + 1 -- Enable IBSS coalescing function + 2 -- Get current IBSS coalescing status + +bcnmisto + This command is used to set/get beacon miss timeout for Power Save + under infrastructure mode. + + where value is: + 0xffff -- Disabled + 0 -- no change + 1--50 (miliseconds) + +adhocawakepd + This command is used to set/get adhoc awake period for Power Save Mode. + + where value is: + 0xff -- firmware will go to sleep after send out beacon + 0--no change + 1--31 (beacon interval) + +sdiopullctrl + This command is used to set/get seting of pulling up and pulling down of SDIO lines, + The PullUp is the delay before pulling SDIO lines up. The PullDown is the + delay before pull SDIO lines down. the unit is us for both PullUp and PullDown. + + where PullUp + 0--no delay is needed + 0xffff--no pulling up is needed + + PullDown + 0--no delay is needed + 0xffff--no pulling up is needed + + Usage: + iwpriv ethX sdiopullctrl + Read the currect firmware SDIO PullUp and PullDown settings + iwpriv ethX sdiopullctrl 5 5 + Set SDIO PullUp/PullDown to 5 us + iwpriv ethX sdiopullctrl 0xffff 0xffff + Disable SDIO PullUp and PullDown + +scantime + This command is used to set/get scan time per channel in milliseconds. + The current setting will be returned every time. + + Usage: + iwpriv ethX scantime [s] [a] [p] + + where the parameters are, + [s]: specific SSID/BSSID scan time, default 100 ms, max 500 ms + [a]: active scan time, default 100 ms, max 500 ms + [p]: passive scan time, default 100 ms, max 2000 ms + No change if the parameter is 0 or the parameter is not provided. + + For example: + iwpriv ethX scantime 30 (set specific scan time to 30 ms) + iwpriv ethX scantime 0 100 (set active scan time to 100 ms) + iwpriv ethX scantime 30 80 200 (set specific scan time to 30 ms, + set active scan time to 80 ms, + set passive scan time to 200 ms) + +ldocfg + This command is used to set/get internal/external core power voltage source. + By default firmware uses internal LDO for 1.2V core power supply. + The current setting will be returned if no parameter provided. + + Usage: + iwpriv ethX ldocfg [n] + + where the parameter is, + 0 -- internal + 1 -- external + +dataevtcfg + This command is used to set/get the subscription of low RSSI, low SNR, high RSSI and + high SNR events in data packet. + + Where value is: + Events bitmap + Bit + 0 RSSI_LOW RSSI_LOW event is generated when avg data RSSI is below threshold + 1 SNR_LOW SNR_LOW event is generated when avg data SNR is below threshold + 2 RSSI_HIGH RSSI_HIGH event is generated when avg data RSSI go above threshold + 3 SNR_HIGH SNR_HIGH event is generated when avg data SNR go above threshold + + Where value is: + RSSI_LOW threshold (Absolute value, the actual value should be negative) + Where value is: + RSSI_LOW reporting frequency. if set to 0, event will only report once. + if set to 1, event will be reported every time it occur. + if set to N, event will be reported only when the condition happens N consecutive times. + + Where value is: + SNR_LOW threshold + Where value is: + SNR_LOW reporting frequency. if set to 0, event will only report once. + if set to 1, event will be reported every time it occur. + if set to N, event will be reported only when the condition happens N consecutive times. + + Where value is: + RSSI_HIGH threshold (Absolute value, the actual value should be negative) + Where value is: + RSSI_HIGH reporting frequency. if set to 0, event will only report once. + if set to 1, event will be reported every time it occur. + if set to N, event will be reported only when the condition happens N consecutive times. + + Where value is: + SNR_HIGH threshold + Where value is: + SNR_HIGH reporting frequency. if set to 0, event will only report once. + if set to 1, event will be reported every time it occur. + if set to N, event will be reported only when the condition happens N consecutive times. + + Usage: + iwpriv ethX dataevtcfg (get current data subscribe event settings) + iwpriv ethX dataevtcfg 15 70 1 56 0 40 5 86 0 + (set current data subscribe event, + when avg data RSSI below -70, such as -80, + RSSI_LOW event will be generated every time; + when avg data RSSI above -40 happens 5 consecutive times, + such as -30,-32,-34,-29,-33, RSSI_HIGH event will be generated 1 times; + when avg data SNR below 56 or above 86, + SNR_LOW or SNR_HIGHT event will be generated once) + +sdiomode + This command is used to set/get sdio mode. + The setting will take effect when next command 53 is issued. + + where the parameter is, + 1 -- set sdio to 1 bit mode + 4 -- set sdio to 4 bit mode + + Usage: + iwpriv ethX sdiomode (get current sdio mode) + iwpriv ethX sdiomode 1 (set to 1 bit mode) + iwpriv ethX sdiomode 4 (set to 4 bit mode) + +rtsctsctrl + This command is used to set/get the RTS/CTS or CTS To SELF for g rate protection to the firmware. + where the parameter is, + 1 -- set RTS/CTS for g rate protection to the firmware. + 0 -- set CTS To SELF for g rate protection to the firmware. + + Usage: + iwpriv ethX rtsctsctrl (gets the RTS/CTS or CTS To SELF) + iwpriv ethX rtsctsctrl 1 (set RTS/CTS for g rate protection to the firmware.) + iwpriv ethX rtsctsctrl 0 (set CTS To SELF for g rate protection to the firmware.) + +drvdbg + This command is used to set/get the bit masks of driver debug message control. + + Usage: + iwpriv ethX drvdbg [n] [m] + + Where the parameter is the generic debug message control bit mask. + The following types of driver debug messages can be dynamically enabled or + disabled by setting or clearing the corresponding bits, + bit 0: MSG PRINTM(MSG,...) + bit 1: FATAL PRINTM(FATAL,...) + bit 2: ERROR PRINTM(ERROR,...) + bit 3: DATA PRINTM(DATA,...) + bit 4: CMND PRINTM(CMND,...) + bit 5: EVENT PRINTM(EVENT,...) + bit 6: INTR PRINTM(INTR,...) + ... + bit 16: DAT_D PRINTM(DAT_D,...), DBG_HEXDUMP(DAT_D,...) + bit 17: CMD_D PRINTM(CMD_D,...), DBG_HEXDUMP(CMD_D,...) + bit 18: FW_D PRINTM(FW_D,...) + ... + bit 28: ENTRY PRINTM(ENTRY,...), ENTER(), LEAVE() + bit 29: WARN PRINTM(WARN,...) + bit 30: INFO PRINTM(INFO,...) + + Where the parameter is the extended interface module debug message control + bit mask. The following types of debug messages can be controlled. + + bit 0: IF_D PRINTM(IF_D,...), DBG_HEXDUMP(IF_D,...) + + If CONFIG_DEBUG=2, all kinds of debug messages can be configured. + By default all debug messages are enabled except for EVENT and IF_D. + + If CONFIG_DEBUG=1, all kinds of debug messages can be configured except + for ENTRY, WARN and INFO. By default MSG and FATAL are enabled. + + Some special debug messages, + '*' // WLAN driver ISR is called (bit 6 INTR enabled) + '|' // PS awake event is received (bit 5 EVENT enabled) + '_' // PS sleep event is received (bit 5 EVENT enabled) + '+' // PS sleep confirm is sent (bit 5 EVENT enabled) + + For example: + iwpriv ethX drvdbg (get the current driver debug masks) + iwpriv ethX drvdbg 0 0 (disable all the debug messages) + iwpriv ethX drvdbg 7 (enable MSG, FATAL and ERROR messages, + no change for if debug control) + iwpriv ethX drvdbg 3 1 (enable MSG and FATAL messages, + enable IF_D message) + iwpriv ethX drvdbg -1 -1 (enable all the debug messages) + +adhocgprot + This command is used to set/get 802.11g ad-hoc protection state. + + where value is: + 0 -- Disable + 1 -- Enable + 2 -- Get + Usage: + iwpriv ethX adhocgprot 0 (disable 802.11g ad-hoc g protection) + iwpriv ethX adhocgprot 1 (enable 802.11g ad-hoc g protection) + iwpriv ethX adhocgprot 2 (get 802.11g ad-hoc g protection state) + +getrate + This command is used to get the supported rates. Returned rates are in 0.5M unit. + +associate + Associate to a specific BSS entry in the scan table. The enumeration + of the entries in the scan table is determined by the + getscantable API or from the iwlist scan results. A re-scan is not + performed before the association attempt is made. + +getdtim + This command is used to get the current DTIM value. + Note: This command is available only when STA is connected. + + +=============================================================================== + + U S E R M A N U A L F O R W L A N _ C O N F I G + +NAME +wlanconfig - configure the additional parameters available for the Marvell + WLAN Linux Driver. + +SYNOPSIS +wlanconfig -v +wlanconfig [parameters] ... + +wlanconfig ethX version +wlanconfig ethX +wlanconfig ethX +wlanconfig ethX sdcmd52r
+wlanconfig ethX sdcmd52w
+wlanconfig ethX caldataext +wlanconfig ethX rdeeprom +wlanconfig ethX sleepparams +wlanconfig ethX bca-ts +wlanconfig ethX extscan +wlanconfig ethX getscanlist + +Version 4 Command: +wlanconfig ethX bgscanconfig + +Version 5 Command: +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd + +Version 6 Command: +wlanconfig ethX setuserscan [ARGS] +wlanconfig ethX getscantable +wlanconfig ethX getassocrsp + +Version 8 +wlanconfig ethX addts +wlanconfig ethX delts +wlanconfig ethX qconfig set msdu [Queue Id: 0-3] +wlanconfig ethX qconfig get [Queue Id: 0-3] +wlanconfig ethX qconfig def [Queue Id: 0-3] +wlanconfig ethX qstats on [Queue Id: 0-3] +wlanconfig ethX qstats off [Queue Id: 0-3] +wlanconfig ethX qstats get [Queue Id: 0-3] +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd +wlanconfig ethX hostcmd +wlanconfig ethX hstest +wlanconfig ethX getcfptable [region] + +Version 9 +wlanconfig ethX gettsf +wlanconfig ethX arpfilter +wlanconfig ethX txpktstats + +DESCRIPTION + +those commands are used in Marvell specic applicaion called wlanconfig. + +=========== +-v + This command is used to display the version of wlanconfig utility. + Usage: + wlanconfig -v + +rdmac +rdbbp +rdrf + These commands are used to read the MAC, BBP and RF registers from the + card. These commands take one parameter that specifies the offset + location that is to be read. This parameter can be specified either in + decimal or in hexadecimal (by preceding the number with a "0x"). + + Usage: + wlanconfig ethX rdmac 0xa123 + wlanconfig ethX rdbbp 0x0123 + wlanconfig ethX rdrf 0x0123 + +wrmac +wrbbp +wrrf + These commands are used to write the MAC, BBP and RF registers in the + card. These commands take two parameters that specify the offset + location and the value that is to be written. This parameters can be + specified either in decimal or in hexadecimal (by preceding the number + with a "0x"). + + Usage: + wlanconfig ethX wrmac 0xa123 0xaa + wlanconfig ethX wrbbp 0x0123 0xaa + wlanconfig ethX wrrf 0x0123 0xaa + +sdcmd52r + This command is used to read a controller register in + Secure Digital I/O Interfaces. + + wlanconfig eth1 sdcmd52r + + Usage: + wlanconfig eth1 sdcmd52r 0x00 0x07 + +sdcmd52w + This command is used to write to a controller register in + Secure Digital I/O Interfaces. + + wlanconfig eth1 sdcmd52w + + Usage: + wlanconfig eth1 sdcmd52w 0x00 0x02 0x0a + +caldataext + In order to overcome the situation without EEPROM in the WLAN module, + we send the extension calibration command to modify the existing + hardware-spec command. This command takes one parameter that specifies + the file name of the configuration file. + + Usage: + wlanconfig eth1 caldataext .conf> + cal_data_ext_set_.conf is a configuration file to the + wlanconfig to set the calibration values. The 3 existing + versions are v5, vA and v7. + Example: + wlanconfig eth1 caldataext cal_data_ext_set_v5.conf + Edit this file for changing calibration values. + +rdeeprom + To read the EEPROM contents of the card. + + Usage: + wlanconfig ethX rdeeprom 0x00 0x10 + +sleepparams + This command is used to set the sleepclock configurations + + Usage: + wlanconfig ethX sleepparams get: reads the current sleepclock configuration + + wlanconfig ethX sleepparams set p1 p2 p3 p4 p5 p6: writes the sleepclock configuration. + + where: + p1 is Sleep clock error in ppm (0-65535) + p2 is Wakeup offset in usec (0-65535) + p3 is Clock stabilization time in usec (0-65535) + p4 is Control periodic calibration (0-2) + p5 is Control the use of external sleep clock (0-2) + p6 is reserved for debug (0-65535) + +bca-ts + This command is used to set/get the BCA timeshare parameters. + + This command only works after BCA been enabled. + + Usage: + wlanconfig ethX bca-ts + + where: + Traffic Type 0 - Wlan and bluetooth are low priority. + 1 - Wlan and bluetooth are high priority. + + TimeShareInterval value is not multiple of 10 then floor value + is taken and the valid range is < 20 ... 60,000 > in milliseconds. + + BTTime value is not multiple of 10 then floor value is + taken and the valid range is < 0 ... TimeShareInterval value > + in milliseconds. + + Example: + wlanconfig ethX bca-ts get 1 + get the BCA timeshare settings when wlan and bluetooth are set to high priority. + + wlanconfig ethX bca-ts set 1 30 20 + set wlan and bluetooth to high priority, wlan TimeShareInterval to 30ms, BTTime to 20ms. + + +bgscanconfig + This will configure the various parameters for background scan. + + wlanconfig ethX bgscanconfig bg_scan_config.conf + + bg_scan_config.conf is the configuration file to wlanconfig + + Edit this file for changing bg scan values. + +hostcmd +hostcmd + This command is used to set the configurations for + event descriptor interface command. + hostcmd.conf is a generic configuration file containing multiple configuration enties + for subscrive event API + subsvent_get: get subscribed event parameters + subsvent_set: set subscribed event parameters + + Usage: + wlanconfig ethX hostcmd hostcmd.conf subevent_get + wlanconfig ethX hostcmd hostcmd.conf subevent_set + +extscan + This command is used to do a specific scan. + + Usage: wlanconfig ethX extscan + + Example: + wlanconfig ethX extscan LINKSYS-AP + + To see the results of use getscanlist command. + +getscanlist + This command is used to get the scan results. + + Usage: wlanconfig ethX getscanlist + + Example: + wlanconfig ethX getscanlist + +setuserscan + Initiate a customized scan and retrieve the results + + Usage: + wlanconfig ethX setuserscan [ARGS] + + where [ARGS]: + + chan=[chan#][band][mode] where band is [a,b,g] and mode is + blank for active or 'p' for passive + bssid=xx:xx:xx:xx:xx:xx specify a BSSID filter for the scan + ssid="[SSID]" specify a SSID filter for the scan + wc="[WILDCARD SSID]" specify a UNIX pattern matching filter (using * + and ?) for SSIDs found in a broadcast probe + keep=[0 or 1] keep the previous scan results (1), discard (0) + dur=[scan time] time to scan for each channel in milliseconds + probes=[#] number of probe requests to send on each chan + for each broadcast probe required and each SSID + specific probe required + type=[1,2,3] BSS type: 1 (Infra), 2(Adhoc), 3(Any) + + Any combination of the above arguments can be supplied on the command line. + If the chan token is absent, a full channel scan will be completed by + the driver. If the dur or probes tokens are absent, the driver default + setting will be used. The bssid and ssid fields, if blank, + will produce an unfiltered scan. The type field will default to 3 (Any) + and the keep field will default to 0 (Discard). + + Examples: + 1) Perform an active scan on channels 1, 6, and 11 in the 'g' band: + setuserscan chan=1g,6g,11g + + 2) Perform a passive scan on channel 11 for 20 ms: + setuserscan chan=11gp dur=20 + + 3) Perform an active scan on channels 1, 6, and 11; and a passive scan on + channel 36 in the 'a' band: + setuserscan chan=1g,6g,11g,36ap + + 4) Perform an active scan on channel 6 and 36 for a specific SSID: + setuserscan chan=6g,36a ssid="TestAP" + + 5) Scan all available channels (B/G, A bands) for a specific BSSID, keep + the current scan table intact, update existing or append new scan data: + setuserscan bssid=00:50:43:20:12:82 keep=1 + + 6) Scan channel 6, for all infrastructure networks, sending two probe + requests. Keep the previous scan table intact. Update any duplicate + BSSID/SSID matches with the new scan data: + setuserscan chan=6g type=1 probes=2 keep=1 + + 7) Scan channel 1 and 6, for all networks matching the Mrvl*AP + or AP*Mrvl? patterns and for MrvlTst SSID. Generate 3 broadcast + probes for the patterns and 3 SSID specific probes for MrvlTst on + both channel 1 and channel 6. + chan=1g,6g probes=3 wc="Mrvl*AP" wc="AP*Mrvl?" ssid="MrvlTst" + + All entries in the scan table (not just the new scan data when keep=1) + will be displayed upon completion by use of the getscantable ioctl. + +getscantable + Display the current contents of the driver scan table + + Usage: + wlanconfig ethX getscantable + wlanconfig ethX getscantable [#] + wlanconfig ethX getscantable tsf + wlanconfig ethX getscantable help + + 1) Without argument, the entire scantable is displayed. + 2) Specifying a # will display detailed information about a specific scan + table entry. '0' displays driver cached information regarding the + current association (if any). + 3) The tsf argument will display the entire scan table with the recorded + TSF timestamp for the entry. + 4) The help argument will display the legend for the capability field + +getassocrsp + Display the contents of the driver association response buffer. The + driver buffer is cleared after the response is returned to prevent + state response buffer returns. + + Usage: + wlanconfig ethX getassocrsp + +setmrvltlv + Setup a test Marvell TLV for the driver to insert in the next + association command to the firmware. + + wlanconfig will provision a test TLV that can be verified in the assoc. + response to the AP. Used to test the IOCTL functionality. + + Usage: + wlanconfig ethX setmrvltlv + +addts + Send an ADDTS command to the associated AP. + + Process a given conf file for a specific TSPEC data block. Send the + TSPEC along with any other IEs to the driver/firmware for transmission + in an ADDTS request to the associated AP. + + Return the execution status of the command as well as the ADDTS response + from the AP if any. + + Usage: + wlanconfig ethX addts + +delts + Send a DELTS command to the associated AP. + + Process a given conf file for a specific TSPEC data block. Send the + TSPEC along with any other IEs to the driver/firmware for transmission + in a DELTS request to the associated AP. + + Return the execution status of the command. There is no response to a + DELTS from the AP. + + Usage: + wlanconfig ethX delts + +qconfig + Send a WMM AC Queue configuration command to get/set/default params + + Configure or get the parameters of a WMM AC queue. The command takes + an optional Queue Id as a last parameter. Without the queue id, all + queues will be acted upon. + + Usage: + wlanconfig ethX qconfig set msdu [Queue Id: 0-3] + wlanconfig ethX qconfig get [Queue Id: 0-3] + wlanconfig ethX qconfig def [Queue Id: 0-3] + +qstats + Turn on/off or retrieve and clear the queue statistics for an AC + + Turn the queue statistics collection on/off for a given AC or retrieve the + current accumulated stats and clear them from the firmware. The command + takes an optional Queue Id as a last parameter. Without the queue id, + all queues will be acted upon. + + Usage: + wlanconfig ethX qstats on [Queue Id: 0-3] + wlanconfig ethX qstats off [Queue Id: 0-3] + wlanconfig ethX qstats get [Queue Id: 0-3] + +hostcmd +hostcmd + This configures the power adaptation paramemters + + Usage: + wlanconfig ethX hostcmd hostcmd.conf pa_cfg_ext_get + wlanconfig ethX hostcmd hostcmd.conf pa_cfg_ext_set + + hostcmd.conf is a generic configuration file containing multiple configuration enties + for power adapation + pa_cfg_ext_get: get pa_cfg_ext parameters + pa_cfg_ext_set: set pa_cfg_ext parameters + + The following table shows the bitmap of the rates (bit 0 is the least significant bit): + + Bit Data rate + 0 1 Mbps + 1 2 Mbps + 2 5.5 Mbps + 3 11 Mbps + 4 Reserved + 5 6 Mbps + 6 9 Mbps + 7 12 Mbps + 8 18 Mbps + 9 24 Mbps + 10 36 Mbps + 11 48 Mbps + 12 54 Mbps + 13-15 Reserved + + Up to 5 power level groups are supported. + + The default power adaptation groups: + + Power Level Rate Bitmap (Mbps) + 13 dbm 0x1800 (54, 48) + 15 dbm 0x07e0 (36, 24, 18, 12, 9, 6) + 18 dbm 0x000f (11, 5.5, 2, 1) + + Edit the hostcmd.conf file to change the settings + +hostcmd + This is an extended host_sleep_cfg command to configure the ARP filtering parameters. + + Usage: + wlanconfig ethX hostcmd hostcmd.conf arp_filter + + Edit the arp_filter section in hostcmd.conf file to change the settings + +hostcmd +hostcmd +hostcmd + This configures the Frame Auto Transmission paramemters + + Usage: + wlanconfig ethX hostcmd hostcmd.conf auto_tx_get + wlanconfig ethX hostcmd hostcmd.conf NatKeepAlive + wlanconfig ethX hostcmd hostcmd.conf auto_tx_unreg + + hostcmd.conf is a generic configuration file containing multiple configuration enties + for Frame Auto Transmission + auto_tx_get: get auto_tx parameters + NatKeepAlive: register to firmware for sending NAT Keep Alive packet + auto_tx_unreg: unregister to firmware auto_tx + + Edit the auto_tx section in hostcmd.conf file to change the settings + +hostcmd + This command is used to set/get LED control. + + Usage: + wlanconfig ethX hostcmd hostcmd.conf ledctrl_get + wlanconfig ethX hostcmd hostcmd.conf ledctrl_set + + hostcmd.conf is a generic configuration file containing multiple configuration enties + for LED Ctrl + led_ctrl_get: get auto_tx parameters + led_ctrl_set: set auto_tx parameters + + Edit the ledctrl section in hostcmd.conf file to change the settings + +hstest + This command runs in the background to handle GPIO/SDIO interrupt events + in HOST SLEEP mode. + + Usage: + wlanconfig ethX hstest & + +getcfptable + This command is used to get Channel-Freq-MaxTxPower table based on the region code. + If no parameter provided, the CFP table for current region code will be returned. + + Usage: + wlanconfig ethX getcfptable [region] + +gettsf + Display the current MAC TSF value. + +arpfilter + This command is used to configure the ARP filtering parameters. + + Usage: + wlanconfig ethX arpfilter arpfilter.conf + + Edit arpfilter.conf file to change the settings + +txpktstats + Retrieve and clear transmit packet statistics collected by the firmware: + + The API displays the following statistics for each rate: + - Number of packets initially queued using the rate + - Number of total attempts for the packets queued using this initial + rate. This includes attempts at other rates in case of hardware or + single rate drop modes. + - Number of retry exhaustion failures for packets queued using this + initial rate. + - Number of MSDU lifetime expiry failures for packets queued using + this initial rate. + - Number of packets successfully completed at this rate + +============================================================================== diff --git a/drivers/net/wireless/marvell8686/host.h b/drivers/net/wireless/marvell8686/host.h new file mode 100644 index 0000000..cf4562a --- /dev/null +++ b/drivers/net/wireless/marvell8686/host.h @@ -0,0 +1,349 @@ +/** @file host.h + * + * @brief This file contains definitions of WLAN commands. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/11/05: Add Doxygen format comments + 01/11/06: Remove assoc response codes; full IEEE assoc resp now returned + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support + 04/10/06: Add power_adapt_cfg_ext command + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/03/06: Add auto_tx hostcmd + 05/04/06: Add IBSS coalescing related new hostcmd and event + 08/28/06: Add LED_CTRL hostcmd +********************************************************/ + +#ifndef _HOST_H_ +#define _HOST_H_ + +/** PUBLIC DEFINITIONS */ +#define DEFAULT_AD_HOC_CHANNEL 6 +#define DEFAULT_AD_HOC_CHANNEL_A 36 +/** The first valid channel for use */ +#define FIRST_VALID_CHANNEL 0xff + +/** IEEE 802.11 OIDs */ +#define OID_802_11_INFRASTRUCTURE_MODE 0x00008001 +#define OID_802_11_FRAGMENTATION_THRESHOLD 0x00008002 +#define OID_802_11_RTS_THRESHOLD 0x00008003 +#define OID_802_11_ADD_WEP 0x00008004 +#define OID_802_11_REMOVE_WEP 0x00008005 +#define OID_802_11_TX_RETRYCOUNT 0x00008006 +#define OID_802_11D_ENABLE 0x00008007 +#define OID_802_11_DTIM 0x00008009 + +#define HostCmd_OPTION_WAITFORRSP 0x0002 + +/** Host Command ID */ +#define HostCmd_CMD_GET_HW_SPEC 0x0003 +#define HostCmd_CMD_802_11_RESET 0x0005 +#define HostCmd_CMD_802_11_SCAN 0x0006 +#define HostCmd_CMD_802_11_GET_LOG 0x000b +#define HostCmd_CMD_MAC_MULTICAST_ADR 0x0010 +#define HostCmd_CMD_802_11_EEPROM_ACCESS 0x0059 +#define HostCmd_CMD_802_11_ASSOCIATE 0x0012 +#define HostCmd_CMD_802_11_SET_WEP 0x0013 +#define HostCmd_CMD_802_11_SNMP_MIB 0x0016 +#define HostCmd_CMD_MAC_REG_ACCESS 0x0019 +#define HostCmd_CMD_BBP_REG_ACCESS 0x001a +#define HostCmd_CMD_RF_REG_ACCESS 0x001b +#define HostCmd_CMD_802_11_RADIO_CONTROL 0x001c +#define HostCmd_CMD_802_11_RF_CHANNEL 0x001d +#define HostCmd_CMD_802_11_RF_TX_POWER 0x001e +#define HostCmd_CMD_802_11_RSSI 0x001f +#define HostCmd_CMD_802_11_RF_ANTENNA 0x0020 + +#define HostCmd_CMD_802_11_PS_MODE 0x0021 + +#define HostCmd_CMD_802_11_DEAUTHENTICATE 0x0024 +#define HostCmd_CMD_MAC_CONTROL 0x0028 +#define HostCmd_CMD_802_11_AD_HOC_START 0x002b +#define HostCmd_CMD_802_11_AD_HOC_JOIN 0x002c + +#define HostCmd_CMD_802_11_KEY_MATERIAL 0x005e + +#define HostCmd_CMD_802_11_DEEP_SLEEP 0x003e + +#define HostCmd_CMD_802_11_AD_HOC_STOP 0x0040 + +#define HostCmd_CMD_802_11_HOST_SLEEP_CFG 0x0043 +#define HostCmd_CMD_802_11_WAKEUP_CONFIRM 0x0044 +#define HostCmd_CMD_802_11_HOST_SLEEP_ACTIVATE 0x0045 + +#define HostCmd_CMD_802_11_MAC_ADDRESS 0x004D +#define HostCmd_CMD_802_11_EEPROM_ACCESS 0x0059 + +#define HostCmd_CMD_802_11_BAND_CONFIG 0x0058 + +#define HostCmd_CMD_802_11D_DOMAIN_INFO 0x005b + +#define HostCmd_CMD_802_11_SLEEP_PARAMS 0x0066 + +#define HostCmd_CMD_802_11_INACTIVITY_TIMEOUT 0x0067 + +#define HostCmd_CMD_802_11_SLEEP_PERIOD 0x0068 +#define HostCmd_CMD_802_11_BCA_CONFIG_TIMESHARE 0x0069 + +#define HostCmd_CMD_802_11_BG_SCAN_CONFIG 0x006b +#define HostCmd_CMD_802_11_BG_SCAN_QUERY 0x006c + +#define HostCmd_CMD_802_11_CAL_DATA_EXT 0x006d + +#define HostCmd_CMD_WMM_ADDTS_REQ 0x006E +#define HostCmd_CMD_WMM_DELTS_REQ 0x006F +#define HostCmd_CMD_WMM_QUEUE_CONFIG 0x0070 +#define HostCmd_CMD_WMM_GET_STATUS 0x0071 + +#define HostCmd_CMD_802_11_TPC_CFG 0x0072 + +#define HostCmd_CMD_802_11_FW_WAKE_METHOD 0x0074 + +#define HostCmd_CMD_802_11_LED_CONTROL 0x004e + +#define HostCmd_CMD_802_11_SUBSCRIBE_EVENT 0x0075 + +#define HostCmd_CMD_802_11_RATE_ADAPT_RATESET 0x0076 + +#define HostCmd_CMD_802_11_TX_RATE_QUERY 0x007f + +#define HostCmd_CMD_802_11_POWER_ADAPT_CFG_EXT 0x007e + +#define HostCmd_CMD_GET_TSF 0x0080 + +#define HostCmd_CMD_WMM_QUEUE_STATS 0x0081 + +#define HostCmd_CMD_802_11_AUTO_TX 0x0082 +#define HostCmd_CMD_802_11_IBSS_COALESCING_STATUS 0x0083 + +#define HostCmd_CMD_MEM_ACCESS 0x0086 + +#define HostCmd_CMD_SDIO_GPIO_INT_CONFIG (0x0088) + +#ifdef MFG_CMD_SUPPORT +#define HostCmd_CMD_MFG_COMMAND 0x0089 +#define HostCmd_RET_MFG_COMMAND 0x8089 +#endif + +#define HostCmd_CMD_TX_PKT_STATS 0x008d + +#define HostCmd_CMD_SDIO_PULL_CTRL 0x0093 + +#define HostCmd_CMD_802_11_LDO_CONFIG 0x0096 + +#define HostCmd_CMD_VERSION_EXT 0x0097 + +/* For the IEEE Power Save */ +#define HostCmd_SubCmd_Enter_PS 0x0030 +#define HostCmd_SubCmd_Exit_PS 0x0031 +#define HostCmd_SubCmd_Sleep_Confirmed 0x0034 +#define HostCmd_SubCmd_Full_PowerDown 0x0035 +#define HostCmd_SubCmd_Full_PowerUp 0x0036 + +/* Command RET code, MSB is set to 1 */ +#define HostCmd_RET_HW_SPEC_INFO 0x8003 +#define HostCmd_RET_802_11_RESET 0x8005 +#define HostCmd_RET_802_11_SCAN 0x8006 +#define HostCmd_RET_802_11_GET_LOG 0x800b +#define HostCmd_RET_MAC_CONTROL 0x8028 +#define HostCmd_RET_MAC_MULTICAST_ADR 0x8010 +#define HostCmd_RET_802_11_DEAUTHENTICATE 0x8024 +#define HostCmd_RET_802_11_ASSOCIATE 0x8012 +#define HostCmd_RET_802_11_SET_WEP 0x8013 +#define HostCmd_RET_802_3_STAT 0x8015 +#define HostCmd_RET_802_11_SNMP_MIB 0x8016 +#define HostCmd_RET_MAC_REG_ACCESS 0x8019 +#define HostCmd_RET_BBP_REG_ACCESS 0x801a +#define HostCmd_RET_RF_REG_ACCESS 0x801b +#define HostCmd_RET_802_11_RADIO_CONTROL 0x801c +#define HostCmd_RET_802_11_RF_CHANNEL 0x801d +#define HostCmd_RET_802_11_RSSI 0x801f +#define HostCmd_RET_802_11_RF_TX_POWER 0x801e +#define HostCmd_RET_802_11_RF_ANTENNA 0x8020 +#define HostCmd_RET_802_11_PS_MODE 0x8021 + +#define HostCmd_RET_802_11_AD_HOC_START 0x802B +#define HostCmd_RET_802_11_AD_HOC_JOIN 0x802C + +#define HostCmd_RET_802_11_KEY_MATERIAL 0x805e + +#define HostCmd_ACT_SET 0x0001 +#define HostCmd_ACT_GET 0x0000 + +#define HostCmd_RET_802_11_AD_HOC_STOP 0x8040 + +#define HostCmd_RET_802_11_HOST_SLEEP_CFG 0x8043 +#define HostCmd_RET_802_11_WAKEUP_CONFIRM 0x8044 +#define HostCmd_RET_802_11_HOST_SLEEP_ACTIVATE 0x8045 + +#define HostCmd_RET_802_11_MAC_ADDRESS 0x804D +#define HostCmd_RET_802_11_EEPROM_ACCESS 0x8059 + +#define HostCmd_RET_802_11_BAND_CONFIG 0x8058 + +#define HostCmd_RET_802_11_SLEEP_PARAMS 0x8066 + +#define HostCmd_RET_802_11_INACTIVITY_TIMEOUT 0x8067 + +#define HostCmd_RET_802_11_SLEEP_PERIOD 0x8068 +#define HostCmd_RET_802_11_BCA_CONFIG_TIMESHARE 0x8069 + +#define HostCmd_RET_802_11D_DOMAIN_INFO 0x805B + +#define HostCmd_RET_802_11_BG_SCAN_CONFIG 0x806b +#define HostCmd_RET_802_11_BG_SCAN_QUERY 0x806c + +#define HostCmd_RET_802_11_CAL_DATA_EXT 0x806d + +#define HostCmd_RET_WMM_ADDTS_REQ 0x806E +#define HostCmd_RET_WMM_DELTS_REQ 0x806F +#define HostCmd_RET_WMM_QUEUE_CONFIG 0x8070 +#define HostCmd_RET_WMM_GET_STATUS 0x8071 + +#define HostCmd_RET_802_11_TPC_CFG 0x8072 + +#define HostCmd_RET_802_11_LED_CONTROL 0x804e + +#define HostCmd_RET_802_11_FW_WAKE_METHOD 0x8074 + +#define HostCmd_RET_802_11_SUBSCRIBE_EVENT 0x8075 + +#define HostCmd_RET_802_11_RATE_ADAPT_RATESET 0x8076 + +#define HostCmd_RTE_802_11_TX_RATE_QUERY 0x807f + +#define HostCmd_RET_GET_TSF 0x8080 + +#define HostCmd_RET_WMM_QUEUE_STATS 0x8081 + +#define HostCmd_RET_802_11_POWER_ADAPT_CFG_EXT 0x807e + +#define HostCmd_RET_802_11_AUTO_TX 0x8082 + +#define HostCmd_RET_802_11_IBSS_COALESCING_STATUS 0x8083 + +#define HostCmd_RET_MEM_ACCESS 0x8086 + +#define HostCmd_RET_SDIO_GPIO_INT_CONFIG (0x8088) + +#define HostCmd_RET_TX_PKT_STATS 0x808d + +#define HostCmd_RET_SDIO_PULL_CTRL 0x8093 + +#define HostCmd_RET_802_11_LDO_CONFIG 0x8096 + +#define HostCmd_RET_VERSION_EXT 0x8097 + +/** General Result Code*/ +/* OK */ +#define HostCmd_RESULT_OK 0x0000 +/* Genenral error */ +#define HostCmd_RESULT_ERROR 0x0001 +/* Command is not valid */ +#define HostCmd_RESULT_NOT_SUPPORT 0x0002 +/* Command is pending */ +#define HostCmd_RESULT_PENDING 0x0003 +/* System is busy (command ignored) */ +#define HostCmd_RESULT_BUSY 0x0004 +/* Data buffer is not big enough */ +#define HostCmd_RESULT_PARTIAL_DATA 0x0005 + +/* Definition of action or option for each command */ + +/* Define general purpose action */ +#define HostCmd_ACT_GEN_READ 0x0000 +#define HostCmd_ACT_GEN_WRITE 0x0001 +#define HostCmd_ACT_GEN_GET 0x0000 +#define HostCmd_ACT_GEN_SET 0x0001 +#define HostCmd_ACT_GEN_REMOVE 0x0002 +#define HostCmd_ACT_GEN_OFF 0x0000 +#define HostCmd_ACT_GEN_ON 0x0001 + +/* Define action or option for HostCmd_CMD_802_11_SET_WEP */ +#define HostCmd_ACT_ADD 0x0002 +#define HostCmd_ACT_REMOVE 0x0004 + +#define HostCmd_TYPE_WEP_40_BIT 0x0001 +#define HostCmd_TYPE_WEP_104_BIT 0x0002 + +#define HostCmd_WEP_KEY_INDEX_MASK 0x3fff + +/* Define action or option for HostCmd_CMD_802_11_SCAN */ +#define HostCmd_BSS_TYPE_BSS 0x0001 +#define HostCmd_BSS_TYPE_IBSS 0x0002 +#define HostCmd_BSS_TYPE_ANY 0x0003 + +/* Define action or option for HostCmd_CMD_802_11_SCAN */ +#define HostCmd_SCAN_TYPE_ACTIVE 0x0000 +#define HostCmd_SCAN_TYPE_PASSIVE 0x0001 + +/* Radio type definitions for the channel TLV */ +#define HostCmd_SCAN_RADIO_TYPE_BG 0 +#define HostCmd_SCAN_RADIO_TYPE_A 1 + +/* Define action or option for HostCmd_CMD_MAC_CONTROL */ +#define HostCmd_ACT_MAC_RX_ON 0x0001 +#define HostCmd_ACT_MAC_TX_ON 0x0002 +#define HostCmd_ACT_MAC_LOOPBACK_ON 0x0004 +#define HostCmd_ACT_MAC_WEP_ENABLE 0x0008 +#define HostCmd_ACT_MAC_ETHERNETII_ENABLE 0x0010 +#define HostCmd_ACT_MAC_PROMISCUOUS_ENABLE 0x0080 +#define HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE 0x0100 +#define HostCmd_ACT_MAC_RTS_CTS_ENABLE 0x0200 +#define HostCmd_ACT_MAC_STRICT_PROTECTION_ENABLE 0x0400 +#define HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON 0x2000 + +/* Define action or option or constant for HostCmd_CMD_MAC_MULTICAST_ADR */ +#define HostCmd_SIZE_MAC_ADR 6 +#define HostCmd_MAX_MCAST_ADRS 32 + +#define RADIO_ON 0x01 +#define RADIO_OFF 0x00 + +/* Define action or option for CMD_802_11_RF_CHANNEL */ +#define HostCmd_OPT_802_11_RF_CHANNEL_GET 0x00 +#define HostCmd_OPT_802_11_RF_CHANNEL_SET 0x01 + +#define HostCmd_ACT_SET_RX 0x0001 +#define HostCmd_ACT_SET_TX 0x0002 +#define HostCmd_ACT_SET_BOTH 0x0003 +#define HostCmd_ACT_GET_RX 0x0004 +#define HostCmd_ACT_GET_TX 0x0008 +#define HostCmd_ACT_GET_BOTH 0x000c + +/** Card Event definition */ +#define MACREG_INT_CODE_DUMMY_HOST_WAKEUP_SIGNAL 0x00000001 +#define MACREG_INT_CODE_LINK_LOST_WITH_SCAN 0x00000002 +#define MACREG_INT_CODE_LINK_LOST 0x00000003 +#define MACREG_INT_CODE_LINK_SENSED 0x00000004 +#define MACREG_INT_CODE_MIB_CHANGED 0x00000006 +#define MACREG_INT_CODE_INIT_DONE 0x00000007 +#define MACREG_INT_CODE_DEAUTHENTICATED 0x00000008 +#define MACREG_INT_CODE_DISASSOCIATED 0x00000009 +#define MACREG_INT_CODE_PS_AWAKE 0x0000000a +#define MACREG_INT_CODE_PS_SLEEP 0x0000000b +#define MACREG_INT_CODE_MIC_ERR_MULTICAST 0x0000000d +#define MACREG_INT_CODE_MIC_ERR_UNICAST 0x0000000e +#define MACREG_INT_CODE_WM_AWAKE 0x0000000f +#define MACREG_INT_CODE_DEEP_SLEEP_AWAKE 0x00000010 +#define MACREG_INT_CODE_ADHOC_BCN_LOST 0x00000011 +#define MACREG_INT_CODE_HOST_SLEEP_AWAKE 0x00000012 +#define MACREG_INT_CODE_WMM_STATUS_CHANGE 0x00000017 +#define MACREG_INT_CODE_BG_SCAN_REPORT 0x00000018 +#define MACREG_INT_CODE_RSSI_LOW 0x00000019 +#define MACREG_INT_CODE_SNR_LOW 0x0000001a +#define MACREG_INT_CODE_MAX_FAIL 0x0000001b +#define MACREG_INT_CODE_RSSI_HIGH 0x0000001c +#define MACREG_INT_CODE_SNR_HIGH 0x0000001d +#define MACREG_INT_CODE_IBSS_COALESCED 0x0000001e +#define MACREG_INT_CODE_PRE_BEACON_LOST 0x00000031 + +/* Define bitmap conditions for HOST_SLEEP_CFG */ +#define HOST_SLEEP_CFG_CANCEL 0xffffffff +#define HOST_SLEEP_CFG_WAKEUP_THRU_INTERFACE 0xff +#define HOST_SLEEP_CFG_GAP_FF 0xff + +#endif /* _HOST_H_ */ diff --git a/drivers/net/wireless/marvell8686/hostcmd.h b/drivers/net/wireless/marvell8686/hostcmd.h new file mode 100644 index 0000000..9f189bb --- /dev/null +++ b/drivers/net/wireless/marvell8686/hostcmd.h @@ -0,0 +1,1052 @@ +/** @file hostcmd.h + * + * @brief This file contains the function prototypes, data structure + * and defines for all the host/station commands + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/11/05: Add Doxygen format comments + 01/11/06: Update association struct to reflect IEEE passthrough response + Conditionalize new scan/join structures + 04/10/06: Add hostcmd generic API and power_adapt_cfg_ext command + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/03/06: Add auto_tx hostcmd + 05/04/06: Add IBSS coalescing related new hostcmd + 08/28/06: Add LED_CTRL hostcmd + 08/29/06: Add ledgpio private command +********************************************************/ + +#ifndef __HOSTCMD__H +#define __HOSTCMD__H + +/* 802.11-related definitions */ + +/** TxPD descriptor */ +typedef struct _TxPD +{ + /** Current Tx packet status */ + u32 TxStatus; + /** Tx Control */ + u32 TxControl; + u32 TxPacketLocation; + /** Tx packet length */ + u16 TxPacketLength; + /**Destination MAC address */ + u8 TxDestAddr[MRVDRV_ETH_ADDR_LEN]; + /** Pkt Priority */ + u8 Priority; + /** Trasnit Pkt Flags*/ + u8 Flags; + /** Amount of time the packet has been queued in the driver (units = 2ms)*/ + u8 PktDelay_2ms; + /** Reserved */ + u8 Reserved1; + +} __ATTRIB_PACK__ TxPD, *PTxPD; + +/** RxPD Descriptor */ +typedef struct _RxPD +{ + /** Current Rx packet status */ + u16 RxStatus; + + /** SNR */ + u8 SNR; + + /** Tx Control */ + u8 RxControl; + + /** Pkt Length */ + u16 PktLen; + + /** Noise Floor */ + u8 NF; + + /** Rx Packet Rate */ + u8 RxRate; + + /** Pkt offset */ + u32 PktOffset; + u8 RxPacketType; + u8 Reserved_1[3]; + /** Pkt Priority */ + u8 Priority; + u8 Reserved[3]; + +} __ATTRIB_PACK__ RxPD, *PRxPD; + +#if defined(__KERNEL__) + +/** CmdCtrlNode */ +typedef struct _CmdCtrlNode +{ + /* CMD link list */ + struct list_head list; + + u32 Status; + + /* CMD ID */ + WLAN_OID cmd_oid; + + /*CMD wait option: wait for finish or no wait */ + u16 wait_option; + + /* command parameter */ + void *pdata_buf; + + /*command data */ + u8 *BufVirtualAddr; + + u16 CmdFlags; + + /* wait queue */ + u16 CmdWaitQWoken; + wait_queue_head_t cmdwait_q __ATTRIB_ALIGN__; +} __ATTRIB_PACK__ CmdCtrlNode, *PCmdCtrlNode; + +#endif + +/** MRVL_WEP_KEY */ +typedef struct _MRVL_WEP_KEY +{ + u32 Length; + u32 KeyIndex; + u32 KeyLength; + u8 KeyMaterial[MRVL_KEY_BUFFER_SIZE_IN_BYTE]; +} __ATTRIB_PACK__ MRVL_WEP_KEY, *PMRVL_WEP_KEY; + +typedef ULONGLONG WLAN_802_11_KEY_RSC; + +/** WLAN_802_11_KEY */ +typedef struct _WLAN_802_11_KEY +{ + u32 Length; + u32 KeyIndex; + u32 KeyLength; + WLAN_802_11_MAC_ADDRESS BSSID; + WLAN_802_11_KEY_RSC KeyRSC; + u8 KeyMaterial[MRVL_MAX_KEY_WPA_KEY_LENGTH]; +} __ATTRIB_PACK__ WLAN_802_11_KEY; + +/** MRVL_WPA_KEY */ +typedef struct _MRVL_WPA_KEY +{ + u32 KeyIndex; + u32 KeyLength; + u32 KeyRSC; + u8 KeyMaterial[MRVL_MAX_KEY_WPA_KEY_LENGTH]; +} MRVL_WPA_KEY, *PMRVL_WPA_KEY; + +/** MRVL_WLAN_WPA_KEY */ +typedef struct _MRVL_WLAN_WPA_KEY +{ + u8 EncryptionKey[16]; + u8 MICKey1[8]; + u8 MICKey2[8]; +} MRVL_WLAN_WPA_KEY, *PMRVL_WLAN_WPA_KEY; + +/* Received Signal Strength Indication in dBm*/ +typedef LONG WLAN_802_11_RSSI; + +/** WLAN_802_11_WEP */ +typedef struct _WLAN_802_11_WEP +{ + /* Length of this structure */ + u32 Length; + + /* 0 is the per-client key, 1-N are the global keys */ + u32 KeyIndex; + + /* length of key in bytes */ + u32 KeyLength; + + /* variable length depending on above field */ + u8 KeyMaterial[1]; +} __ATTRIB_PACK__ WLAN_802_11_WEP; + +/** WLAN_802_11_SSID */ +typedef struct _WLAN_802_11_SSID +{ + /* SSID Length */ + u32 SsidLength; + + /* SSID information field */ + u8 Ssid[WLAN_MAX_SSID_LENGTH]; +} __ATTRIB_PACK__ WLAN_802_11_SSID; + +typedef u32 WLAN_802_11_FRAGMENTATION_THRESHOLD; +typedef u32 WLAN_802_11_RTS_THRESHOLD; +typedef u32 WLAN_802_11_ANTENNA; + +/** wlan_offset_value */ +typedef struct _wlan_offset_value +{ + u32 offset; + u32 value; +} wlan_offset_value; + +/** WLAN_802_11_FIXED_IEs */ +typedef struct _WLAN_802_11_FIXED_IEs +{ + u8 Timestamp[8]; + u16 BeaconInterval; + u16 Capabilities; +} WLAN_802_11_FIXED_IEs; + +/** WLAN_802_11_VARIABLE_IEs */ +typedef struct _WLAN_802_11_VARIABLE_IEs +{ + u8 ElementID; + u8 Length; + u8 data[1]; +} WLAN_802_11_VARIABLE_IEs; + +/** WLAN_802_11_AI_RESFI */ +typedef struct _WLAN_802_11_AI_RESFI +{ + u16 Capabilities; + u16 StatusCode; + u16 AssociationId; +} WLAN_802_11_AI_RESFI; + +/** WLAN_802_11_AI_REQFI */ +typedef struct _WLAN_802_11_AI_REQFI +{ + u16 Capabilities; + u16 ListenInterval; + WLAN_802_11_MAC_ADDRESS CurrentAPAddress; +} WLAN_802_11_AI_REQFI; + +/* Define general data structure */ +/** HostCmd_DS_GEN */ +typedef struct _HostCmd_DS_GEN +{ + u16 Command; + u16 Size; + u16 SeqNum; + u16 Result; +} __ATTRIB_PACK__ HostCmd_DS_GEN, HostCmd_DS_802_11_DEEP_SLEEP; + +#define S_DS_GEN sizeof(HostCmd_DS_GEN) +/* + * Define data structure for HostCmd_CMD_GET_HW_SPEC + * This structure defines the response for the GET_HW_SPEC command + */ +/** HostCmd_DS_GET_HW_SPEC */ +typedef struct _HostCmd_DS_GET_HW_SPEC +{ + /* HW Interface version number */ + u16 HWIfVersion; + + /* HW version number */ + u16 Version; + + /* Max number of TxPD FW can handle */ + u16 NumOfTxPD; + + /* Max no of Multicast address */ + u16 NumOfMCastAdr; + + /* MAC address */ + u8 PermanentAddr[6]; + + /* Region Code */ + u16 RegionCode; + + /* Number of antenna used */ + u16 NumberOfAntenna; + + /* FW release number, example 0x1234=1.2.3.4 */ + u32 FWReleaseNumber; + + u32 Reserved_1; + + u32 Reserved_2; + + u32 Reserved_3; + + /*FW/HW Capability */ + u32 fwCapInfo; +} __ATTRIB_PACK__ HostCmd_DS_GET_HW_SPEC; + +typedef struct _HostCmd_DS_802_11_SUBSCRIBE_EVENT +{ + u16 Action; + u16 Events; +} __ATTRIB_PACK__ HostCmd_DS_802_11_SUBSCRIBE_EVENT; + +/* + * This scan handle Country Information IE(802.11d compliant) + * Define data structure for HostCmd_CMD_802_11_SCAN + */ +/** HostCmd_DS_802_11_SCAN */ +typedef struct _HostCmd_DS_802_11_SCAN +{ + u8 BSSType; + u8 BSSID[ETH_ALEN]; + u8 TlvBuffer[1]; + /* MrvlIEtypes_SsIdParamSet_t SsIdParamSet; + * MrvlIEtypes_ChanListParamSet_t ChanListParamSet; + * MrvlIEtypes_RatesParamSet_t OpRateSet; + * */ +} __ATTRIB_PACK__ HostCmd_DS_802_11_SCAN; + +typedef struct _HostCmd_DS_802_11_SCAN_RSP +{ + u16 BSSDescriptSize; + u8 NumberOfSets; + u8 BssDescAndTlvBuffer[1]; + +} __ATTRIB_PACK__ HostCmd_DS_802_11_SCAN_RSP; + +/** HostCmd_CMD_802_11_GET_LOG */ +typedef struct _HostCmd_DS_802_11_GET_LOG +{ + u32 mcasttxframe; + u32 failed; + u32 retry; + u32 multiretry; + u32 framedup; + u32 rtssuccess; + u32 rtsfailure; + u32 ackfailure; + u32 rxfrag; + u32 mcastrxframe; + u32 fcserror; + u32 txframe; + u32 reserved; +} __ATTRIB_PACK__ HostCmd_DS_802_11_GET_LOG; + +/** HostCmd_CMD_MAC_CONTROL */ +typedef struct _HostCmd_DS_MAC_CONTROL +{ + u16 Action; + u16 Reserved; +} __ATTRIB_PACK__ HostCmd_DS_MAC_CONTROL; + +/** HostCmd_CMD_MAC_MULTICAST_ADR */ +typedef struct _HostCmd_DS_MAC_MULTICAST_ADR +{ + u16 Action; + u16 NumOfAdrs; + u8 MACList[MRVDRV_ETH_ADDR_LEN * MRVDRV_MAX_MULTICAST_LIST_SIZE]; +} __ATTRIB_PACK__ HostCmd_DS_MAC_MULTICAST_ADR; + +/** HostCmd_CMD_802_11_DEAUTHENTICATE */ +typedef struct _HostCmd_DS_802_11_DEAUTHENTICATE +{ + u8 MacAddr[6]; + u16 ReasonCode; +} __ATTRIB_PACK__ HostCmd_DS_802_11_DEAUTHENTICATE; + +/** HostCmd_DS_802_11_ASSOCIATE */ +typedef struct _HostCmd_DS_802_11_ASSOCIATE +{ + u8 PeerStaAddr[6]; + IEEEtypes_CapInfo_t CapInfo; + u16 ListenInterval; + u8 Reserved1[3]; + + /* + * MrvlIEtypes_SsIdParamSet_t SsIdParamSet; + * MrvlIEtypes_PhyParamSet_t PhyParamSet; + * MrvlIEtypes_SsParamSet_t SsParamSet; + * MrvlIEtypes_RatesParamSet_t RatesParamSet; + */ +} __ATTRIB_PACK__ HostCmd_DS_802_11_ASSOCIATE; + +/** HostCmd_RET_802_11_ASSOCIATE */ +typedef struct +{ + IEEEtypes_AssocRsp_t assocRsp; +} __ATTRIB_PACK__ HostCmd_DS_802_11_ASSOCIATE_RSP; + +/** HostCmd_RET_802_11_AD_HOC_JOIN */ +typedef struct _HostCmd_DS_802_11_AD_HOC_RESULT +{ + u8 PAD[3]; + u8 BSSID[MRVDRV_ETH_ADDR_LEN]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_AD_HOC_RESULT; + +/** HostCmd_CMD_802_11_SET_WEP */ +typedef struct _HostCmd_DS_802_11_SET_WEP +{ + /* ACT_ADD, ACT_REMOVE or ACT_ENABLE */ + u16 Action; + + /* Key Index selected for Tx */ + u16 KeyIndex; + + /* 40, 128bit or TXWEP */ + u8 WEPTypeForKey1; + + u8 WEPTypeForKey2; + u8 WEPTypeForKey3; + u8 WEPTypeForKey4; + u8 WEP1[16]; + u8 WEP2[16]; + u8 WEP3[16]; + u8 WEP4[16]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_SET_WEP; + +/** HostCmd_DS_802_11_AD_HOC_STOP */ +typedef struct _HostCmd_DS_802_11_AD_HOC_STOP +{ + +} __ATTRIB_PACK__ HostCmd_DS_802_11_AD_HOC_STOP; + +/** HostCmd_CMD_802_11_SNMP_MIB */ +typedef struct _HostCmd_DS_802_11_SNMP_MIB +{ + u16 QueryType; + u16 OID; + u16 BufSize; + u8 Value[128]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_SNMP_MIB; + +/** HostCmd_CMD_MAC_REG_ACCESS */ +typedef struct _HostCmd_DS_MAC_REG_ACCESS +{ + u16 Action; + u16 Offset; + u32 Value; +} __ATTRIB_PACK__ HostCmd_DS_MAC_REG_ACCESS; + +/** HostCmd_CMD_BBP_REG_ACCESS */ +typedef struct _HostCmd_DS_BBP_REG_ACCESS +{ + u16 Action; + u16 Offset; + u8 Value; + u8 Reserved[3]; +} __ATTRIB_PACK__ HostCmd_DS_BBP_REG_ACCESS; + +/** HostCmd_CMD_RF_REG_ACCESS */ +typedef struct _HostCmd_DS_RF_REG_ACCESS +{ + u16 Action; + u16 Offset; + u8 Value; + u8 Reserved[3]; +} __ATTRIB_PACK__ HostCmd_DS_RF_REG_ACCESS; + +/** HostCmd_CMD_802_11_RADIO_CONTROL */ +typedef struct _HostCmd_DS_802_11_RADIO_CONTROL +{ + u16 Action; + u16 Control; +} __ATTRIB_PACK__ HostCmd_DS_802_11_RADIO_CONTROL; + +/* HostCmd_DS_802_11_SLEEP_PARAMS */ +typedef struct _HostCmd_DS_802_11_SLEEP_PARAMS +{ + /* ACT_GET/ACT_SET */ + u16 Action; + + /* Sleep clock error in ppm */ + u16 Error; + + /* Wakeup offset in usec */ + u16 Offset; + + /* Clock stabilization time in usec */ + u16 StableTime; + + /* Control periodic calibration */ + u8 CalControl; + + /* Control the use of external sleep clock */ + u8 ExternalSleepClk; + + /* Reserved field, should be set to zero */ + u16 Reserved; +} __ATTRIB_PACK__ HostCmd_DS_802_11_SLEEP_PARAMS; + +/* HostCmd_DS_802_11_SLEEP_PERIOD */ +typedef struct _HostCmd_DS_802_11_SLEEP_PERIOD +{ + /* ACT_GET/ACT_SET */ + u16 Action; + + /* Sleep Period in msec */ + u16 Period; +} __ATTRIB_PACK__ HostCmd_DS_802_11_SLEEP_PERIOD; + +/* HostCmd_DS_802_11_BCA_TIMESHARE */ +typedef struct _HostCmd_DS_802_11_BCA_TIMESHARE +{ + /* ACT_GET/ACT_SET */ + u16 Action; + + /* Type: WLAN, BT */ + u16 TrafficType; + + /* 20msec - 60000msec */ + u32 TimeShareInterval; + + /* PTA arbiter time in msec */ + u32 BTTime; +} __ATTRIB_PACK__ HostCmd_DS_802_11_BCA_TIMESHARE; + +/* HostCmd_DS_802_11_INACTIVITY_TIMEOUT */ +typedef struct _HostCmd_DS_802_11_INACTIVITY_TIMEOUT +{ + /* ACT_GET/ACT_SET */ + u16 Action; + + /* Inactivity timeout in msec */ + u16 Timeout; +} __ATTRIB_PACK__ HostCmd_DS_802_11_INACTIVITY_TIMEOUT; + +/** HostCmd_CMD_802_11_RF_CHANNEL */ +typedef struct _HostCmd_DS_802_11_RF_CHANNEL +{ + u16 Action; + u16 CurrentChannel; + u16 RFType; + u16 Reserved; + u8 ChannelList[32]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_RF_CHANNEL; + +/** HostCmd_CMD_802_11_RSSI */ +typedef struct _HostCmd_DS_802_11_RSSI +{ + /* weighting factor */ + u16 N; + + u16 Reserved_0; + u16 Reserved_1; + u16 Reserved_2; +} __ATTRIB_PACK__ HostCmd_DS_802_11_RSSI; + +/** HostCmd_DS_802_11_RSSI_RSP */ +typedef struct _HostCmd_DS_802_11_RSSI_RSP +{ + u16 SNR; + u16 NoiseFloor; + u16 AvgSNR; + u16 AvgNoiseFloor; +} __ATTRIB_PACK__ HostCmd_DS_802_11_RSSI_RSP; + +/** HostCmd_DS_802_11_MAC_ADDRESS */ +typedef struct _HostCmd_DS_802_11_MAC_ADDRESS +{ + u16 Action; + u8 MacAdd[ETH_ALEN]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_MAC_ADDRESS; + +/** HostCmd_CMD_802_11_RF_TX_POWER */ +typedef struct _HostCmd_DS_802_11_RF_TX_POWER +{ + u16 Action; + u16 CurrentLevel; + u8 MaxPower; + u8 MinPower; +} __ATTRIB_PACK__ HostCmd_DS_802_11_RF_TX_POWER; + +/** HostCmd_CMD_802_11_RF_ANTENNA */ +typedef struct _HostCmd_DS_802_11_RF_ANTENNA +{ + u16 Action; + + /* Number of antennas or 0xffff(diversity) */ + u16 AntennaMode; + +} __ATTRIB_PACK__ HostCmd_DS_802_11_RF_ANTENNA; + +/** HostCmd_CMD_802_11_PS_MODE */ +typedef struct _HostCmd_DS_802_11_PS_MODE +{ + u16 Action; + u16 NullPktInterval; + u16 MultipleDtim; + u16 BCNMissTimeOut; + u16 LocalListenInterval; + u16 AdhocAwakePeriod; +} __ATTRIB_PACK__ HostCmd_DS_802_11_PS_MODE; + +/** PS_CMD_ConfirmSleep */ +typedef struct _PS_CMD_ConfirmSleep +{ + u16 Command; + u16 Size; + u16 SeqNum; + u16 Result; + + u16 Action; + u16 Reserved1; + u16 MultipleDtim; + u16 Reserved; + u16 LocalListenInterval; +} __ATTRIB_PACK__ PS_CMD_ConfirmSleep, *PPS_CMD_ConfirmSleep; + +/** HostCmd_CMD_802_11_FW_WAKE_METHOD */ +typedef struct _HostCmd_DS_802_11_FW_WAKEUP_METHOD +{ + u16 Action; + u16 Method; +} __ATTRIB_PACK__ HostCmd_DS_802_11_FW_WAKEUP_METHOD; + +/** HostCmd_DS_802_11_RATE_ADAPT_RATESET */ +typedef struct _HostCmd_DS_802_11_RATE_ADAPT_RATESET +{ + u16 Action; + u16 HWRateDropMode; + u16 Bitmap; + u16 Threshold; + u16 FinalRate; +} __ATTRIB_PACK__ HostCmd_DS_802_11_RATE_ADAPT_RATESET; + +/** HostCmd_DS_802_11_AD_HOC_START*/ +typedef struct _HostCmd_DS_802_11_AD_HOC_START +{ + u8 SSID[MRVDRV_MAX_SSID_LENGTH]; + u8 BSSType; + u16 BeaconPeriod; + u8 DTIMPeriod; + IEEEtypes_SsParamSet_t SsParamSet; + IEEEtypes_PhyParamSet_t PhyParamSet; + u16 Reserved1; + IEEEtypes_CapInfo_t Cap; + u8 DataRate[HOSTCMD_SUPPORTED_RATES]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_AD_HOC_START; + +/** AdHoc_BssDesc_t */ +typedef struct _AdHoc_BssDesc_t +{ + u8 BSSID[6]; + u8 SSID[32]; + u8 BSSType; + u16 BeaconPeriod; + u8 DTIMPeriod; + u8 TimeStamp[8]; + u8 LocalTime[8]; + IEEEtypes_PhyParamSet_t PhyParamSet; + IEEEtypes_SsParamSet_t SsParamSet; + IEEEtypes_CapInfo_t Cap; + u8 DataRates[HOSTCMD_SUPPORTED_RATES]; + + /* DO NOT ADD ANY FIELDS TO THIS STRUCTURE. It is used below in the + * Adhoc join command and will cause a binary layout mismatch with + * the firmware + */ +} __ATTRIB_PACK__ AdHoc_BssDesc_t; + +/** HostCmd_DS_802_11_AD_HOC_JOIN */ +typedef struct _HostCmd_DS_802_11_AD_HOC_JOIN +{ + AdHoc_BssDesc_t BssDescriptor; + u16 Reserved1; + u16 Reserved2; + +} __ATTRIB_PACK__ HostCmd_DS_802_11_AD_HOC_JOIN; + +typedef union _KeyInfo_WEP_t +{ + u8 Reserved; + + /* bits 1-4: Specifies the index of key */ + u8 WepKeyIndex; + + /* bit 0: Specifies that this key is + * to be used as the default for TX data packets + * */ + u8 isWepDefaultKey; +} __ATTRIB_PACK__ KeyInfo_WEP_t; + +typedef union _KeyInfo_TKIP_t +{ + u8 Reserved; + + /* bit 2: Specifies that this key is + * enabled and valid to use */ + u8 isKeyEnabled; + + /* bit 1: Specifies that this key is + * to be used as the unicast key */ + u8 isUnicastKey; + + /* bit 0: Specifies that this key is + * to be used as the multicast key */ + u8 isMulticastKey; +} __ATTRIB_PACK__ KeyInfo_TKIP_t; + +typedef union _KeyInfo_AES_t +{ + u8 Reserved; + + /* bit 2: Specifies that this key is + * enabled and valid to use */ + u8 isKeyEnabled; + + /* bit 1: Specifies that this key is + * to be used as the unicast key */ + u8 isUnicastKey; + + /* bit 0: Specifies that this key is + * to be used as the multicast key */ + u8 isMulticastKey; +} __ATTRIB_PACK__ KeyInfo_AES_t; + +/** KeyMaterial_TKIP_t */ +typedef struct _KeyMaterial_TKIP_t +{ + /* TKIP encryption/decryption key */ + u8 TkipKey[16]; + + /* TKIP TX MIC Key */ + u8 TkipTxMicKey[16]; + + /* TKIP RX MIC Key */ + u8 TkipRxMicKey[16]; +} __ATTRIB_PACK__ KeyMaterial_TKIP_t, *PKeyMaterial_TKIP_t; + +/** KeyMaterial_AES_t */ +typedef struct _KeyMaterial_AES_t +{ + /* AES encryption/decryption key */ + u8 AesKey[16]; +} __ATTRIB_PACK__ KeyMaterial_AES_t, *PKeyMaterial_AES_t; + +/** MrvlIEtype_KeyParamSet_t */ +typedef struct _MrvlIEtype_KeyParamSet_t +{ + /* Type ID */ + u16 Type; + + /* Length of Payload */ + u16 Length; + + /* Type of Key: WEP=0, TKIP=1, AES=2 */ + u16 KeyTypeId; + + /* Key Control Info specific to a KeyTypeId */ + u16 KeyInfo; + + /* Length of key */ + u16 KeyLen; + + /* Key material of size KeyLen */ + u8 Key[32]; +} __ATTRIB_PACK__ MrvlIEtype_KeyParamSet_t, *PMrvlIEtype_KeyParamSet_t; + +/** HostCmd_DS_802_11_KEY_MATERIAL */ +typedef struct _HostCmd_DS_802_11_KEY_MATERIAL +{ + u16 Action; + + MrvlIEtype_KeyParamSet_t KeyParamSet; +} __ATTRIB_PACK__ HostCmd_DS_802_11_KEY_MATERIAL; + +/** HostCmd_DS_802_11_HOST_SLEEP_CFG */ +typedef struct _HostCmd_DS_HOST_802_11_HOST_SLEEP_CFG +{ + /* bit0=1: non-unicast data + * bit1=1: unicast data + * bit2=1: mac events + * bit3=1: magic packet + */ + u32 conditions; + + u8 gpio; + + /* in milliseconds */ + u8 gap; +} __ATTRIB_PACK__ HostCmd_DS_802_11_HOST_SLEEP_CFG; + +#define CAL_DATA_HEADER_LEN 6 /* sizeof(HostCmd_DS_802_11_CAL_DATA_EXT)-sizeof(CalData) */ + +/** HostCmd_DS_802_11_CAL_DATA_EXT */ +typedef struct _HostCmd_DS_802_11_CAL_DATA_EXT +{ + u16 Action; + u16 Revision; + u16 CalDataLen; + u8 CalData[1]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_CAL_DATA_EXT; + +/** HostCmd_DS_802_11_EEPROM_ACCESS */ +typedef struct _HostCmd_DS_802_11_EEPROM_ACCESS +{ + u16 Action; + + /* multiple 4 */ + u16 Offset; + u16 ByteCount; + u8 Value; +} __ATTRIB_PACK__ HostCmd_DS_802_11_EEPROM_ACCESS; + +/** HostCmd_DS_802_11_BG_SCAN_CONFIG */ +typedef struct _HostCmd_DS_802_11_BG_SCAN_CONFIG +{ + /** Action */ + u16 Action; + + /** + * Enable/Disable + * 0 - Disable + * 1 - Enable + */ + u8 Enable; + + /** + * bssType + * 1 - Infrastructure + * 2 - IBSS + * 3 - any + */ + u8 BssType; + + /** + * ChannelsPerScan + * Number of channels to scan during a single scanning opportunity + */ + u8 ChannelsPerScan; + + u8 Reserved1[3]; + + /** ScanInterval */ + u32 ScanInterval; + + /** + * StoreCondition + * - SSID Match + * - SSID Match & Exceed SNR Threshold + */ + u32 StoreCondition; + + /** + * ReportConditions + * - SSID Match + * - SSID Match & Exceed SNR Threshold + */ + u32 ReportConditions; + + u16 Reserved2; + + /* Attach TLV based parameters as needed: + * + * MrvlIEtypes_SsIdParamSet_t Set specific SSID filter + * MrvlIEtypes_ChanListParamSet_t Set the channels & channel params + * MrvlIEtypes_NumProbes_t Number of probes per SSID/broadcast + * MrvlIEtypes_WildCardSsIdParamSet_t Wildcard SSID matching patterns + * MrvlIEtypes_SnrThreshold_t SNR Threshold for match/report + */ + +} __ATTRIB_PACK__ HostCmd_DS_802_11_BG_SCAN_CONFIG; + +/** HostCmd_DS_802_11_BG_SCAN_QUERY */ +typedef struct _HostCmd_DS_802_11_BG_SCAN_QUERY +{ + u8 Flush; +} __ATTRIB_PACK__ HostCmd_DS_802_11_BG_SCAN_QUERY; + +/** HostCmd_DS_802_11_BG_SCAN_QUERY_RSP */ +typedef struct _HostCmd_DS_802_11_BG_SCAN_QUERY_RSP +{ + u32 ReportCondition; + HostCmd_DS_802_11_SCAN_RSP scanresp; +} __ATTRIB_PACK__ HostCmd_DS_802_11_BG_SCAN_QUERY_RSP; + +/** HostCmd_DS_802_11_TPC_CFG */ +typedef struct _HostCmd_DS_802_11_TPC_CFG +{ + u16 Action; + u8 Enable; + s8 P0; + s8 P1; + s8 P2; + u8 UseSNR; +} __ATTRIB_PACK__ HostCmd_DS_802_11_TPC_CFG; + +/** HostCmd_DS_802_11_LED_CTRL */ +typedef struct _HostCmd_DS_802_11_LED_CTRL +{ + u16 Action; /* 0 = ACT_GET; 1 = ACT_SET; */ + u16 LedNums; /* Numbers of LEDs supported */ + MrvlIEtypes_LedGpio_t LedGpio; + MrvlIEtypes_LedBehavior_t LedBehavior[1]; +} __ATTRIB_PACK__ HostCmd_DS_802_11_LED_CTRL; + +/** HostCmd_DS_802_11_POWER_ADAPT_CFG_EXT */ +typedef struct _HostCmd_DS_802_11_POWER_ADAPT_CFG_EXT +{ + /** Action */ + u16 Action; /* 0 = ACT_GET; 1 = ACT_SET; */ + u16 EnablePA; /* 0 = disable; 1 = enable; */ + MrvlIEtypes_PowerAdapt_Group_t PowerAdaptGroup; +} __ATTRIB_PACK__ HostCmd_DS_802_11_POWER_ADAPT_CFG_EXT; + +typedef struct _HostCmd_DS_SDIO_INT_CONFIG +{ + u16 Action; /* 0: get; 1: set */ + u16 Gpio_pin; + u16 Gpio_int_edge; /*1: failing edge; 0: rasing edge */ + u16 Gpio_pulse_width; /* in usec units */ +} __ATTRIB_PACK__ HostCmd_DS_SDIO_INT_CONFIG; + +typedef struct _HostCmd_DS_SDIO_PULL_CTRL +{ + u16 Action; /* 0: get; 1: set */ + u16 PullUp; /* the delay of pulling up in us */ + u16 PullDown; /* the delay of pulling down in us */ +} __ATTRIB_PACK__ HostCmd_DS_SDIO_PULL_CTRL; +typedef struct _HostCmd_DS_802_11_IBSS_Status +{ + u16 Action; + u16 Enable; + u8 BSSID[ETH_ALEN]; + u16 BeaconInterval; + u16 ATIMWindow; + u16 UseGRateProtection; +} __ATTRIB_PACK__ HostCmd_DS_802_11_IBSS_Status; + +typedef struct _HostCmd_TX_RATE_QUERY +{ + u16 TxRate; +} __ATTRIB_PACK__ HostCmd_TX_RATE_QUERY; + +/** HostCmd_DS_802_11_AUTO_TX */ +typedef struct _HostCmd_DS_802_11_AUTO_TX +{ + /** Action */ + u16 Action; /* 0 = ACT_GET; 1 = ACT_SET; */ + MrvlIEtypes_AutoTx_t AutoTx; +} __ATTRIB_PACK__ HostCmd_DS_802_11_AUTO_TX; + +/** HostCmd_MEM_ACCESS */ +typedef struct _HostCmd_DS_MEM_ACCESS +{ + /** Action */ + u16 Action; /* 0 = ACT_GET; 1 = ACT_SET; */ + u16 Reserved; + u32 Addr; + u32 Value; +} __ATTRIB_PACK__ HostCmd_DS_MEM_ACCESS; + +typedef struct +{ + u64 TsfValue; +} __ATTRIB_PACK__ HostCmd_DS_GET_TSF; + +#define LDO_INTERNAL 0 +#define LDO_EXTERNAL 1 + +typedef struct _HostCmd_DS_802_11_LDO_CONFIG +{ + u16 Action; /* 0 = ACT_GET; 1 = ACT_SET; */ + u16 PMSource; /* 0 = LDO_INTERNAL; 1 = LDO_EXTERNAL */ +} __ATTRIB_PACK__ HostCmd_DS_802_11_LDO_CONFIG; + +typedef struct _HostCmd_DS_VERSION_EXT +{ + u8 versionStrSel; + char versionStr[128]; +} __ATTRIB_PACK__ HostCmd_DS_VERSION_EXT; + +/** Define data structure for HostCmd_CMD_802_11D_DOMAIN_INFO */ +typedef struct _HostCmd_DS_802_11D_DOMAIN_INFO +{ + u16 Action; + MrvlIEtypes_DomainParamSet_t Domain; +} __ATTRIB_PACK__ HostCmd_DS_802_11D_DOMAIN_INFO; + +/** Define data structure for HostCmd_RET_802_11D_DOMAIN_INFO */ +typedef struct _HostCmd_DS_802_11D_DOMAIN_INFO_RSP +{ + u16 Action; + MrvlIEtypes_DomainParamSet_t Domain; +} __ATTRIB_PACK__ HostCmd_DS_802_11D_DOMAIN_INFO_RSP; + +typedef struct +{ + u32 PktInitCnt; + u32 PktSuccessCnt; + u32 TxAttempts; + u32 RetryFailure; + u32 ExpiryFailure; +} __ATTRIB_PACK__ HostCmd_DS_TX_PKT_STAT_Entry; + +typedef struct +{ + HostCmd_DS_TX_PKT_STAT_Entry StatEntry[HOSTCMD_SUPPORTED_RATES]; +} __ATTRIB_PACK__ HostCmd_DS_TX_PKT_STATS; + +/** _HostCmd_DS_COMMAND*/ +struct _HostCmd_DS_COMMAND +{ + + /** Command Header */ + u16 Command; + u16 Size; + u16 SeqNum; + u16 Result; + + /** Command Body */ + union + { + HostCmd_DS_GET_HW_SPEC hwspec; + HostCmd_DS_802_11_PS_MODE psmode; + HostCmd_DS_802_11_SCAN scan; + HostCmd_DS_802_11_SCAN_RSP scanresp; + HostCmd_DS_MAC_CONTROL macctrl; + HostCmd_DS_802_11_ASSOCIATE associate; + HostCmd_DS_802_11_ASSOCIATE_RSP associatersp; + HostCmd_DS_802_11_DEAUTHENTICATE deauth; + HostCmd_DS_802_11_SET_WEP wep; + HostCmd_DS_802_11_AD_HOC_START ads; + HostCmd_DS_802_11_AD_HOC_RESULT result; + HostCmd_DS_802_11_GET_LOG glog; + HostCmd_DS_802_11_SNMP_MIB smib; + HostCmd_DS_802_11_RF_TX_POWER txp; + HostCmd_DS_802_11_RF_ANTENNA rant; + HostCmd_DS_802_11_RATE_ADAPT_RATESET rateset; + HostCmd_DS_MAC_MULTICAST_ADR madr; + HostCmd_DS_802_11_AD_HOC_JOIN adj; + HostCmd_DS_802_11_RADIO_CONTROL radio; + HostCmd_DS_802_11_RF_CHANNEL rfchannel; + HostCmd_DS_802_11_RSSI rssi; + HostCmd_DS_802_11_RSSI_RSP rssirsp; + HostCmd_DS_802_11_AD_HOC_STOP adhoc_stop; + HostCmd_DS_802_11_MAC_ADDRESS macadd; + HostCmd_DS_802_11_KEY_MATERIAL keymaterial; + HostCmd_DS_MAC_REG_ACCESS macreg; + HostCmd_DS_BBP_REG_ACCESS bbpreg; + HostCmd_DS_RF_REG_ACCESS rfreg; + HostCmd_DS_802_11_CAL_DATA_EXT caldataext; + HostCmd_DS_802_11_HOST_SLEEP_CFG hostsleepcfg; + HostCmd_DS_802_11_EEPROM_ACCESS rdeeprom; + + HostCmd_DS_802_11D_DOMAIN_INFO domaininfo; + HostCmd_DS_802_11D_DOMAIN_INFO_RSP domaininforesp; + HostCmd_DS_802_11_BG_SCAN_CONFIG bgscancfg; + HostCmd_DS_802_11_BG_SCAN_QUERY bgscanquery; + HostCmd_DS_802_11_BG_SCAN_QUERY_RSP bgscanqueryresp; + HostCmd_DS_WMM_GET_STATUS getWmmStatus; + HostCmd_DS_WMM_ADDTS_REQ addTsReq; + HostCmd_DS_WMM_DELTS_REQ delTsReq; + HostCmd_DS_WMM_QUEUE_CONFIG queueConfig; + HostCmd_DS_WMM_QUEUE_STATS queueStats; + HostCmd_DS_TX_PKT_STATS txPktStats; + HostCmd_DS_802_11_SLEEP_PARAMS sleep_params; + HostCmd_DS_802_11_BCA_TIMESHARE bca_timeshare; + HostCmd_DS_802_11_INACTIVITY_TIMEOUT inactivity_timeout; + HostCmd_DS_802_11_SLEEP_PERIOD ps_sleeppd; + HostCmd_DS_802_11_TPC_CFG tpccfg; + HostCmd_DS_802_11_LED_CTRL ledgpio; + HostCmd_DS_802_11_FW_WAKEUP_METHOD fwwakeupmethod; + + HostCmd_TX_RATE_QUERY txrate; + HostCmd_DS_GET_TSF gettsf; + HostCmd_DS_802_11_IBSS_Status ibssCoalescing; + HostCmd_DS_SDIO_INT_CONFIG sdio_int; + HostCmd_DS_SDIO_PULL_CTRL sdiopullctl; + HostCmd_DS_802_11_LDO_CONFIG ldocfg; + HostCmd_DS_VERSION_EXT verext; + } params; +} __ATTRIB_PACK__; + +#endif diff --git a/drivers/net/wireless/marvell8686/if_sdio.c b/drivers/net/wireless/marvell8686/if_sdio.c new file mode 100644 index 0000000..2384e05 --- /dev/null +++ b/drivers/net/wireless/marvell8686/if_sdio.c @@ -0,0 +1,1379 @@ +/** @file if_sdio.c + * @brief This file contains SDIO IF (interface) module + * related functions. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/**************************************************** +Change log: + 10/14/05: add Doxygen format comments + 01/05/06: add kernel 2.6.x support + 01/23/06: add fw downlaod + 06/06/06: add macro SD_BLOCK_SIZE_FW_DL for firmware download + add macro ALLOC_BUF_SIZE for cmd resp/Rx data skb buffer allocation +****************************************************/ +#include +#include +#include + +#include "if_sdio.h" + +//#undef PRINTM +//#define PRINTM(INFO, msg...) printk(msg) + +/* define SD block size for firmware download */ +#define SD_BLOCK_SIZE_FW_DL 32 + +/* define SD block size for data Tx/Rx */ +#define SD_BLOCK_SIZE 320 /* To minimize the overhead of ethernet frame + with 1514 bytes, 320 bytes block size is used */ + +#define ALLOC_BUF_SIZE (((MAX(MRVDRV_ETH_RX_PACKET_BUFFER_SIZE, \ + MRVDRV_SIZE_OF_CMD_BUFFER) + SDIO_HEADER_LEN \ + + SD_BLOCK_SIZE - 1) / SD_BLOCK_SIZE) * SD_BLOCK_SIZE) + +/* Max retry number of CMD53 write */ +#define MAX_WRITE_IOMEM_RETRY 2 + +/******************************************************** + Local Variables +********************************************************/ + +/******************************************************** + Global Variables +********************************************************/ + +struct if_sdio_card { + struct sdio_func *func; + wlan_private *priv; + int model; + + u8 int_cause; + + u8 chiprev; + u8 async_int_mode; + u8 block_size_512; + card_capability info; +}; + +extern wlan_private *wlanpriv; +const char *helper_name; +const char *fw_name; +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function adds the card + * + * @param card A pointer to the card + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +sbi_add_card(void *card) +{ + struct if_sdio_card *sdio_card = (struct if_sdio_card*)card; + sdio_card->priv = wlan_add_card(card); + if (sdio_card->priv) + return WLAN_STATUS_SUCCESS; + else + return WLAN_STATUS_FAILURE; +} + +/** + * @brief This function removes the card + * + * @param card A pointer to the card + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +sbi_remove_card(void *card) +{ + return wlan_remove_card(card); +} + +/** + * @brief This function reads scratch registers + * + * @param priv A pointer to wlan_private structure + * @param dat A pointer to keep returned data + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +mv_sdio_read_scratch(wlan_private * priv, u16 * dat) +{ + int ret = WLAN_STATUS_SUCCESS; + u8 scr0; + u8 scr1; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + scr0 = sdio_readb(card->func, CARD_OCR_0_REG, &ret); + if (ret) + return WLAN_STATUS_FAILURE; + + scr1 = sdio_readb(card->func, CARD_OCR_1_REG, &ret); + PRINTM(INFO, "CARD_OCR_0_REG = 0x%x, CARD_OCR_1_REG = 0x%x\n", scr0, + scr1); + if (ret) + return WLAN_STATUS_FAILURE; + + *dat = (((u16) scr1) << 8) | scr0; + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function polls the card status register. + * + * @param priv A pointer to wlan_private structure + * @param bits the bit mask + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +mv_sdio_poll_card_status(wlan_private * priv, u8 bits) +{ + int tries; + int rval; + u8 cs; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + cs = sdio_readb(card->func, CARD_STATUS_REG, &rval); + if (rval == 0 && (cs & bits) == bits) { + return WLAN_STATUS_SUCCESS; + } + + mdelay(1); + } + + PRINTM(WARN, "mv_sdio_poll_card_status: FAILED!:%d\n", rval); + return WLAN_STATUS_FAILURE; +} + +/** + * @brief This function programs the firmware image. + * + * @param priv A pointer to wlan_private structure + * @param firmware A pointer to the buffer of firmware image + * @param firmwarelen the length of firmware image + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +sbi_prog_firmware_image(wlan_private * priv, + const u8 * firmware, int firmwarelen) +{ + int ret = WLAN_STATUS_SUCCESS; + u16 firmwarestat; + u8 *fwbuf = priv->adapter->TmpTxBuf; + int fwblknow; + u32 tx_len; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; +#ifdef FW_DOWNLOAD_SPEED + u32 tv1, tv2; +#endif + + ENTER(); + + sdio_claim_host(card->func); + ret = sdio_set_block_size(card->func, SD_BLOCK_SIZE_FW_DL); + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if ((ret = mv_sdio_read_scratch(priv, &firmwarestat)) < 0) { + PRINTM(INFO, "read scratch returned <0\n"); + goto done; + } + + if (firmwarestat == FIRMWARE_READY) { + PRINTM(INFO, "FW already downloaded!\n"); + ret = WLAN_STATUS_SUCCESS; + goto done; + } + + PRINTM(INFO, "Downloading helper image (%d bytes), block size %d bytes\n", + firmwarelen, SD_BLOCK_SIZE_FW_DL); + +#ifdef FW_DOWNLOAD_SPEED + tv1 = get_utimeofday(); +#endif + /* Perform firmware data transfer */ + tx_len = + (FIRMWARE_TRANSFER_NBLOCK * SD_BLOCK_SIZE_FW_DL) - SDIO_HEADER_LEN; + for (fwblknow = 0; fwblknow < firmwarelen; fwblknow += tx_len) { + + /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY bits */ + ret = mv_sdio_poll_card_status(priv, CARD_IO_READY | DN_LD_CARD_RDY); + if (ret < 0) { + PRINTM(FATAL, "FW download died @ %d\n", fwblknow); + goto done; + } + + /* Set blocksize to transfer - checking for last block */ + if (firmwarelen - fwblknow < tx_len) + tx_len = firmwarelen - fwblknow; + + fwbuf[0] = ((tx_len & 0x000000ff) >> 0); /* Little-endian */ + fwbuf[1] = ((tx_len & 0x0000ff00) >> 8); + fwbuf[2] = ((tx_len & 0x00ff0000) >> 16); + fwbuf[3] = ((tx_len & 0xff000000) >> 24); + + /* Copy payload to buffer */ + memcpy(&fwbuf[SDIO_HEADER_LEN], &firmware[fwblknow], tx_len); + + PRINTM(INFO, "."); + + /* Send data */ + ret = sdio_writesb(card->func, priv->wlan_dev.ioport, + fwbuf, FIRMWARE_TRANSFER_NBLOCK * SD_BLOCK_SIZE_FW_DL); + + if (ret) { + PRINTM(FATAL, "IO error: transferring block @ %d\n", fwblknow); + goto done; + } + } + +#ifdef FW_DOWNLOAD_SPEED + tv2 = get_utimeofday(); + PRINTM(INFO, "helper: %ld.%03ld.%03ld ", tv1 / 1000000, + (tv1 % 1000000) / 1000, tv1 % 1000); + PRINTM(INFO, " -> %ld.%03ld.%03ld ", tv2 / 1000000, + (tv2 % 1000000) / 1000, tv2 % 1000); + tv2 -= tv1; + PRINTM(INFO, " == %ld.%03ld.%03ld\n", tv2 / 1000000, + (tv2 % 1000000) / 1000, tv2 % 1000); +#endif + + /* Write last EOF data */ + PRINTM(INFO, "\nTransferring EOF block\n"); + memset(fwbuf, 0x0, SD_BLOCK_SIZE_FW_DL); + ret = sdio_writesb(card->func, priv->wlan_dev.ioport, fwbuf, SD_BLOCK_SIZE_FW_DL); + + if (ret) { + PRINTM(FATAL, "IO error in writing EOF FW block\n"); + goto done; + } + + ret = WLAN_STATUS_SUCCESS; + +done: + sdio_set_block_size(card->func, 0); + sdio_release_host(card->func); + return ret; +} + +/** + * @brief This function downloads firmware image to the card. + * + * @param priv A pointer to wlan_private structure + * @param firmware A pointer to firmware image buffer + * @param firmwarelen the length of firmware image + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +sbi_download_wlan_fw_image(wlan_private * priv, + const u8 * firmware, int firmwarelen) +{ + u8 base0; + u8 base1; + int ret = WLAN_STATUS_SUCCESS; + int offset; + u8 *fwbuf = priv->adapter->TmpTxBuf; + int timeout = 5000; + u16 len; + int txlen = 0; + int tx_blocks = 0; + int i = 0; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; +#ifdef FW_DOWNLOAD_SPEED + u32 tv1, tv2; +#endif + + ENTER(); + + PRINTM(INFO, "Downloading FW image (%d bytes)\n", firmwarelen); + +#ifdef FW_DOWNLOAD_SPEED + tv1 = get_utimeofday(); +#endif + sdio_claim_host(card->func); + + ret = sdio_set_block_size(card->func, SD_BLOCK_SIZE_FW_DL); + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Wait initially for the first non-zero value */ + do { + base0 = sdio_readb(card->func, HOST_F1_RD_BASE_0, &ret); + if (ret) { + PRINTM(WARN, "Dev BASE0 register read failed:" + " base0=0x%04X(%d)\n", base0, base0); + ret = WLAN_STATUS_FAILURE; + goto done; + } + base1 = sdio_readb(card->func, HOST_F1_RD_BASE_1, &ret); + if (ret) { + PRINTM(WARN, "Dev BASE1 register read failed:" + " base1=0x%04X(%d)\n", base1, base1); + ret = WLAN_STATUS_FAILURE; + goto done; + } + len = (((u16) base1) << 8) | base0; + mdelay(1); + } while (!len && --timeout); + + if (!timeout) { + PRINTM(MSG, "Helper downloading finished.\n"); + PRINTM(MSG, "Timeout for FW downloading!\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY bits */ + ret = mv_sdio_poll_card_status(priv, CARD_IO_READY | DN_LD_CARD_RDY); + if (ret < 0) { + PRINTM(FATAL, "FW download died, helper not ready\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + len &= ~B_BIT_0; + + /* Perform firmware data transfer */ + for (offset = 0; offset < firmwarelen; offset += txlen) { + txlen = len; + + /* Set blocksize to transfer - checking for last block */ + if (firmwarelen - offset < txlen) { + txlen = firmwarelen - offset; + } + /* PRINTM(INFO, "fw: offset=%d, txlen = 0x%04X(%d)\n", + offset,txlen,txlen); */ + PRINTM(INFO, "."); + + tx_blocks = (txlen + SD_BLOCK_SIZE_FW_DL - 1) / SD_BLOCK_SIZE_FW_DL; + + /* Copy payload to buffer */ + memcpy(fwbuf, &firmware[offset], txlen); + + /* Send data */ + ret = sdio_writesb(card->func, priv->wlan_dev.ioport, + fwbuf, tx_blocks * SD_BLOCK_SIZE_FW_DL); + + if (ret) { + PRINTM(ERROR, "FW download, write iomem (%d) failed: %d\n", i, + ret); + sdio_writeb(card->func, 0x04, CONFIGURATION_REG, &ret); + if (ret) { + PRINTM(ERROR, "write ioreg failed (FN1 CFG)\n"); + } + } + + /* The host polls for the DN_LD_CARD_RDY and CARD_IO_READY bits */ + ret = mv_sdio_poll_card_status(priv, CARD_IO_READY | DN_LD_CARD_RDY); + if (ret < 0) { + PRINTM(FATAL, "FW download with helper died @ %d\n", offset); + goto done; + } + + base0 = sdio_readb(card->func, HOST_F1_RD_BASE_0, &ret); + if (ret) { + PRINTM(WARN, "Dev BASE0 register read failed:" + " base0=0x%04X(%d)\n", base0, base0); + ret = WLAN_STATUS_FAILURE; + goto done; + } + base1 = sdio_readb(card->func, HOST_F1_RD_BASE_1, &ret); + if (ret) { + PRINTM(WARN, "Dev BASE1 register read failed:" + " base1=0x%04X(%d)\n", base1, base1); + ret = WLAN_STATUS_FAILURE; + goto done; + } + len = (((u16) base1) << 8) | base0; + + if (!len) { + break; + } + + if (len & B_BIT_0) { + i++; + if (i > MAX_WRITE_IOMEM_RETRY) { + PRINTM(FATAL, "FW download failure, over max retry count\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + PRINTM(ERROR, "CRC error indicated by the helper:" + " len = 0x%04X, txlen = %d\n", len, txlen); + len &= ~B_BIT_0; + /* Setting this to 0 to resend from same offset */ + txlen = 0; + } + else + i = 0; + } + PRINTM(INFO, "\nFW download over, size %d bytes\n", firmwarelen); + + ret = WLAN_STATUS_SUCCESS; +done: + sdio_set_block_size(card->func, 0); + sdio_release_host(card->func); +#ifdef FW_DOWNLOAD_SPEED + tv2 = get_utimeofday(); + PRINTM(INFO, "FW: %ld.%03ld.%03ld ", tv1 / 1000000, + (tv1 % 1000000) / 1000, tv1 % 1000); + PRINTM(INFO, " -> %ld.%03ld.%03ld ", tv2 / 1000000, + (tv2 % 1000000) / 1000, tv2 % 1000); + tv2 -= tv1; + PRINTM(INFO, " == %ld.%03ld.%03ld\n", tv2 / 1000000, + (tv2 % 1000000) / 1000, tv2 % 1000); +#endif + LEAVE(); + return ret; +} + +/** + * @brief This function reads data from the card. + * + * @param priv A pointer to wlan_private structure + * @param type A pointer to keep type as data or command + * @param nb A pointer to keep the data/cmd length retured in buffer + * @param payload A pointer to the data/cmd buffer + * @param nb the length of data/cmd buffer + * @param npayload the length of data/cmd buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +mv_sdio_card_to_host(wlan_private * priv, + u32 * type, int *nb, u8 * payload, int npayload) +{ + int ret = WLAN_STATUS_SUCCESS; + u16 buf_len = 0; + int buf_block_len; + int blksz; + u32 *pevent; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + ENTER(); + + if (!payload) { + PRINTM(WARN, "payload NULL pointer received!\n"); + ret = WLAN_STATUS_FAILURE; + goto exit; + } + + /* Read the length of data to be transferred */ + ret = mv_sdio_read_scratch(priv, &buf_len); + if (ret < 0) { + PRINTM(ERROR, "card_to_host, read RX length failed\n"); + ret = WLAN_STATUS_FAILURE; + goto exit; + } + + if (buf_len <= SDIO_HEADER_LEN || buf_len > npayload) { + PRINTM(ERROR, "card_to_host, invalid packet length: %d\n", buf_len); + ret = WLAN_STATUS_FAILURE; + goto exit; + } + + ret = mv_sdio_poll_card_status(priv, CARD_IO_READY); + if (ret < 0) { + PRINTM(FATAL, "MV card status fail\n"); + goto exit; + } + + /* Allocate buffer */ + blksz = SD_BLOCK_SIZE; + buf_block_len = (buf_len + blksz - 1) / blksz; + + ret = sdio_set_block_size(card->func, blksz); + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto exit; + } + + ret = sdio_readsb(card->func, payload, priv->wlan_dev.ioport, buf_block_len * blksz); + + if (ret) { + PRINTM(ERROR, "card_to_host, read iomem failed: %d\n", ret); + ret = WLAN_STATUS_FAILURE; + goto exit; + } + *nb = buf_len; + + DBG_HEXDUMP(IF_D, "SDIO Blk Rd", payload, blksz * buf_block_len); + + *type = (payload[2] | (payload[3] << 8)); + if (*type == MVSD_EVENT) { + pevent = (u32 *) & payload[4]; + priv->adapter->EventCause = MVSD_EVENT | (((u16) (*pevent)) << 3); + } + +exit: + sdio_set_block_size(card->func, 0); + LEAVE(); + return ret; +} + +/** + * @brief This function enables the host interrupts mask + * + * @param priv A pointer to wlan_private structure + * @param mask the interrupt mask + * @return WLAN_STATUS_SUCCESS + */ +static int +enable_host_int_mask(wlan_private * priv, u8 mask) +{ + int ret = WLAN_STATUS_SUCCESS; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + sdio_claim_host(card->func); + /* Simply write the mask to the register */ + sdio_writeb(card->func, mask, HOST_INT_MASK_REG, &ret); + sdio_release_host(card->func); + + if (ret) { + PRINTM(WARN, "ret = %d\n", ret); + ret = WLAN_STATUS_FAILURE; + } + + priv->adapter->HisRegCpy = 1; + + return ret; +} + +/** @brief This function disables the host interrupts mask. + * + * @param priv A pointer to wlan_private structure + * @param mask the interrupt mask + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +disable_host_int_mask(wlan_private * priv, u8 mask) +{ + int ret = WLAN_STATUS_SUCCESS; + u8 host_int_mask; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + sdio_claim_host(card->func); + /* Read back the host_int_mask register */ + host_int_mask = sdio_readb(card->func, HOST_INT_MASK_REG, &ret); + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Update with the mask and write back to the register */ + host_int_mask &= ~mask; + sdio_writeb(card->func, host_int_mask, HOST_INT_MASK_REG, &ret); + if (ret) { + PRINTM(WARN, "Unable to diable the host interrupt!\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + +done: + sdio_release_host(card->func); + return ret; +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function handles the interrupt. + * + * @param irq The irq of device. + * @param dev_id A pointer to net_device structure + * @param fp A pointer to pt_regs structure + * @return n/a + */ +static void sbi_interrupt(struct sdio_func *func) +{ + int ret = WLAN_STATUS_SUCCESS; + u8 sdio_ireg = 0; + u8 *cmdBuf; + wlan_private *priv; + wlan_dev_t *wlan_dev; + struct sk_buff *skb; + struct if_sdio_card* card = NULL; + ENTER(); + + priv = wlanpriv; + wlan_dev = &priv->wlan_dev; + card = (struct if_sdio_card*)wlan_dev->card; + + sdio_ireg = sdio_readb(func, HOST_INTSTATUS_REG, &ret); + if (ret) { + PRINTM(WARN, "sdio_read_ioreg: read int status register failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + sdio_writeb(func, (~sdio_ireg) & (DN_LD_HOST_INT_STATUS | UP_LD_HOST_INT_STATUS), + HOST_INTSTATUS_REG, &ret); + if (ret) { + PRINTM(WARN, "sdio_write_ioreg: clear int status register failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if (sdio_ireg & DN_LD_HOST_INT_STATUS) { /* tx_done INT */ + if (!priv->wlan_dev.dnld_sent) { /* tx_done already received */ + PRINTM(INFO, "warning: tx_done already received:" + " dnld_sent=0x%x int status=0x%x\n", + priv->wlan_dev.dnld_sent, sdio_ireg); + } + else { + wmm_process_fw_iface_tx_xfer_end(priv); + priv->wlan_dev.dnld_sent = DNLD_RES_RECEIVED; + } + OS_INT_DISABLE; + card->int_cause |= HIS_TxDnLdRdy; + wlan_interrupt(wlan_dev->netdev); + OS_INT_RESTORE; + } + + if (sdio_ireg & UP_LD_HOST_INT_STATUS) { + + /* + * DMA read data is by block alignment,so we need alloc extra block + * to avoid wrong memory access. + */ + if (!(skb = dev_alloc_skb(ALLOC_BUF_SIZE))) { + PRINTM(WARN, "No free skb\n"); + priv->stats.rx_dropped++; + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* + * Transfer data from card + * skb->tail is passed as we are calling skb_put after we + * are reading the data + */ + if (mv_sdio_card_to_host(priv, &wlan_dev->upld_typ, + (int *) &wlan_dev->upld_len, skb->tail, + ALLOC_BUF_SIZE) < 0) { + u8 cr = 0; + + PRINTM(ERROR, "Card to host failed: int status=0x%x\n", + sdio_ireg); + cr = sdio_readb(func, CONFIGURATION_REG, &ret); + if (ret) + PRINTM(ERROR, "read ioreg failed (FN1 CFG)\n"); + + PRINTM(INFO, "Config Reg val = %d\n", cr); + sdio_writeb(func, cr | 0x04, CONFIGURATION_REG, &ret); + if (ret) + PRINTM(ERROR, "write ioreg failed (FN1 CFG)\n"); + + PRINTM(INFO, "write success\n"); + cr = sdio_readb(func, CONFIGURATION_REG, &ret); + if (ret) + PRINTM(ERROR, "read ioreg failed (FN1 CFG)\n"); + + PRINTM(INFO, "Config reg val =%x\n", cr); + ret = WLAN_STATUS_FAILURE; + kfree_skb(skb); + goto done; + } + + OS_INT_DISABLE; + switch (wlan_dev->upld_typ) { + case MVSD_DAT: + PRINTM(DATA, "Data <= FW\n"); + card->int_cause |= HIS_RxUpLdRdy; + skb_put(skb, priv->wlan_dev.upld_len); + skb_pull(skb, SDIO_HEADER_LEN); + list_add_tail((struct list_head *) skb, + (struct list_head *) &priv->adapter->RxSkbQ); + /* skb will be freed by kernel later */ + break; + + case MVSD_CMD: + PRINTM(DATA, "CMD\n"); + + /* take care of CurCmd = NULL case */ + if (!priv->adapter->CurCmd) { + cmdBuf = priv->wlan_dev.upld_buf; + } + else { + cmdBuf = priv->adapter->CurCmd->BufVirtualAddr; + } + + priv->wlan_dev.upld_len -= SDIO_HEADER_LEN; + memcpy(cmdBuf, skb->data + SDIO_HEADER_LEN, + MIN(MRVDRV_SIZE_OF_CMD_BUFFER, priv->wlan_dev.upld_len)); + kfree_skb(skb); + card->int_cause |= HIS_CmdUpLdRdy; + break; + + case MVSD_EVENT: + /* event cause has been saved to priv->adapter->EventCause */ + kfree_skb(skb); + card->int_cause |= HIS_CardEvent; + break; + + default: + PRINTM(ERROR, "SDIO unknown upld type = 0x%x\n", + wlan_dev->upld_typ); + kfree_skb(skb); + break; + } + wlan_interrupt(wlan_dev->netdev); + OS_INT_RESTORE; + } + + ret = WLAN_STATUS_SUCCESS; +done: + LEAVE(); + return; +} + +/** + * @brief This function reads the IO register. + * + * @param priv A pointer to wlan_private structure + * @param func funcion number + * @param reg register to be read + * @param dat A pointer to variable that keeps returned value + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_read_ioreg(wlan_private * priv, u8 func, u32 reg, u8 * dat) +{ + int ret; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + sdio_claim_host(card->func); + if (func == 0) + *dat = sdio_f0_readb(card->func, reg, &ret); + else + *dat = sdio_readb(card->func, reg, &ret); + sdio_release_host(card->func); + return ret; +} + +/** + * @brief This function writes the IO register. + * + * @param priv A pointer to wlan_private structure + * @param func funcion number + * @param reg register to be written + * @param dat the value to be written + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_write_ioreg(wlan_private * priv, u8 func, u32 reg, u8 dat) +{ + int ret; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + sdio_claim_host(card->func); + if (func == 0) + sdio_f0_writeb(card->func, dat, reg, &ret); + else + sdio_writeb(card->func, dat, reg, &ret); + sdio_release_host(card->func); + return ret; +} + +/** + * @brief This function checks the interrupt status and handle it accordingly. + * + * @param priv A pointer to wlan_private structure + * @param ireg A pointer to variable that keeps returned value + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_get_int_status(wlan_private * priv, u8 * ireg) +{ + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + OS_INT_DISABLE; + *ireg = card->int_cause; + card->int_cause = 0; + OS_INT_RESTORE; + return WLAN_STATUS_SUCCESS; + +} + +/** + * @brief This function is a dummy function. + * + * @return WLAN_STATUS_SUCCESS + */ +int +sbi_card_to_host(wlan_private * priv, u32 type, + u32 * nb, u8 * payload, u16 npayload) +{ + return WLAN_STATUS_SUCCESS; +} + +/** + * @return WLAN_STATUS_SUCCESS + */ +int +sbi_read_event_cause(wlan_private * priv) +{ + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function disables the host interrupts. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_disable_host_int(wlan_private * priv) +{ + return disable_host_int_mask(priv, HIM_DISABLE); +} + +/** + * @brief This function enables the host interrupts. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS + */ +int +sbi_enable_host_int(wlan_private * priv) +{ + return enable_host_int_mask(priv, HIM_ENABLE); +} + +/** + * @brief This function de-registers the device. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS + */ +int +sbi_unregister_dev(wlan_private * priv) +{ + ENTER(); + + if (priv->wlan_dev.card != NULL) { + /* Release the SDIO IRQ */ + PRINTM(WARN, "Making the sdio dev card as NULL\n"); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function registers the device. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_register_dev(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + u8 reg; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + ENTER(); + + /* Initialize the private structure */ + strncpy(priv->wlan_dev.name, "sdio0", sizeof(priv->wlan_dev.name)); + priv->wlan_dev.ioport = 0; + priv->wlan_dev.upld_rcv = 0; + priv->wlan_dev.upld_typ = 0; + priv->wlan_dev.upld_len = 0; + + sdio_claim_host(card->func); + /* Read the IO port */ + reg = sdio_readb(card->func, IO_PORT_0_REG, &ret); + if (ret) + goto failed; + else + priv->wlan_dev.ioport |= reg; + + reg = sdio_readb(card->func, IO_PORT_1_REG, &ret); + if (ret) + goto failed; + else + priv->wlan_dev.ioport |= (reg << 8); + + reg = sdio_readb(card->func, IO_PORT_2_REG, &ret); + if (ret) + goto failed; + else + priv->wlan_dev.ioport |= (reg << 16); + sdio_release_host(card->func); + + PRINTM(INFO, "SDIO FUNC1 IO port: 0x%x\n", priv->wlan_dev.ioport); + + /* Disable host interrupt first. */ + if ((ret = disable_host_int_mask(priv, 0xff)) < 0) { + PRINTM(WARN, "Warning: unable to disable host interrupt!\n"); + } + + priv->adapter->chip_rev = card->chiprev; + priv->adapter->sdiomode = 4; + + return WLAN_STATUS_SUCCESS; + +failed: + sdio_release_host(card->func); + priv->wlan_dev.card = NULL; + + return WLAN_STATUS_FAILURE; +} + +/** + * @brief This function sends data to the card. + * + * @param priv A pointer to wlan_private structure + * @param type data or command + * @param payload A pointer to the data/cmd buffer + * @param nb the length of data/cmd + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_host_to_card(wlan_private * priv, u8 type, u8 * payload, u16 nb) +{ + int ret = WLAN_STATUS_SUCCESS; + int buf_block_len; + int blksz; + int i = 0; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + ENTER(); + + sdio_claim_host(card->func); + priv->adapter->HisRegCpy = 0; + + /* Allocate buffer and copy payload */ + blksz = SD_BLOCK_SIZE; + buf_block_len = (nb + SDIO_HEADER_LEN + blksz - 1) / blksz; + + /* This is SDIO specific header + * length: byte[1][0], + * type: byte[3][2] (MVSD_DAT = 0, MVSD_CMD = 1, MVSD_EVENT = 3) + */ + priv->adapter->TmpTxBuf[0] = (nb + SDIO_HEADER_LEN) & 0xff; + priv->adapter->TmpTxBuf[1] = ((nb + SDIO_HEADER_LEN) >> 8) & 0xff; + priv->adapter->TmpTxBuf[2] = type; + priv->adapter->TmpTxBuf[3] = 0x0; + + if (payload != NULL && + (nb > 0 && + nb <= (sizeof(priv->adapter->TmpTxBuf) - SDIO_HEADER_LEN))) { + if (type == MVMS_CMD) + memcpy(&priv->adapter->TmpTxBuf[SDIO_HEADER_LEN], payload, nb); + } + else { + PRINTM(WARN, "sbi_host_to_card(): Error: payload=%p, nb=%d\n", + payload, nb); + } + + if (type == MVSD_DAT) + priv->wlan_dev.dnld_sent = DNLD_DATA_SENT; + else + priv->wlan_dev.dnld_sent = DNLD_CMD_SENT; + + ret = sdio_set_block_size(card->func, blksz); + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto exit; + } + + do { + /* Transfer data to card */ + ret = sdio_writesb(card->func, priv->wlan_dev.ioport, + priv->adapter->TmpTxBuf, blksz * buf_block_len); + if (ret) { + i++; + + PRINTM(ERROR, "host_to_card, write iomem (%d) failed: %d\n", i, + ret); + sdio_writeb(card->func, 0x04, CONFIGURATION_REG, &ret); + if (ret) { + PRINTM(ERROR, "write ioreg failed (FN1 CFG)\n"); + } + ret = WLAN_STATUS_FAILURE; + if (i > MAX_WRITE_IOMEM_RETRY) { + priv->wlan_dev.dnld_sent = DNLD_RES_RECEIVED; + goto exit; + } + } + else { + DBG_HEXDUMP(IF_D, "SDIO Blk Wr", priv->adapter->TmpTxBuf, + blksz * buf_block_len); + } + } while (ret == WLAN_STATUS_FAILURE); + +exit: + sdio_set_block_size(card->func, 0); + sdio_release_host(card->func); + LEAVE(); + return ret; +} + +/** + * @brief This function reads CIS informaion. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_get_cis_info(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + u8 tupledata[255]; + ENTER(); + + /* TODO using sdio tuple data */ + + /* Copy the CIS Table to Adapter */ + memset(Adapter->CisInfoBuf, 0x0, sizeof(Adapter->CisInfoBuf)); + memcpy(Adapter->CisInfoBuf, tupledata, sizeof(tupledata)); + Adapter->CisInfoLen = sizeof(tupledata); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function probes the card. + * + * @param card_p A pointer to the card + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_probe_card(void *card_p) +{ + struct if_sdio_card* card = (struct if_sdio_card*)card_p; + int ret = WLAN_STATUS_SUCCESS; + + if (!card) { + ret = -ENODEV; //WLAN_STATUS_FAILURE; + goto done; + } + + /* Check for MANFID */ + PRINTM(INFO, "Marvell SDIO card detected!\n"); + + sdio_claim_host(card->func); + /* read Revision Register to get the hw revision number */ + card->chiprev = sdio_readb(card->func, CARD_REVISION_REG, &ret); + if (ret) { + PRINTM(FATAL, "cannot read CARD_REVISION_REG\n"); + } + else { + PRINTM(INFO, "revision=0x%x\n", card->chiprev); + switch (card->chiprev) { + default: + card->block_size_512 = TRUE; + card->async_int_mode = TRUE; + break; + } + } + + ret = WLAN_STATUS_SUCCESS; +done: + sdio_release_host(card->func); + return ret; +} + +/** + * @brief This function calls sbi_download_wlan_fw_image to download + * firmware image to the card. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_prog_firmware_w_helper(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + if (Adapter->fmimage != NULL) { + return sbi_download_wlan_fw_image(priv, + Adapter->fmimage, + Adapter->fmimage_len); + } + else { + PRINTM(MSG, "No external FW image\n"); + return WLAN_STATUS_FAILURE; + } +} + +/** + * @brief This function programs helper image. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_prog_helper(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + if (Adapter->helper != NULL) { + return sbi_prog_firmware_image(priv, + Adapter->helper, Adapter->helper_len); + } + else { + PRINTM(MSG, "No external helper image\n"); + return WLAN_STATUS_FAILURE; + } +} + +/** + * @brief This function checks if the firmware is ready to accept + * command or not. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_verify_fw_download(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + u16 firmwarestat; + int tries; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + sdio_claim_host(card->func); + /* Wait for firmware initialization event */ + for (tries = 0; tries < MAX_FIRMWARE_POLL_TRIES; tries++) { + if ((ret = mv_sdio_read_scratch(priv, &firmwarestat)) < 0) + continue; + + if (firmwarestat == FIRMWARE_READY) { + ret = WLAN_STATUS_SUCCESS; + break; + } + else { + mdelay(10); + ret = WLAN_STATUS_FAILURE; + } + } + + if (ret < 0) { + PRINTM(MSG, "Timeout waiting for FW to become active\n"); + goto done; + } + + ret = WLAN_STATUS_SUCCESS; +done: + sdio_release_host(card->func); + return ret; +} + +/** + * @brief This function set bus clock on/off + * + * @param priv A pointer to wlan_private structure + * @param option TRUE--on , FALSE--off + * @return WLAN_STATUS_SUCCESS + */ +int +sbi_set_bus_clock(wlan_private * priv, u8 option) +{ +/* if (option == TRUE) + start_bus_clock(((mmc_card_t) ((priv->wlan_dev).card))->ctrlr); + else + stop_bus_clock_2(((mmc_card_t) ((priv->wlan_dev).card))->ctrlr); */ + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function makes firmware exiting from deep sleep. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_exit_deep_sleep(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + sbi_set_bus_clock(priv, TRUE); + + sdio_claim_host(card->func); + sdio_writeb(card->func, HOST_POWER_UP, CONFIGURATION_REG, &ret); + sdio_release_host(card->func); + + return ret; +} + +/** + * @brief This function resets the setting of deep sleep. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sbi_reset_deepsleep_wakeup(wlan_private * priv) +{ + + int ret = WLAN_STATUS_SUCCESS; + struct if_sdio_card* card = (struct if_sdio_card*)priv->wlan_dev.card; + + ENTER(); + + sdio_claim_host(card->func); + sdio_writeb(card->func, 0, CONFIGURATION_REG, &ret); + sdio_release_host(card->func); + + LEAVE(); + + return ret; +} + +static const struct sdio_device_id if_sdio_ids[] = { + { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_LIBERTAS)}, + { /* end: all zeroes */}, +}; + +MODULE_DEVICE_TABLE(sdio, if_sdio_ids); +struct if_sdio_model +{ + int model; + const char *helper; + const char *firmware; +}; + +static struct if_sdio_model if_sdio_models[] = { + { + /* 8686 */ + .model = 0x0B, + .helper = "/lib/firmware/sd8686_helper.bin", + .firmware = "/lib/firmware/sd8686.bin", + }, +}; + +static int if_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct if_sdio_card *card; + int ret, i; + unsigned int model; + + card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL); + + for (i = 0;i < func->card->num_info;i++) { + if (sscanf(func->card->info[i], + "802.11 SDIO ID: %x", &model) == 1) + break; + if (sscanf(func->card->info[i], + "ID: %x", &model) == 1) + break; + if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) { + model = 4; + break; + } + } + + if (i == func->card->num_info) { + printk("unable to identify card model\n"); + return -ENODEV; + } + + card->func = func; + card->model = model; + + for (i = 0;i < ARRAY_SIZE(if_sdio_models);i++) { + if (card->model == if_sdio_models[i].model) + break; + } + + if (i == ARRAY_SIZE(if_sdio_models)) { + printk("unknown card model 0x%x\n", card->model); + ret = -ENODEV; + goto free; + } + + helper_name = if_sdio_models[i].helper; + fw_name = if_sdio_models[i].firmware; + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if (ret) + goto release; + + sdio_writeb(func, 0x00, HOST_INT_MASK_REG, &ret); + if (ret) { + PRINTM(WARN, "Unable to diable the host interrupt!\n"); + goto reclaim; + } + + ret = sdio_claim_irq(func, sbi_interrupt); + if (ret) + goto disable; + + sdio_release_host(func); + + sdio_set_drvdata(func, card); + + ret = sbi_add_card(card); + if (ret) + goto reclaim; + +out: + return ret; + +reclaim: + sdio_claim_host(func); + sdio_release_irq(func); +disable: + sdio_disable_func(func); +release: + sdio_release_host(func); +free: + kfree(card); + goto out; +} + +static void if_sdio_remove(struct sdio_func *func) +{ + struct if_sdio_card *card = sdio_get_drvdata(func); + sbi_remove_card(card); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + kfree(card); + return; +} + +static struct sdio_driver if_sdio_driver = { + .name = "sd8686_sdio", + .id_table = if_sdio_ids, + .probe = if_sdio_probe, + .remove = if_sdio_remove, +}; + +static int if_sdio_init_module(void) +{ + int ret = 0; + + printk(KERN_INFO "8686 sdio: sd 8686 driver\n"); + printk(KERN_INFO "8686 sdio: Copyright HHCN 2009\n"); + + ret = sdio_register_driver(&if_sdio_driver); + + return ret; +} + +static void if_sdio_exit_module(void) +{ + sdio_unregister_driver(&if_sdio_driver); +} + +module_init(if_sdio_init_module); +module_exit(if_sdio_exit_module); + +MODULE_DESCRIPTION("Marvell SD8686 SDIO WLAN Driver"); +MODULE_AUTHOR("You Sheng"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wireless/marvell8686/if_sdio.h b/drivers/net/wireless/marvell8686/if_sdio.h new file mode 100644 index 0000000..d059ccf --- /dev/null +++ b/drivers/net/wireless/marvell8686/if_sdio.h @@ -0,0 +1,97 @@ +/** @file if_sdio.h + * @brief This file contains SDIO IF (interface) module + * related macros, enum, and structure. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/**************************************************** +Change log: + 10/12/05: add Doxygen format comments +****************************************************/ + +#ifndef _IF_SDIO_H_ +#define _IF_SDIO_H_ + +#include "include.h" +#include "sdio.h" + +#define SD_BUS_WIDTH_1 0x00 +#define SD_BUS_WIDTH_4 0x02 +#define SD_BUS_WIDTH_MASK 0x03 +#define ASYNC_INT_MODE 0x20 + +/* Host Control Registers */ +#define IO_PORT_0_REG 0x00 +#define IO_PORT_1_REG 0x01 +#define IO_PORT_2_REG 0x02 +#define CONFIGURATION_REG 0x03 +#define HOST_WO_CMD53_FINISH_HOST (0x1U << 2) +#define HOST_POWER_UP (0x1U << 1) +#define HOST_POWER_DOWN (0x1U << 0) +#define HOST_INT_MASK_REG 0x04 +#define UP_LD_HOST_INT_MASK (0x1U) +#define DN_LD_HOST_INT_MASK (0x2U) +#define HOST_INTSTATUS_REG 0x05 +#define UP_LD_HOST_INT_STATUS (0x1U) +#define DN_LD_HOST_INT_STATUS (0x2U) +#define HOST_INT_RSR_REG 0x06 +#define UP_LD_HOST_INT_RSR (0x1U) +#define HOST_INT_STATUS_REG 0x07 +#define UP_LD_CRC_ERR (0x1U << 2) +#define UP_LD_RESTART (0x1U << 1) +#define DN_LD_RESTART (0x1U << 0) + +/* Card Control Registers */ +#define SQ_READ_BASE_ADDRESS_A0_REG 0x10 +#define SQ_READ_BASE_ADDRESS_A1_REG 0x11 +#define SQ_READ_BASE_ADDRESS_A2_REG 0x12 +#define SQ_READ_BASE_ADDRESS_A3_REG 0x13 +#define SQ_READ_BASE_ADDRESS_B0_REG 0x14 +#define SQ_READ_BASE_ADDRESS_B1_REG 0x15 +#define SQ_READ_BASE_ADDRESS_B2_REG 0x16 +#define SQ_READ_BASE_ADDRESS_B3_REG 0x17 +#define CARD_STATUS_REG 0x20 +#define CARD_IO_READY (0x1U << 3) +#define CIS_CARD_RDY (0x1U << 2) +#define UP_LD_CARD_RDY (0x1U << 1) +#define DN_LD_CARD_RDY (0x1U << 0) +#define HOST_INTERRUPT_MASK_REG 0x24 +#define HOST_POWER_INT_MASK (0x1U << 3) +#define ABORT_CARD_INT_MASK (0x1U << 2) +#define UP_LD_CARD_INT_MASK (0x1U << 1) +#define DN_LD_CARD_INT_MASK (0x1U << 0) +#define CARD_INTERRUPT_STATUS_REG 0x28 +#define POWER_UP_INT (0x1U << 4) +#define POWER_DOWN_INT (0x1U << 3) +#define CARD_INTERRUPT_RSR_REG 0x2c +#define POWER_UP_RSR (0x1U << 4) +#define POWER_DOWN_RSR (0x1U << 3) +#define DEBUG_0_REG 0x30 +#define SD_TESTBUS0 (0x1U) +#define DEBUG_1_REG 0x31 +#define SD_TESTBUS1 (0x1U) +#define DEBUG_2_REG 0x32 +#define SD_TESTBUS2 (0x1U) +#define DEBUG_3_REG 0x33 +#define SD_TESTBUS3 (0x1U) +#define CARD_OCR_0_REG 0x34 +#define CARD_OCR_1_REG 0x35 +#define CARD_OCR_3_REG 0x36 +#define CARD_CONFIG_REG 0x38 +#define CARD_REVISION_REG 0x3c +#define CMD53_FINISH_GBUS (0x1U << 1) +#define SD_NEG_EDGE (0x1U << 0) + +/* Special registers in function 0 of the SDxx card */ +#define SCRATCH_0_REG 0x80fe +#define SCRATCH_1_REG 0x80ff + +#define HOST_F1_RD_BASE_0 0x0010 +#define HOST_F1_RD_BASE_1 0x0011 +#define HOST_F1_CARD_RDY 0x0020 + +/******************************************************** + Global Functions +********************************************************/ + +#endif /* _IF_SDIO_H */ diff --git a/drivers/net/wireless/marvell8686/include.h b/drivers/net/wireless/marvell8686/include.h new file mode 100644 index 0000000..6bf9abe --- /dev/null +++ b/drivers/net/wireless/marvell8686/include.h @@ -0,0 +1,42 @@ +/** @file include.h + * + * @brief This file contains all the necessary include file. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/11/05: Add Doxygen format comments + 01/11/06: Conditional include file removal/addition + 01/30/06: Add kernel 2.6 support + +********************************************************/ + +#ifndef _INCLUDE_H_ +#define _INCLUDE_H_ + +#include "os_headers.h" +#include "wlan_types.h" +#include "wlan_defs.h" +#include "wlan_thread.h" + +#include "wlan_wmm.h" +#include "wlan_11d.h" + +#include "os_timers.h" + +#include "host.h" +#include "hostcmd.h" + +#include "wlan_scan.h" +#include "wlan_join.h" + +#include "wlan_dev.h" +#include "os_macros.h" +#include "sbi.h" + +#include "sdio.h" + +#include "wlan_wext.h" +#include "wlan_decl.h" +#endif /* _INCLUDE_H_ */ diff --git a/drivers/net/wireless/marvell8686/os_defs.h b/drivers/net/wireless/marvell8686/os_defs.h new file mode 100644 index 0000000..0529bdd --- /dev/null +++ b/drivers/net/wireless/marvell8686/os_defs.h @@ -0,0 +1,42 @@ +/* + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ + +#ifndef _OS_HEADER1_ +#define _OS_HEADER1_ + +typedef char CHAR; +typedef char *PCHAR; +typedef u8 *PUCHAR; +typedef u16 *PUSHORT; +typedef long *PLONG; +typedef PLONG LONG_PTR; +typedef u32 *ULONG_PTR; +typedef u32 *Pu32; +typedef unsigned int UINT; +typedef UINT *PUINT; +typedef void VOID; +typedef VOID *PVOID; +typedef int WLAN_STATUS; +typedef u8 BOOLEAN; +typedef BOOLEAN *PBOOLEAN; +typedef PVOID PDRIVER_OBJECT; +typedef PUCHAR PUNICODE_STRING; +typedef long long LONGLONG; +typedef LONGLONG *PLONGLONG; +typedef unsigned long long *PULONGLONG; +typedef PUCHAR ANSI_STRING; +typedef ANSI_STRING *PANSI_STRING; +typedef unsigned short WCHAR; +typedef WCHAR *PWCHAR; +typedef WCHAR *LPWCH, *PWCH; +typedef WCHAR *NWPSTR; +typedef WCHAR *LPWSTR, *PWSTR; +typedef struct semaphore SEMAPHORE; + +#ifdef __KERNEL__ +typedef irqreturn_t IRQ_RET_TYPE; +#define IRQ_RET return IRQ_HANDLED +#endif /* __KERNEL__ */ + +#endif /* _OS_HEADER1 */ diff --git a/drivers/net/wireless/marvell8686/os_headers.h b/drivers/net/wireless/marvell8686/os_headers.h new file mode 100644 index 0000000..d01c604 --- /dev/null +++ b/drivers/net/wireless/marvell8686/os_headers.h @@ -0,0 +1,69 @@ +/* + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ + +#ifndef _OS_HEADERS_H +#define _OS_HEADERS_H + +#ifndef __ATTRIB_ALIGN__ +#define __ATTRIB_ALIGN__ __attribute__((aligned(4))) +#endif + +#ifndef __ATTRIB_PACK__ +#define __ATTRIB_PACK__ __attribute__ ((packed)) +#endif + +/* Linux header files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) +#include +#endif + +#include + +/* New Code to synchronize between IEEE Power save and PM*/ +#ifdef ENABLE_PM +#include +#endif + +/* ASM files */ +#include +#include +#include +#include +#include +#include + +/* Net header files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Wireless header */ +#include +#endif diff --git a/drivers/net/wireless/marvell8686/os_macros.h b/drivers/net/wireless/marvell8686/os_macros.h new file mode 100644 index 0000000..945c609 --- /dev/null +++ b/drivers/net/wireless/marvell8686/os_macros.h @@ -0,0 +1,165 @@ +/* + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +#ifndef _OS_MACROS_H +#define _OS_MACROS_H + +#define os_time_get() jiffies + +extern spinlock_t driver_lock; +extern unsigned long driver_flags; +#define OS_INT_DISABLE spin_lock_irqsave(&driver_lock, driver_flags) +#define OS_INT_RESTORE spin_unlock_irqrestore(&driver_lock, driver_flags); \ + driver_lock = SPIN_LOCK_UNLOCKED + +#define UpdateTransStart(dev) { \ + dev->trans_start = jiffies; \ +} + +#define OS_SET_THREAD_STATE(x) set_current_state(x) + +#define MODULE_GET if(try_module_get(THIS_MODULE)==0) return WLAN_STATUS_FAILURE; +#define MODULE_PUT module_put(THIS_MODULE) + +#define OS_INIT_SEMAPHORE(x) init_MUTEX(x) +#define OS_ACQ_SEMAPHORE_BLOCK(x) down_interruptible(x) +#define OS_ACQ_SEMAPHORE_NOBLOCK(x) down_trylock(x) +#define OS_REL_SEMAPHORE(x) up(x) + +/* Definitions below are needed for other OS like threadx */ +#define TX_DISABLE +#define TX_RESTORE +#define ConfigureThreadPriority() +#define OS_INTERRUPT_SAVE_AREA +#define OS_FREE_LOCK(x) +#define TX_EVENT_FLAGS_SET(x, y, z) + +#define os_wait_interruptible_timeout(waitq, cond, timeout) \ + wait_event_interruptible_timeout(waitq, cond, timeout) + +static inline void +os_sched_timeout(u32 millisec) +{ + set_current_state(TASK_INTERRUPTIBLE); + + schedule_timeout((millisec * HZ) / 1000); +} + +static inline void +os_schedule(u32 millisec) +{ + schedule_timeout((millisec * HZ) / 1000); +} + +static inline int +CopyMulticastAddrs(wlan_adapter * Adapter, struct net_device *dev) +{ + int i = 0; + struct dev_mc_list *mcptr = dev->mc_list; + + for (i = 0; i < dev->mc_count; i++) { + memcpy(&Adapter->MulticastList[i], mcptr->dmi_addr, ETH_ALEN); + mcptr = mcptr->next; + } + + return i; +} + +static inline u32 +get_utimeofday(void) +{ + struct timeval t; + u32 ut; + + do_gettimeofday(&t); + ut = (u32) t.tv_sec * 1000000 + ((u32) t.tv_usec); + return ut; +} + +static inline int +os_upload_rx_packet(wlan_private * priv, struct sk_buff *skb) +{ + +#define IPFIELD_ALIGN_OFFSET 2 + + skb->dev = priv->wlan_dev.netdev; + skb->protocol = eth_type_trans(skb, priv->wlan_dev.netdev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + netif_rx(skb); + + return 0; +} + +static inline void +os_free_tx_packet(wlan_private * priv) +{ + ulong flags; + + if (priv->adapter->CurrentTxSkb) { + kfree_skb(priv->adapter->CurrentTxSkb); + spin_lock_irqsave(&priv->adapter->CurrentTxLock, flags); + priv->adapter->CurrentTxSkb = NULL; + spin_unlock_irqrestore(&priv->adapter->CurrentTxLock, flags); + } +} + +/* + * netif carrier_on/off and start(wake)/stop_queue handling + * + * carrier_on carrier_off start_queue stop_queue + * open x(connect) x(disconnect) x + * close x x + * assoc x x + * deauth x x + * adhoc-start + * adhoc-join + * adhoc-link x x + * adhoc-bcnlost x x + * scan-begin x x + * scan-end x x + * ds-enter x x + * ds-exit x x + * xmit x + * xmit-done x + * tx-timeout + */ +static inline void +os_carrier_on(wlan_private * priv) +{ + if (!netif_carrier_ok(priv->wlan_dev.netdev) && + (priv->adapter->MediaConnectStatus == WlanMediaStateConnected) && + ((priv->adapter->InfrastructureMode != Wlan802_11IBSS) || + (priv->adapter->AdhocLinkSensed))) { + netif_carrier_on(priv->wlan_dev.netdev); + } +} + +static inline void +os_carrier_off(wlan_private * priv) +{ + if (netif_carrier_ok(priv->wlan_dev.netdev)) { + netif_carrier_off(priv->wlan_dev.netdev); + } +} + +static inline void +os_start_queue(wlan_private * priv) +{ + if (netif_queue_stopped(priv->wlan_dev.netdev) && + (priv->adapter->MediaConnectStatus == WlanMediaStateConnected) && + ((priv->adapter->InfrastructureMode != Wlan802_11IBSS) || + (priv->adapter->AdhocLinkSensed))) { + netif_wake_queue(priv->wlan_dev.netdev); + } +} + +static inline void +os_stop_queue(wlan_private * priv) +{ + if (!netif_queue_stopped(priv->wlan_dev.netdev)) { + netif_stop_queue(priv->wlan_dev.netdev); + } +} + +#endif /* _OS_MACROS_H */ diff --git a/drivers/net/wireless/marvell8686/os_timers.h b/drivers/net/wireless/marvell8686/os_timers.h new file mode 100644 index 0000000..b31387f --- /dev/null +++ b/drivers/net/wireless/marvell8686/os_timers.h @@ -0,0 +1,83 @@ +/* + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ + +#ifndef _OS_TIMERS_H +#define _OS_TIMERS_H + +typedef struct __WLAN_DRV_TIMER +{ + struct timer_list tl; + void (*timer_function) (void *context); + void *function_context; + UINT time_period; + BOOLEAN timer_is_periodic; + BOOLEAN timer_is_canceled; +} __ATTRIB_PACK__ WLAN_DRV_TIMER, *PWLAN_DRV_TIMER; + +static inline void +TimerHandler(unsigned long fcontext) +{ + PWLAN_DRV_TIMER timer = (PWLAN_DRV_TIMER) fcontext; + + timer->timer_function(timer->function_context); + + if (timer->timer_is_periodic == TRUE) { + mod_timer(&timer->tl, jiffies + ((timer->time_period * HZ) / 1000)); + } +} + +static inline void +InitializeTimer(PWLAN_DRV_TIMER timer, + void (*TimerFunction) (void *context), void *FunctionContext) +{ + // first, setup the timer to trigger the WlanTimerHandler proxy + init_timer(&timer->tl); + timer->tl.function = TimerHandler; + timer->tl.data = (u32) timer; + + // then tell the proxy which function to call and what to pass it + timer->timer_function = TimerFunction; + timer->function_context = FunctionContext; + timer->timer_is_canceled = FALSE; +} + +static inline void +SetTimer(PWLAN_DRV_TIMER timer, UINT MillisecondPeriod) +{ + timer->time_period = MillisecondPeriod; + timer->timer_is_periodic = FALSE; + timer->tl.expires = jiffies + (MillisecondPeriod * HZ) / 1000; + add_timer(&timer->tl); + timer->timer_is_canceled = FALSE; +} + +static inline void +ModTimer(PWLAN_DRV_TIMER timer, UINT MillisecondPeriod) +{ + timer->time_period = MillisecondPeriod; + timer->timer_is_periodic = FALSE; + mod_timer(&timer->tl, jiffies + (MillisecondPeriod * HZ) / 1000); + timer->timer_is_canceled = FALSE; +} + +static inline void +SetPeriodicTimer(PWLAN_DRV_TIMER timer, UINT MillisecondPeriod) +{ + timer->time_period = MillisecondPeriod; + timer->timer_is_periodic = TRUE; + timer->tl.expires = jiffies + (MillisecondPeriod * HZ) / 1000; + add_timer(&timer->tl); + timer->timer_is_canceled = FALSE; +} + +#define FreeTimer(x) do {} while (0) + +static inline void +CancelTimer(WLAN_DRV_TIMER * timer) +{ + del_timer(&timer->tl); + timer->timer_is_canceled = TRUE; +} + +#endif /* _OS_TIMERS_H */ diff --git a/drivers/net/wireless/marvell8686/release_version.h b/drivers/net/wireless/marvell8686/release_version.h new file mode 100644 index 0000000..161da80 --- /dev/null +++ b/drivers/net/wireless/marvell8686/release_version.h @@ -0,0 +1,5 @@ +/** + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ + +#define DRIVER_RELEASE_VERSION "26409.p60" diff --git a/drivers/net/wireless/marvell8686/sbi.h b/drivers/net/wireless/marvell8686/sbi.h new file mode 100644 index 0000000..65cf3f8 --- /dev/null +++ b/drivers/net/wireless/marvell8686/sbi.h @@ -0,0 +1,111 @@ +/** @file sbi.h + * + * @brief This file contains IF layer definitions. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 10/11/05: Add Doxygen format comments + 01/05/06: Add kernel 2.6.x support + +********************************************************/ + +#ifndef _SBI_H_ +#define _SBI_H_ + +/**Bit Definition*/ +#define B_BIT_0 0x01 +#define B_BIT_1 0x02 +#define B_BIT_2 0x04 +#define B_BIT_3 0x08 +#define B_BIT_4 0x10 +#define B_BIT_5 0x20 +#define B_BIT_6 0x40 +#define B_BIT_7 0x80 +#define B_BIT_8 0x100 +#define B_BIT_9 0X200 +#define B_BIT_10 0x400 + +/** INT Status Bit Definition*/ +#define HIS_RxUpLdRdy B_BIT_0 +#define HIS_TxDnLdRdy B_BIT_1 +#define HIS_CmdDnLdRdy B_BIT_2 +#define HIS_CardEvent B_BIT_3 +#define HIS_CmdUpLdRdy B_BIT_4 +#define HIS_WrFifoOvrflow B_BIT_5 +#define HIS_RdFifoUndrflow B_BIT_6 +#define HIS_WlanReady B_BIT_7 + +#define HIM_DISABLE 0xff +#define HIM_ENABLE 0x03 + +#define FIRMWARE_READY 0xfedc +#ifndef DEV_NAME_LEN +#define DEV_NAME_LEN 32 +#endif +#define MAXKEYLEN 13 + +/* The number of times to try when polling for status bits */ +#define MAX_POLL_TRIES 1000 + +/* The number of times to try when waiting for downloaded firmware to + become active. (polling the scratch register). */ + +#define MAX_FIRMWARE_POLL_TRIES 1000 + +#define FIRMWARE_TRANSFER_NBLOCK 1 +#define SBI_EVENT_CAUSE_SHIFT 3 + +typedef enum _mv_sd_type +{ + MVSD_DAT = 0, + MVSD_CMD = 1, + MVSD_EVENT = 3 +} mv_sd_type; + +/** Function Prototype Declaration */ +typedef wlan_private *(*wlan_notifier_fn_add) (void *dev_id); +typedef int (*wlan_notifier_fn_remove) (void *dev_id); + +typedef IRQ_RET_TYPE(*isr_notifier_fn_t) (s32 irq, void *dev_id, + struct pt_regs * reg); +typedef IRQ_RET_TYPE(*handler_fn_t) (s32 irq, void *dev_id, struct pt_regs *); + +/* Probe and Check if the card is present*/ +int sbi_probe_card(void *card); +int *sbi_register(wlan_notifier_fn_add, wlan_notifier_fn_remove, void *); +int sbi_register_dev(wlan_private * priv); +int sbi_unregister_dev(wlan_private *); +int sbi_disable_host_int(wlan_private * priv); +int sbi_get_int_status(wlan_private * priv, u8 *); +void sbi_unregister(void); +int sbi_prog_firmware(wlan_private *); +int sbi_verify_fw_download(wlan_private *); + +int sbi_prog_helper(wlan_private *); +int sbi_prog_firmware_w_helper(wlan_private *); + +int sbi_read_event_cause(wlan_private *); +int sbi_host_to_card(wlan_private * priv, u8 type, u8 * payload, u16 nb); +int sbi_card_to_host(wlan_private * priv, u32 type, u32 * nb, u8 * payload, + u16 npayload); +int sbi_enable_host_int(wlan_private *); + +int sbi_exit_deep_sleep(wlan_private *); +int sbi_reset_deepsleep_wakeup(wlan_private *); +#ifdef ENABLE_PM +int sbi_suspend(wlan_private *); +int sbi_resume(wlan_private *); +#endif + +int sbi_read_ioreg(wlan_private * priv, u8 func, u32 reg, u8 * dat); +int sbi_write_ioreg(wlan_private * priv, u8 func, u32 reg, u8 dat); +int sbi_set_bus_clock(wlan_private * priv, u8 option); + +int sbi_get_cis_info(wlan_private * priv); + +int wlan_remove_card(void *card); +wlan_private* wlan_add_card(void *card); + +#endif /* _SBI_H */ diff --git a/drivers/net/wireless/marvell8686/sdio.h b/drivers/net/wireless/marvell8686/sdio.h new file mode 100644 index 0000000..3e2e60d --- /dev/null +++ b/drivers/net/wireless/marvell8686/sdio.h @@ -0,0 +1,112 @@ +/*File sdio.h + * This file contains the structure definations for the low level driver + * And the error response related code + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ + +#ifndef __SDIO_H__ +#define __SDIO_H__ + +#include /* For read write semaphores */ +#include +#include +#include +#ifdef CONFIG_MARVELL_8686_PROC_FS + #include +#endif + +#include "os_defs.h" + +#ifdef DEBUG_SDIO_LEVEL2 +#ifndef DEBUG_LEVEL1 +#define DEBUG_LEVEL1 +#endif +#define _ENTER() printk(KERN_DEBUG "Enter: %s, %s linux %i\n", __FUNCTION__, \ + __FILE__, __LINE__) +#define _LEAVE() printk(KERN_DEBUG "Leave: %s, %s linux %i\n", __FUNCTION__, \ + __FILE__, __LINE__) +#else +#define _ENTER() +#define _LEAVE() +#endif + +#ifdef DEBUG_SDIO_LEVEL1 +#define _DBGMSG(x...) printk(KERN_DEBUG x) +#define _WARNING(x...) printk(KERN_DEBUG x) +#else +#define _DBGMSG(x...) +#define _WARNING(x...) +#endif + +#ifdef DEBUG_SDIO_LEVEL0 +#define _PRINTK(x...) printk(x) +#define _ERROR(x...) printk(KERN_ERR x) +#else +#define _PRINTK(x...) +#define _ERROR(x...) +#endif + +typedef struct _card_capability +{ + u8 num_of_io_funcs; /* Number of i/o functions */ + u8 memory_yes; /* Memory present ? */ + u16 rca; /* Relative Card Address */ + u32 ocr; /* Operation Condition register */ + u16 fnblksz[8]; + u32 cisptr[8]; +} card_capability; + +typedef struct _dummy_tmpl +{ + int irq_line; +} dummy_tmpl; + +typedef struct _sdio_host *mmc_controller_t; + +typedef enum _sdio_fsm +{ + SDIO_FSM_IDLE = 1, + SDIO_FSM_CLK_OFF, + SDIO_FSM_END_CMD, + SDIO_FSM_BUFFER_IN_TRANSIT, + SDIO_FSM_END_BUFFER, + SDIO_FSM_END_IO, + SDIO_FSM_END_PRG, + SDIO_FSM_ERROR +} sdio_fsm_state; + +typedef struct _sdio_host +{ + char name[16]; + int bus_width; +} __attribute__ ((aligned)) sdio_ctrller; + +typedef struct _sdio_operations +{ + char name[16]; +} sdio_operations; + +typedef struct _iorw_extended_t +{ + u8 rw_flag; /** If 0 command is READ; else if 1 command is WRITE */ + u8 func_num; + u8 blkmode; + u8 op_code; + u32 reg_addr; + u32 byte_cnt; + u32 blk_size; + u8 *buf; +} iorw_extended_t; + +#define BUS_INTERFACE_CONTROL_REG 0x07 +#define CARD_CAPABILITY_REG 0x08 +#define COMMON_CIS_POINTER_0_REG 0x09 +#define COMMON_CIS_POINTER_1_REG 0x0a +#define COMMON_CIS_POINTER_2_REG 0x0b +#define BUS_SUSPEND_REG 0x0c +#define FUNCTION_SELECT_REG 0x0d +#define EXEC_FLAGS_REG 0x0e +#define READY_FLAGS_REG 0x0f + +#endif /* __SDIO__H */ diff --git a/drivers/net/wireless/marvell8686/wlan_11d.c b/drivers/net/wireless/marvell8686/wlan_11d.c new file mode 100644 index 0000000..0dbcf49 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_11d.c @@ -0,0 +1,893 @@ +/** @file wlan_11d.c + * @brief This file contains functions for 802.11D. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 10/04/05: Add Doxygen format comments + +********************************************************/ +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ +#define TX_PWR_DEFAULT 10 + +static region_code_mapping_t region_code_mapping[] = { + {"US ", 0x10}, /* US FCC */ + {"CA ", 0x20}, /* IC Canada */ + {"SG ", 0x10}, /* Singapore */ + {"EU ", 0x30}, /* ETSI */ + {"AU ", 0x30}, /* Australia */ + {"KR ", 0x30}, /* Republic Of Korea */ + {"FR ", 0x32}, /* France */ + {"JP ", 0x40}, /* Japan */ + {"JP ", 0x41}, /* Japan */ +}; + +/******************************************************** + Global Variables +********************************************************/ +/* Following 2 structure defines the supported channels */ +CHANNEL_FREQ_POWER channel_freq_power_UN_BG[] = { + {1, 2412, TX_PWR_DEFAULT}, + {2, 2417, TX_PWR_DEFAULT}, + {3, 2422, TX_PWR_DEFAULT}, + {4, 2427, TX_PWR_DEFAULT}, + {5, 2432, TX_PWR_DEFAULT}, + {6, 2437, TX_PWR_DEFAULT}, + {7, 2442, TX_PWR_DEFAULT}, + {8, 2447, TX_PWR_DEFAULT}, + {9, 2452, TX_PWR_DEFAULT}, + {10, 2457, TX_PWR_DEFAULT}, + {11, 2462, TX_PWR_DEFAULT}, + {12, 2467, TX_PWR_DEFAULT}, + {13, 2472, TX_PWR_DEFAULT}, + {14, 2484, TX_PWR_DEFAULT} +}; + +CHANNEL_FREQ_POWER channel_freq_power_UN_AJ[] = { + {8, 5040, TX_PWR_DEFAULT}, + {12, 5060, TX_PWR_DEFAULT}, + {16, 5080, TX_PWR_DEFAULT}, + {34, 5170, TX_PWR_DEFAULT}, + {38, 5190, TX_PWR_DEFAULT}, + {42, 5210, TX_PWR_DEFAULT}, + {46, 5230, TX_PWR_DEFAULT}, + {36, 5180, TX_PWR_DEFAULT}, + {40, 5200, TX_PWR_DEFAULT}, + {44, 5220, TX_PWR_DEFAULT}, + {48, 5240, TX_PWR_DEFAULT}, + {52, 5260, TX_PWR_DEFAULT}, + {56, 5280, TX_PWR_DEFAULT}, + {60, 5300, TX_PWR_DEFAULT}, + {64, 5320, TX_PWR_DEFAULT}, + {100, 5500, TX_PWR_DEFAULT}, + {104, 5520, TX_PWR_DEFAULT}, + {108, 5540, TX_PWR_DEFAULT}, + {112, 5560, TX_PWR_DEFAULT}, + {116, 5580, TX_PWR_DEFAULT}, + {120, 5600, TX_PWR_DEFAULT}, + {124, 5620, TX_PWR_DEFAULT}, + {128, 5640, TX_PWR_DEFAULT}, + {132, 5660, TX_PWR_DEFAULT}, + {136, 5680, TX_PWR_DEFAULT}, + {140, 5700, TX_PWR_DEFAULT}, + {149, 5745, TX_PWR_DEFAULT}, + {153, 5765, TX_PWR_DEFAULT}, + {157, 5785, TX_PWR_DEFAULT}, + {161, 5805, TX_PWR_DEFAULT}, + {165, 5825, TX_PWR_DEFAULT}, +/* {240, 4920, TX_PWR_DEFAULT}, + {244, 4940, TX_PWR_DEFAULT}, + {248, 4960, TX_PWR_DEFAULT}, + {252, 4980, TX_PWR_DEFAULT}, +channels for 11J JP 10M channel gap */ +}; + +extern CHANNEL_FREQ_POWER *wlan_get_region_cfp_table(u8 region, + u8 band, int *cfp_no); + +/******************************************************** + Local Functions +********************************************************/ +/** + * @brief This function convert Region string to code integer + * @param region region string + * @return region id +*/ +static u8 +wlan_region_2_code(s8 * region) +{ + u8 i; + u8 size = sizeof(region_code_mapping) / sizeof(region_code_mapping_t); + + for (i = 0; i < COUNTRY_CODE_LEN && region[i]; i++) + region[i] = toupper(region[i]); + + for (i = 0; i < size; i++) { + if (!memcmp(region, region_code_mapping[i].region, COUNTRY_CODE_LEN)) + return (region_code_mapping[i].code); + } + + /* default is US */ + return (region_code_mapping[0].code); +} + +/** + * @brief This function converts interger code to region string + * @param code region code + * @return region string +*/ +static u8 * +wlan_code_2_region(u8 code) +{ + u8 i; + u8 size = sizeof(region_code_mapping) / sizeof(region_code_mapping_t); + for (i = 0; i < size; i++) { + if (region_code_mapping[i].code == code) + return (region_code_mapping[i].region); + } + /* default is US */ + return (region_code_mapping[0].region); +} + +/** + * @brief This function finds the NoOfChan-th chan after the firstChan + * @param band band + * @param firstChan first channel number + * @param NoOfChan number of channels + * @return the NoOfChan-th chan number +*/ +static BOOLEAN +wlan_get_chan_11d(u8 band, u8 firstChan, u8 NoOfChan, u8 * chan) +/*find the NoOfChan-th chan after the firstChan*/ +{ + u8 i; + CHANNEL_FREQ_POWER *cfp; + u8 cfp_no; + + ENTER(); + + { + cfp = channel_freq_power_UN_BG; + cfp_no = sizeof(channel_freq_power_UN_BG) / + sizeof(CHANNEL_FREQ_POWER); + } + + for (i = 0; i < cfp_no; i++) { + if ((cfp + i)->Channel == firstChan) { + PRINTM(INFO, "firstChan found\n"); + break; + } + } + + if (i < cfp_no) { + /*if beyond the boundary */ + if (i + NoOfChan < cfp_no) { + *chan = (cfp + i + NoOfChan)->Channel; + return TRUE; + } + } + + LEAVE(); + return FALSE; +} + +/** + * @brief This function Checks if chan txpwr is learned from AP/IBSS + * @param chan chan number + * @param parsed_region_chan pointer to parsed_region_chan_11d_t + * @return TRUE; FALSE +*/ +BOOLEAN +wlan_channel_known_11d(u8 chan, parsed_region_chan_11d_t * parsed_region_chan) +{ + chan_power_11d_t *chanPwr = parsed_region_chan->chanPwr; + u8 NoOfChan = parsed_region_chan->NoOfChan; + u8 i = 0; + + ENTER(); + HEXDUMP("11D:parsed_region_chan:", (char *) chanPwr, + sizeof(chan_power_11d_t) * NoOfChan); + + for (i = 0; i < NoOfChan; i++) { + if (chan == chanPwr[i].chan) { + PRINTM(INFO, "11D: Found Chan:%d\n", chan); + LEAVE(); + return TRUE; + } + } + + PRINTM(INFO, "11D: Not Find Chan:%d\n", chan); + LEAVE(); + return FALSE; +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function Converts chan to frequency + * @param chan channel number + * @param band band + * @return channel frequency +*/ +u32 +chan_2_freq(u8 chan, u8 band) +{ + CHANNEL_FREQ_POWER *cf; + u16 cnt; + u16 i; + u32 freq = 0; + + ENTER(); + + { + cf = channel_freq_power_UN_BG; + cnt = sizeof(channel_freq_power_UN_BG) / sizeof(CHANNEL_FREQ_POWER); + } + + for (i = 0; i < cnt; i++) { + if (chan == cf[i].Channel) + freq = cf[i].Freq; + } + + LEAVE(); + return freq; +} + +/** + * @brief This function generates domaininfo from parsed_region_chan + * @param parsed_region_chan pointer to parsed_region_chan_11d_t + * @param domaininfo pointer to wlan_802_11d_domain_reg_t + * @return WLAN_STATUS_SUCCESS +*/ +int +wlan_generate_domain_info_11d(parsed_region_chan_11d_t * parsed_region_chan, + wlan_802_11d_domain_reg_t * domaininfo) +{ + u8 NoOfSubband = 0; + + u8 NoOfChan = parsed_region_chan->NoOfChan; + u8 NoOfParsedChan = 0; + + u8 firstChan = 0, nextChan = 0, maxPwr = 0; + + u8 i, flag = 0; + + ENTER(); + + memcpy(domaininfo->CountryCode, parsed_region_chan->CountryCode, + COUNTRY_CODE_LEN); + + PRINTM(INFO, "11D:NoOfChan=%d\n", NoOfChan); + HEXDUMP("11D:parsed_region_chan:", (char *) parsed_region_chan, + sizeof(parsed_region_chan_11d_t)); + + for (i = 0; i < NoOfChan; i++) { + if (!flag) { + flag = 1; + nextChan = firstChan = parsed_region_chan->chanPwr[i].chan; + maxPwr = parsed_region_chan->chanPwr[i].pwr; + NoOfParsedChan = 1; + continue; + } + + if (parsed_region_chan->chanPwr[i].chan == nextChan + 1 && + parsed_region_chan->chanPwr[i].pwr == maxPwr) { + nextChan++; + NoOfParsedChan++; + } else { + domaininfo->Subband[NoOfSubband].FirstChan = firstChan; + domaininfo->Subband[NoOfSubband].NoOfChan = NoOfParsedChan; + domaininfo->Subband[NoOfSubband].MaxTxPwr = maxPwr; + NoOfSubband++; + NoOfParsedChan = 1; + nextChan = firstChan = parsed_region_chan->chanPwr[i].chan; + maxPwr = parsed_region_chan->chanPwr[i].pwr; + } + } + + if (flag) { + domaininfo->Subband[NoOfSubband].FirstChan = firstChan; + domaininfo->Subband[NoOfSubband].NoOfChan = NoOfParsedChan; + domaininfo->Subband[NoOfSubband].MaxTxPwr = maxPwr; + NoOfSubband++; + } + domaininfo->NoOfSubband = NoOfSubband; + + PRINTM(INFO, "NoOfSubband=%x\n", domaininfo->NoOfSubband); + HEXDUMP("11D:domaininfo:", (char *) domaininfo, + COUNTRY_CODE_LEN + 1 + + sizeof(IEEEtypes_SubbandSet_t) * NoOfSubband); + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function generates parsed_region_chan from Domain Info learned from AP/IBSS + * @param region_chan pointer to REGION_CHANNEL + * @param *parsed_region_chan pointer to parsed_region_chan_11d_t + * @return N/A +*/ +void +wlan_generate_parsed_region_chan_11d(REGION_CHANNEL * region_chan, + parsed_region_chan_11d_t * + parsed_region_chan) +{ + u8 i; + CHANNEL_FREQ_POWER *cfp; + + ENTER(); + + if (region_chan == NULL) { + PRINTM(INFO, "11D: region_chan is NULL\n"); + return; + } + + cfp = region_chan->CFP; + if (cfp == NULL) { + PRINTM(INFO, "11D: cfp equal NULL \n"); + return; + } + + parsed_region_chan->band = region_chan->Band; + parsed_region_chan->region = region_chan->Region; + memcpy(parsed_region_chan->CountryCode, + wlan_code_2_region(region_chan->Region), COUNTRY_CODE_LEN); + + PRINTM(INFO, "11D: region[0x%x] band[%d]\n", parsed_region_chan->region, + parsed_region_chan->band); + + for (i = 0; i < region_chan->NrCFP; i++, cfp++) { + parsed_region_chan->chanPwr[i].chan = cfp->Channel; + parsed_region_chan->chanPwr[i].pwr = cfp->MaxTxPower; + PRINTM(INFO, "11D: Chan[%d] Pwr[%d]\n", + parsed_region_chan->chanPwr[i].chan, + parsed_region_chan->chanPwr[i].pwr); + } + parsed_region_chan->NoOfChan = region_chan->NrCFP; + + PRINTM(INFO, "11D: NoOfChan[%d]\n", parsed_region_chan->NoOfChan); + + LEAVE(); + return; +} + +/** + * @brief generate parsed_region_chan from Domain Info learned from AP/IBSS + * @param region region ID + * @param band band + * @param chan chan + * @return TRUE;FALSE +*/ +BOOLEAN +wlan_region_chan_supported_11d(u8 region, u8 band, u8 chan) +{ + CHANNEL_FREQ_POWER *cfp; + int cfp_no; + u8 idx; + + ENTER(); + + if ((cfp = wlan_get_region_cfp_table(region, band, &cfp_no)) == NULL) { + return FALSE; + } + + for (idx = 0; idx < cfp_no; idx++) { + if (chan == (cfp + idx)->Channel) { + /* If Mrvl Chip Supported? */ + if ((cfp + idx)->Unsupported) { + return FALSE; + } else { + return TRUE; + } + } + } + + /*chan is not in the region table */ + LEAVE(); + return FALSE; +} + +/** + * @brief This function checks if chan txpwr is learned from AP/IBSS + * @param chan chan number + * @param parsed_region_chan pointer to parsed_region_chan_11d_t + * @return WLAN_STATUS_SUCCESS +*/ +int +wlan_parse_domain_info_11d(IEEEtypes_CountryInfoFullSet_t * CountryInfo, + u8 band, + parsed_region_chan_11d_t * parsed_region_chan) +{ + u8 NoOfSubband, NoOfChan; + u8 lastChan, firstChan, curChan; + u8 region; + + u8 idx = 0; /*chan index in parsed_region_chan */ + + u8 j, i; + + ENTER(); + + /*Validation Rules: + 1. Valid Region Code + 2. First Chan increment + 3. Channel range no overlap + 4. Channel is valid? + 5. Channel is supported by Region? + 6. Others + */ + + HEXDUMP("CountryInfo:", (s8 *) CountryInfo, 30); + + if ((*(CountryInfo->CountryCode)) == 0 || + (CountryInfo->Len <= COUNTRY_CODE_LEN)) { + /* No region Info or Wrong region info: treat as No 11D info */ + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + /*Step1: check region_code */ + parsed_region_chan->region = region = + wlan_region_2_code((s8 *) CountryInfo->CountryCode); + + PRINTM(INFO, "regioncode=%x\n", (u8) parsed_region_chan->region); + HEXDUMP("CountryCode:", (char *) CountryInfo->CountryCode, + COUNTRY_CODE_LEN); + + parsed_region_chan->band = band; + + memcpy(parsed_region_chan->CountryCode, CountryInfo->CountryCode, + COUNTRY_CODE_LEN); + + NoOfSubband = (CountryInfo->Len - COUNTRY_CODE_LEN) / + sizeof(IEEEtypes_SubbandSet_t); + + for (j = 0, lastChan = 0; j < NoOfSubband; j++) { + + if (CountryInfo->Subband[j].FirstChan <= lastChan) { + /*Step2&3. Check First Chan Num increment and no overlap */ + PRINTM(INFO, "11D: Chan[%d>%d] Overlap\n", + CountryInfo->Subband[j].FirstChan, lastChan); + continue; + } + + firstChan = CountryInfo->Subband[j].FirstChan; + NoOfChan = CountryInfo->Subband[j].NoOfChan; + + for (i = 0; idx < MAX_NO_OF_CHAN && i < NoOfChan; i++) { + /*step4: channel is supported? */ + + if (wlan_get_chan_11d(band, firstChan, i, &curChan) + == FALSE) { + /* Chan is not found in UN table */ + PRINTM(INFO, "chan is not supported: %d \n", i); + break; + } + + lastChan = curChan; + + /*step5: We don't need to Check if curChan is supported by mrvl in region */ + parsed_region_chan->chanPwr[idx].chan = curChan; + parsed_region_chan->chanPwr[idx].pwr = + CountryInfo->Subband[j].MaxTxPwr; + idx++; + } + + /*Step6: Add other checking if any */ + + } + + parsed_region_chan->NoOfChan = idx; + + PRINTM(INFO, "NoOfChan=%x\n", parsed_region_chan->NoOfChan); + HEXDUMP("11D:parsed_region_chan:", (s8 *) parsed_region_chan, + 2 + COUNTRY_CODE_LEN + sizeof(parsed_region_chan_11d_t) * idx); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function calculates the scan type for channels + * @param chan chan number + * @param parsed_region_chan pointer to parsed_region_chan_11d_t + * @return PASSIVE if chan is unknown; ACTIVE if chan is known +*/ +u8 +wlan_get_scan_type_11d(u8 chan, parsed_region_chan_11d_t * parsed_region_chan) +{ + u8 scan_type = HostCmd_SCAN_TYPE_PASSIVE; + + ENTER(); + + if (wlan_channel_known_11d(chan, parsed_region_chan)) { + PRINTM(INFO, "11D: Found and do Active Scan\n"); + scan_type = HostCmd_SCAN_TYPE_ACTIVE; + } else { + PRINTM(INFO, "11D: Not Find and do Passive Scan\n"); + } + + LEAVE(); + return scan_type; + +} + +/** + * @brief This function gets if 11D is enabled + * @param priv pointer to wlan_private + * @return ENABLE_11D;DISABLE_11D +*/ +state_11d_t +wlan_get_state_11d(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_802_11d_state_t *state = &Adapter->State11D; + return (state->Enable11D); +} + +/** + * @brief initialize internal variable for 11D + * @param priv pointer to wlan_private + * @return N/A +*/ +void +wlan_init_11d(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_802_11d_state_t *state = &Adapter->State11D; + + state->Enable11D = DISABLE_11D; + + memset(&(priv->adapter->parsed_region_chan), 0, + sizeof(parsed_region_chan_11d_t)); + + return; +} + +/** + * @brief This function enable/disable 11D + * @param priv pointer to wlan_private + * @param flag enable/disable flag + * @return WLAN_STATUS_SUCCESS; WLAN_STATUS_FAILURE +*/ +int +wlan_enable_11d(wlan_private * priv, state_11d_t flag) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_802_11d_state_t *state = &Adapter->State11D; + int ret; + state_11d_t enable = flag; + + ENTER(); + + state->Enable11D = flag; + + /* send cmd to FW to enable/disable 11D fucntion in FW */ + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, + OID_802_11D_ENABLE, &enable); + if (ret) { + PRINTM(INFO, "11D: Fail to enable 11D \n"); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function sets DOMAIN INFO to FW + * @param priv pointer to wlan_private + * @return WLAN_STATUS_SUCCESS; WLAN_STATUS_FAILURE +*/ +int +wlan_set_domain_info_11d(wlan_private * priv) +{ + int ret; + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11D_DOMAIN_INFO, + HostCmd_ACT_GEN_SET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + if (ret) { + PRINTM(INFO, "11D: Fail to dnld domain Info\n"); + } + + LEAVE(); + return ret; +} + +/** + * @brief This function setups scan channels + * @param priv pointer to wlan_private + * @param band band + * @return WLAN_STATUS_SUCCESS +*/ +int +wlan_set_universaltable(wlan_private * priv, u8 band) +{ + wlan_adapter *Adapter = priv->adapter; + u16 size = sizeof(CHANNEL_FREQ_POWER); + u16 i = 0; + + ENTER(); + + memset(Adapter->universal_channel, 0, sizeof(Adapter->universal_channel)); + + { + Adapter->universal_channel[i].NrCFP = + sizeof(channel_freq_power_UN_BG) / size; + PRINTM(INFO, "11D: BG-band NrCFP=%d\n", + Adapter->universal_channel[i].NrCFP); + + Adapter->universal_channel[i].CFP = channel_freq_power_UN_BG; + Adapter->universal_channel[i].Valid = TRUE; + Adapter->universal_channel[i].Region = UNIVERSAL_REGION_CODE; + Adapter->universal_channel[i].Band = band; + i++; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function implements command CMD_802_11D_DOMAIN_INFO + * @param priv pointer to wlan_private + * @param cmd pointer to cmd buffer + * @param cmdno cmd ID + * @param CmdOption cmd action + * @return WLAN_STATUS_SUCCESS +*/ +int +wlan_cmd_802_11d_domain_info(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmdno, + u16 CmdOption) +{ + HostCmd_DS_802_11D_DOMAIN_INFO *pDomainInfo = &cmd->params.domaininfo; + MrvlIEtypes_DomainParamSet_t *domain = &pDomainInfo->Domain; + wlan_adapter *Adapter = priv->adapter; + u8 NoOfSubband = Adapter->DomainReg.NoOfSubband; + + ENTER(); + + PRINTM(INFO, "NoOfSubband=%x\n", NoOfSubband); + + cmd->Command = wlan_cpu_to_le16(cmdno); + pDomainInfo->Action = wlan_cpu_to_le16(CmdOption); + if (CmdOption == HostCmd_ACT_GET) { + cmd->Size = wlan_cpu_to_le16(sizeof(pDomainInfo->Action) + S_DS_GEN); + HEXDUMP("11D: 802_11D_DOMAIN_INFO:", (u8 *) cmd, (int) (cmd->Size)); + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + domain->Header.Type = wlan_cpu_to_le16(TLV_TYPE_DOMAIN); + memcpy(domain->CountryCode, Adapter->DomainReg.CountryCode, + sizeof(domain->CountryCode)); + + domain->Header.Len = + wlan_cpu_to_le16(NoOfSubband * sizeof(IEEEtypes_SubbandSet_t) + + sizeof(domain->CountryCode)); + + if (NoOfSubband) { + memcpy(domain->Subband, Adapter->DomainReg.Subband, + NoOfSubband * sizeof(IEEEtypes_SubbandSet_t)); + + cmd->Size = wlan_cpu_to_le16(sizeof(pDomainInfo->Action) + + domain->Header.Len + + sizeof(MrvlIEtypesHeader_t) + S_DS_GEN); + } else { + cmd->Size = wlan_cpu_to_le16(sizeof(pDomainInfo->Action) + S_DS_GEN); + } + + HEXDUMP("11D:802_11D_DOMAIN_INFO:", (u8 *) cmd, (int) (cmd->Size)); + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function implements private cmd: enable/disable 11D + * @param priv pointer to wlan_private + * @param wrq pointer to user data + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_enable_11d(wlan_private * priv, struct iwreq *wrq) +{ + int data = 0; + int *val; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + + PRINTM(INFO, "Enable 11D: %s\n", + (data == CMD_ENABLED) ? "Enable" : "Disable"); + switch (data) { + case CMD_ENABLED: + wlan_enable_11d(priv, ENABLE_11D); + break; + case CMD_DISABLED: + wlan_enable_11d(priv, DISABLE_11D); + break; + default: + break; + } + + data = + (Adapter->State11D.Enable11D == + ENABLE_11D) ? CMD_ENABLED : CMD_DISABLED; + val = (int *) wrq->u.name; + *val = data; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function parses countryinfo from AP and download country info to FW + * @param priv pointer to wlan_private + * @param resp pointer to command response buffer + * @return WLAN_STATUS_SUCCESS; WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11d_domain_info(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11D_DOMAIN_INFO_RSP + * domaininfo = &resp->params.domaininforesp; + MrvlIEtypes_DomainParamSet_t * domain = &domaininfo->Domain; + u16 Action = wlan_le16_to_cpu(domaininfo->Action); + s16 ret = WLAN_STATUS_SUCCESS; + u8 NoOfSubband = 0; + + ENTER(); + + HEXDUMP("11D DOMAIN Info Rsp Data:", (u8 *) resp, resp->Size); + + NoOfSubband = + (wlan_le16_to_cpu(domain->Header.Len) - + 3) / sizeof(IEEEtypes_SubbandSet_t); + /* countrycode 3 bytes */ + + PRINTM(INFO, "11D Domain Info Resp: NoOfSubband=%d\n", NoOfSubband); + + if (NoOfSubband > MRVDRV_MAX_SUBBAND_802_11D) { + PRINTM(INFO, "Invalid Numrer of Subband returned!!\n"); + return WLAN_STATUS_FAILURE; + } + + switch (Action) { + case HostCmd_ACT_SET: /*Proc Set Action */ + break; + + case HostCmd_ACT_GET: + break; + default: + PRINTM(INFO, "Invalid Action:%d\n", domaininfo->Action); + ret = WLAN_STATUS_FAILURE; + break; + } + + LEAVE(); + return ret; +} + +/** + * @brief This function parses countryinfo from AP and download country info to FW + * @param priv pointer to wlan_private + * @return WLAN_STATUS_SUCCESS; WLAN_STATUS_FAILURE + */ +int +wlan_parse_dnld_countryinfo_11d(wlan_private * priv) +{ + int ret; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + /* Skip new 11d download when roaming */ + return WLAN_STATUS_SUCCESS; + } + + if (wlan_get_state_11d(priv) == ENABLE_11D) { + + memset(&Adapter->parsed_region_chan, 0, + sizeof(parsed_region_chan_11d_t)); + + ret = + wlan_parse_domain_info_11d(&Adapter->pAttemptedBSSDesc-> + CountryInfo, 0, + &Adapter->parsed_region_chan); + + if (ret == WLAN_STATUS_FAILURE) { + PRINTM(INFO, "11D: No region info in the AP BssDesc..\n"); + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + memset(&Adapter->DomainReg, 0, sizeof(wlan_802_11d_domain_reg_t)); + wlan_generate_domain_info_11d(&Adapter->parsed_region_chan, + &Adapter->DomainReg); + + ret = wlan_set_domain_info_11d(priv); + + if (ret) { + PRINTM(INFO, "11D: Err set domainInfo to FW\n"); + LEAVE(); + return ret; + } + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function generates 11D info from user specified regioncode and download to FW + * @param priv pointer to wlan_private + * @return WLAN_STATUS_SUCCESS; WLAN_STATUS_FAILURE + */ +int +wlan_create_dnld_countryinfo_11d(wlan_private * priv, u8 band) +{ + int ret; + wlan_adapter *Adapter = priv->adapter; + REGION_CHANNEL *region_chan; + u8 j; + + ENTER(); + PRINTM(INFO, "11D:Band[%d]\n", band); + + /* update parsed_region_chan_11; dnld domaininf to FW */ + + for (j = 0; j < sizeof(Adapter->region_channel) / + sizeof(Adapter->region_channel[0]); j++) { + region_chan = &Adapter->region_channel[j]; + + PRINTM(INFO, "11D:[%d] region_chan->Band[%d]\n", j, + region_chan->Band); + + if (!region_chan || !region_chan->Valid || !region_chan->CFP) + continue; + if (region_chan->Band != band) + continue; + break; + } + + if (j >= sizeof(Adapter->region_channel) / + sizeof(Adapter->region_channel[0])) { + PRINTM(INFO, "11D:region_chan not found. Band[%d]\n", band); + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + memset(&Adapter->parsed_region_chan, 0, sizeof(parsed_region_chan_11d_t)); + wlan_generate_parsed_region_chan_11d(region_chan, + &Adapter->parsed_region_chan); + + memset(&Adapter->DomainReg, 0, sizeof(wlan_802_11d_domain_reg_t)); + wlan_generate_domain_info_11d(&Adapter->parsed_region_chan, + &Adapter->DomainReg); + + ret = wlan_set_domain_info_11d(priv); + + if (ret) { + PRINTM(INFO, "11D: Err set domainInfo to FW\n"); + LEAVE(); + return ret; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} diff --git a/drivers/net/wireless/marvell8686/wlan_11d.h b/drivers/net/wireless/marvell8686/wlan_11d.h new file mode 100644 index 0000000..208f64b --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_11d.h @@ -0,0 +1,110 @@ +/** @file wlan_11d.h + * @brief This header file contains data structures and + * function declarations of 802.11d + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/************************************************************* +Change log: + 09/26/05: add Doxygen format comments + ************************************************************/ + +#ifndef _WLAN_11D_ +#define _WLAN_11D_ + +#define MAX_CHAN_NUM 255 + +#define UNIVERSAL_REGION_CODE 0xff + +/** (Beaconsize(256)-5(IEId,len,contrystr(3))/3(FirstChan,NoOfChan,MaxPwr) + */ +#define MAX_NO_OF_CHAN 40 + +typedef struct _REGION_CHANNEL *PREGION_CHANNEL; + +typedef enum +{ + DISABLE_11D = 0, + ENABLE_11D = 1, +} state_11d_t; + +/** domain regulatory information */ +typedef struct _wlan_802_11d_domain_reg +{ + /** country Code*/ + u8 CountryCode[COUNTRY_CODE_LEN]; + /** No. of subband*/ + u8 NoOfSubband; + IEEEtypes_SubbandSet_t Subband[MRVDRV_MAX_SUBBAND_802_11D]; +} wlan_802_11d_domain_reg_t; + +typedef struct _chan_power_11d +{ + u8 chan; + u8 pwr; +} __ATTRIB_PACK__ chan_power_11d_t; + +typedef struct _parsed_region_chan_11d +{ + u8 band; + u8 region; + s8 CountryCode[COUNTRY_CODE_LEN]; + chan_power_11d_t chanPwr[MAX_NO_OF_CHAN]; + u8 NoOfChan; +} __ATTRIB_PACK__ parsed_region_chan_11d_t; + +/** Data for state machine */ +typedef struct _wlan_802_11d_state +{ + /** True for Enabling 11D*/ + BOOLEAN Enable11D; +} wlan_802_11d_state_t; + +typedef struct _region_code_mapping +{ + s8 region[COUNTRY_CODE_LEN]; + u8 code; +} region_code_mapping_t; + +/* function prototypes*/ +int wlan_generate_domain_info_11d(parsed_region_chan_11d_t * + parsed_region_chan, + wlan_802_11d_domain_reg_t * domaininfo); + +int wlan_parse_domain_info_11d(IEEEtypes_CountryInfoFullSet_t * CountryInfo, + u8 band, + parsed_region_chan_11d_t * parsed_region_chan); + +u8 wlan_get_scan_type_11d(u8 chan, + parsed_region_chan_11d_t * parsed_region_chan); + +u32 chan_2_freq(u8 chan, u8 band); + +int wlan_set_domain_info_11d(wlan_private * priv); + +state_11d_t wlan_get_state_11d(wlan_private * priv); + +void wlan_init_11d(wlan_private * priv); + +int wlan_enable_11d(wlan_private * priv, state_11d_t flag); + +int wlan_set_universaltable(wlan_private * priv, u8 band); + +void wlan_generate_parsed_region_chan_11d(PREGION_CHANNEL region_chan, + parsed_region_chan_11d_t * + parsed_region_chan); + +int wlan_cmd_802_11d_domain_info(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmdno, + u16 CmdOption); + +int wlan_cmd_enable_11d(wlan_private * priv, struct iwreq *wrq); + +int wlan_ret_802_11d_domain_info(wlan_private * priv, + HostCmd_DS_COMMAND * resp); + +int wlan_parse_dnld_countryinfo_11d(wlan_private * priv); + +int wlan_create_dnld_countryinfo_11d(wlan_private * priv, u8 band); + +#endif /* _WLAN_11D_ */ diff --git a/drivers/net/wireless/marvell8686/wlan_cmd.c b/drivers/net/wireless/marvell8686/wlan_cmd.c new file mode 100644 index 0000000..2d5bf55 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_cmd.c @@ -0,0 +1,2501 @@ +/** @file wlan_cmd.c + * + * @brief This file contains the handling of command. + * it prepares command and sends it to firmware when + * it is ready. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + * + */ +/******************************************************** +Change log: + 10/04/05: Add Doxygen format comments + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join structures + 01/31/06: Add support to selectively enabe the FW Scan channel filter + 02/16/06: Clear scan in progress flag when scan command failed and dropped + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/04/06: Add IBSS coalescing related new hostcmd handling + 08/29/06: Add ledgpio private command +********************************************************/ + +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ + +static u16 Commands_Allowed_In_PS[] = { + HostCmd_CMD_802_11_RSSI, + HostCmd_CMD_802_11_HOST_SLEEP_CFG, + HostCmd_CMD_802_11_WAKEUP_CONFIRM, +}; + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function checks if the commans is allowed + * in PS mode not. + * + * @param Command the command ID + * @return TRUE or FALSE + */ +static BOOLEAN +Is_Command_Allowed_In_PS(u16 Command) +{ + int count = sizeof(Commands_Allowed_In_PS) + / sizeof(Commands_Allowed_In_PS[0]); + int i; + + for (i = 0; i < count; i++) { + if (Command == wlan_cpu_to_le16(Commands_Allowed_In_PS[i])) + return TRUE; + } + + return FALSE; +} + +/** + * @brief This function prepares command of get_hw_spec. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_hw_spec(wlan_private * priv, HostCmd_DS_COMMAND * cmd) +{ + HostCmd_DS_GET_HW_SPEC *hwspec = &cmd->params.hwspec; + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_GET_HW_SPEC); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_GET_HW_SPEC) + S_DS_GEN); + memcpy(hwspec->PermanentAddr, priv->adapter->CurrentAddr, ETH_ALEN); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of ps_mode. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_ps_mode(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmd_action) +{ + HostCmd_DS_802_11_PS_MODE *psm = &cmd->params.psmode; + u16 Action = cmd_action; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_PS_MODE); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_PS_MODE) + S_DS_GEN); + psm->Action = wlan_cpu_to_le16(cmd_action); + psm->MultipleDtim = 0; + switch (Action) { + case HostCmd_SubCmd_Enter_PS: + PRINTM(INFO, "PS Command:" "SubCode- Enter PS\n"); + PRINTM(INFO, "LocalListenInterval = %d\n", + Adapter->LocalListenInterval); + + psm->LocalListenInterval = + wlan_cpu_to_le16(Adapter->LocalListenInterval); + psm->NullPktInterval = wlan_cpu_to_le16(Adapter->NullPktInterval); + psm->MultipleDtim = wlan_cpu_to_le16(priv->adapter->MultipleDtim); + psm->BCNMissTimeOut = wlan_cpu_to_le16(priv->adapter->BCNMissTimeOut); + if (priv->adapter->InfrastructureMode == Wlan802_11IBSS) + psm->AdhocAwakePeriod = + wlan_cpu_to_le16(priv->adapter->AdhocAwakePeriod); + break; + + case HostCmd_SubCmd_Exit_PS: + PRINTM(INFO, "PS Command:" "SubCode- Exit PS\n"); + break; + + case HostCmd_SubCmd_Sleep_Confirmed: + PRINTM(INFO, "PS Command: SubCode- sleep confirm\n"); + break; + + default: + break; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of fw_wakeup_method. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_fw_wakeup_method(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int cmd_action, void *pdata_buf) +{ + HostCmd_DS_802_11_FW_WAKEUP_METHOD *fwwm = &cmd->params.fwwakeupmethod; + u16 action = (u16) cmd_action; + u16 method = *((u16 *) pdata_buf); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_FW_WAKE_METHOD); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_FW_WAKEUP_METHOD) + + S_DS_GEN); + fwwm->Action = wlan_cpu_to_le16(action); + switch (action) { + case HostCmd_ACT_SET: + fwwm->Method = wlan_cpu_to_le16(method); + break; + case HostCmd_ACT_GET: + default: + fwwm->Method = 0; + break; + } + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function sends the HS_Activated event to the application + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_host_sleep_activated_event(wlan_private * priv) +{ + ENTER(); + + priv->adapter->HS_Activated = TRUE; + os_carrier_off(priv); + os_stop_queue(priv); + wmm_stop_queue(priv); + +#if WIRELESS_EXT > 14 + send_iwevcustom_event(priv, CUS_EVT_HS_ACTIVATED); +#endif /* WIRELESS_EXT */ + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function sends the HS_DeActivated event to the application + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_host_sleep_deactivated_event(wlan_private * priv) +{ + ENTER(); + + priv->adapter->HS_Activated = FALSE; + +#if WIRELESS_EXT > 14 + send_iwevcustom_event(priv, CUS_EVT_HS_DEACTIVATED); +#endif /* WIRELESS_EXT */ + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function sends the HS_GPIO_INT event to the application + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_host_sleep_gpio_int_event(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (Adapter->bHostSleepConfigured) { +#if WIRELESS_EXT > 14 + send_iwevcustom_event(priv, CUS_EVT_HS_GPIO_INT); +#endif /* WIRELESS_EXT */ + } else { + PRINTM(INFO, "hs_gpio_int: HS not configured !!!\n"); + } + + LEAVE(); + + return ret; +} + +/** + * @brief This function prepares command of host_sleep_cfg. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to HostCmd_DS_802_11_HOST_SLEEP_CFG structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_host_sleep_cfg(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + HostCmd_DS_802_11_HOST_SLEEP_CFG * pdata_buf) +{ + HostCmd_DS_802_11_HOST_SLEEP_CFG *phwuc = &cmd->params.hostsleepcfg; + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_HOST_SLEEP_CFG); + if ((pdata_buf->conditions != HOST_SLEEP_CFG_CANCEL) + && ((priv->adapter->ArpFilterSize > 0) + && (priv->adapter->ArpFilterSize <= ARP_FILTER_MAX_BUF_SIZE))) { + PRINTM(INFO, "Attach %d bytes ArpFilter to HSCfg cmd\n", + priv->adapter->ArpFilterSize); + memcpy(((u8 *) phwuc) + sizeof(HostCmd_DS_802_11_HOST_SLEEP_CFG), + priv->adapter->ArpFilter, priv->adapter->ArpFilterSize); + cmd->Size = + wlan_cpu_to_le16(priv->adapter->ArpFilterSize + + sizeof(HostCmd_DS_802_11_HOST_SLEEP_CFG) + + S_DS_GEN); + } else + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_HOST_SLEEP_CFG) + + S_DS_GEN); + phwuc->conditions = wlan_cpu_to_le32(pdata_buf->conditions); + phwuc->gpio = pdata_buf->gpio; + phwuc->gap = pdata_buf->gap; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of inactivity_timeout. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action Action: GET SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_inactivity_timeout(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + u16 cmd_action, void *pdata_buf) +{ + u16 *timeout = (u16 *) pdata_buf; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_INACTIVITY_TIMEOUT); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_INACTIVITY_TIMEOUT) + + S_DS_GEN); + + cmd->params.inactivity_timeout.Action = wlan_cpu_to_le16(cmd_action); + + if (cmd_action) + cmd->params.inactivity_timeout.Timeout = wlan_cpu_to_le16(*timeout); + else + cmd->params.inactivity_timeout.Timeout = 0; + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of sleep_period. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_sleep_period(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + u16 cmd_action, void *pdata_buf) +{ + HostCmd_DS_802_11_SLEEP_PERIOD *pSleepPeriod = &cmd->params.ps_sleeppd; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_SLEEP_PERIOD); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_SLEEP_PERIOD) + + S_DS_GEN); + memmove(pSleepPeriod, pdata_buf, sizeof(HostCmd_DS_802_11_SLEEP_PERIOD)); + pSleepPeriod->Action = wlan_cpu_to_le16(pSleepPeriod->Action); + pSleepPeriod->Period = wlan_cpu_to_le16(pSleepPeriod->Period); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of sleep_params. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_sleep_params(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmd_action) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_SLEEP_PARAMS *sp = &cmd->params.sleep_params; + + ENTER(); + + cmd->Size = wlan_cpu_to_le16((sizeof(HostCmd_DS_802_11_SLEEP_PARAMS)) + + S_DS_GEN); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_SLEEP_PARAMS); + + if (cmd_action == HostCmd_ACT_GEN_GET) { + memset(&Adapter->sp, 0, sizeof(SleepParams)); + memset(sp, 0, sizeof(HostCmd_DS_802_11_SLEEP_PARAMS)); + sp->Action = wlan_cpu_to_le16(cmd_action); + } else if (cmd_action == HostCmd_ACT_GEN_SET) { + sp->Action = wlan_cpu_to_le16(cmd_action); + sp->Error = wlan_cpu_to_le16(Adapter->sp.sp_error); + sp->Offset = wlan_cpu_to_le16(Adapter->sp.sp_offset); + sp->StableTime = wlan_cpu_to_le16(Adapter->sp.sp_stabletime); + sp->CalControl = (u8) Adapter->sp.sp_calcontrol; + sp->ExternalSleepClk = (u8) Adapter->sp.sp_extsleepclk; + sp->Reserved = wlan_cpu_to_le16(Adapter->sp.sp_reserved); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +#define WEP_40_BIT_LEN 5 +#define WEP_104_BIT_LEN 13 + +/** + * @brief This function prepares command of set_wep. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_oid OID: ADD_WEP KEY or REMOVE_WEP KEY + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_set_wep(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u32 cmd_oid) +{ + HostCmd_DS_802_11_SET_WEP *wep = &cmd->params.wep; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (cmd_oid == OID_802_11_ADD_WEP) { + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_SET_WEP); + cmd->Size = + wlan_cpu_to_le16((sizeof(HostCmd_DS_802_11_SET_WEP)) + S_DS_GEN); + wep->Action = wlan_cpu_to_le16(HostCmd_ACT_ADD); + + /* default tx key index */ + wep->KeyIndex = wlan_cpu_to_le16(Adapter->CurrentWepKeyIndex & + HostCmd_WEP_KEY_INDEX_MASK); + + PRINTM(INFO, "Tx Key Index: %u\n", wep->KeyIndex); + + switch (Adapter->WepKey[0].KeyLength) { + case WEP_40_BIT_LEN: + wep->WEPTypeForKey1 = HostCmd_TYPE_WEP_40_BIT; + memmove(wep->WEP1, Adapter->WepKey[0].KeyMaterial, + Adapter->WepKey[0].KeyLength); + break; + case WEP_104_BIT_LEN: + wep->WEPTypeForKey1 = HostCmd_TYPE_WEP_104_BIT; + memmove(wep->WEP1, Adapter->WepKey[0].KeyMaterial, + Adapter->WepKey[0].KeyLength); + break; + case 0: + break; + default: + PRINTM(INFO, "Key1 Length = %d is incorrect\n", + Adapter->WepKey[0].KeyLength); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + switch (Adapter->WepKey[1].KeyLength) { + case WEP_40_BIT_LEN: + wep->WEPTypeForKey2 = HostCmd_TYPE_WEP_40_BIT; + memmove(wep->WEP2, Adapter->WepKey[1].KeyMaterial, + Adapter->WepKey[1].KeyLength); + break; + case WEP_104_BIT_LEN: + wep->WEPTypeForKey2 = HostCmd_TYPE_WEP_104_BIT; + memmove(wep->WEP2, Adapter->WepKey[1].KeyMaterial, + Adapter->WepKey[1].KeyLength); + break; + case 0: + break; + default: + PRINTM(INFO, "Key2 Length = %d is incorrect\n", + Adapter->WepKey[1].KeyLength); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + switch (Adapter->WepKey[2].KeyLength) { + case WEP_40_BIT_LEN: + wep->WEPTypeForKey3 = HostCmd_TYPE_WEP_40_BIT; + memmove(wep->WEP3, Adapter->WepKey[2].KeyMaterial, + Adapter->WepKey[2].KeyLength); + break; + case WEP_104_BIT_LEN: + wep->WEPTypeForKey3 = HostCmd_TYPE_WEP_104_BIT; + memmove(wep->WEP3, Adapter->WepKey[2].KeyMaterial, + Adapter->WepKey[2].KeyLength); + break; + case 0: + break; + default: + PRINTM(INFO, "Key3 Length = %d is incorrect\n", + Adapter->WepKey[2].KeyLength); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + switch (Adapter->WepKey[3].KeyLength) { + case WEP_40_BIT_LEN: + wep->WEPTypeForKey4 = HostCmd_TYPE_WEP_40_BIT; + memmove(wep->WEP4, Adapter->WepKey[3].KeyMaterial, + Adapter->WepKey[3].KeyLength); + break; + case WEP_104_BIT_LEN: + wep->WEPTypeForKey4 = HostCmd_TYPE_WEP_104_BIT; + memmove(wep->WEP4, Adapter->WepKey[3].KeyMaterial, + Adapter->WepKey[3].KeyLength); + break; + case 0: + break; + default: + PRINTM(INFO, "Key4 Length = %d is incorrect\n", + Adapter->WepKey[3].KeyLength); + ret = WLAN_STATUS_FAILURE; + goto done; + } + } else if (cmd_oid == OID_802_11_REMOVE_WEP) { + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_SET_WEP); + cmd->Size = + wlan_cpu_to_le16((sizeof(HostCmd_DS_802_11_SET_WEP)) + S_DS_GEN); + wep->Action = wlan_cpu_to_le16(HostCmd_ACT_REMOVE); + + /* default tx key index */ + wep->KeyIndex = wlan_cpu_to_le16((u16) (Adapter->CurrentWepKeyIndex & + (u32) + HostCmd_WEP_KEY_INDEX_MASK)); + } + + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + return ret; +} + +/** + * @brief This function prepares command of key_material. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param cmd_oid OID: ENABLE or DISABLE + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_key_material(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + u16 cmd_action, + WLAN_OID cmd_oid, void *pdata_buf) +{ + HostCmd_DS_802_11_KEY_MATERIAL *pKeyMaterial = &cmd->params.keymaterial; + WLAN_802_11_KEY *pKey = (WLAN_802_11_KEY *) pdata_buf; + u16 KeyParamSet_len; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_KEY_MATERIAL); + pKeyMaterial->Action = wlan_cpu_to_le16(cmd_action); + + if (cmd_action == HostCmd_ACT_GET) { + cmd->Size = wlan_cpu_to_le16(2 + S_DS_GEN); + ret = WLAN_STATUS_SUCCESS; + goto done; + } + + memset(&pKeyMaterial->KeyParamSet, 0, sizeof(MrvlIEtype_KeyParamSet_t)); + + if (pKey->KeyLength == WPA_AES_KEY_LEN) { + PRINTM(INFO, "WPA_AES\n"); + pKeyMaterial->KeyParamSet.KeyTypeId = + wlan_cpu_to_le16(KEY_TYPE_ID_AES); + + if (cmd_oid == (WLAN_OID) KEY_INFO_ENABLED) + pKeyMaterial->KeyParamSet.KeyInfo = + wlan_cpu_to_le16(KEY_INFO_AES_ENABLED); + else + pKeyMaterial->KeyParamSet.KeyInfo = + !(wlan_cpu_to_le16(KEY_INFO_AES_ENABLED)); + + if (pKey->KeyIndex & 0x40000000) //AES pairwise key: unicast + pKeyMaterial->KeyParamSet.KeyInfo |= + wlan_cpu_to_le16(KEY_INFO_AES_UNICAST); + else //AES group key: multicast + pKeyMaterial->KeyParamSet.KeyInfo |= + wlan_cpu_to_le16(KEY_INFO_AES_MCAST); + } else if (pKey->KeyLength == WPA_TKIP_KEY_LEN) { + PRINTM(INFO, "WPA_TKIP\n"); + pKeyMaterial->KeyParamSet.KeyTypeId = + wlan_cpu_to_le16(KEY_TYPE_ID_TKIP); + pKeyMaterial->KeyParamSet.KeyInfo = + wlan_cpu_to_le16(KEY_INFO_TKIP_ENABLED); + + if (pKey->KeyIndex & 0x40000000) //TKIP pairwise key: unicast + pKeyMaterial->KeyParamSet.KeyInfo |= + wlan_cpu_to_le16(KEY_INFO_TKIP_UNICAST); + else //TKIP group key: multicast + pKeyMaterial->KeyParamSet.KeyInfo |= + wlan_cpu_to_le16(KEY_INFO_TKIP_MCAST); + } + + if (pKeyMaterial->KeyParamSet.KeyTypeId) { + pKeyMaterial->KeyParamSet.Type = + wlan_cpu_to_le16(TLV_TYPE_KEY_MATERIAL); + pKeyMaterial->KeyParamSet.KeyLen = wlan_cpu_to_le16(pKey->KeyLength); + memcpy(pKeyMaterial->KeyParamSet.Key, + pKey->KeyMaterial, pKey->KeyLength); + pKeyMaterial->KeyParamSet.Length = + wlan_cpu_to_le16(pKey->KeyLength + 6); + +#define TYPE_LEN_FIELDS_LEN 4 + KeyParamSet_len = (pKey->KeyLength + 6) + TYPE_LEN_FIELDS_LEN; +#define ACTION_FIELD_LEN 2 + cmd->Size = + wlan_cpu_to_le16(KeyParamSet_len + ACTION_FIELD_LEN + S_DS_GEN); + } + + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + return ret; +} + +/** + * @brief This function prepares command of get_log. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_get_log(wlan_private * priv, HostCmd_DS_COMMAND * cmd) +{ + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_GET_LOG); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_GET_LOG) + S_DS_GEN); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of snmp_mib. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param cmd_oid the OID of SNMP MIB + * @param pdata_buf the pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_snmp_mib(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int cmd_action, int cmd_oid, void *pdata_buf) +{ + HostCmd_DS_802_11_SNMP_MIB *pSNMPMIB = &cmd->params.smib; + wlan_adapter *Adapter = priv->adapter; + u8 ucTemp; + + ENTER(); + + PRINTM(INFO, "SNMP_CMD: cmd_oid = 0x%x\n", cmd_oid); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_SNMP_MIB); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_SNMP_MIB) + S_DS_GEN); + + switch (cmd_oid) { + case OID_802_11_INFRASTRUCTURE_MODE: + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_SET); + pSNMPMIB->OID = wlan_cpu_to_le16((u16) DesiredBssType_i); + pSNMPMIB->BufSize = wlan_cpu_to_le16(sizeof(u8)); + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) + ucTemp = SNMP_MIB_VALUE_INFRA; + else + ucTemp = SNMP_MIB_VALUE_ADHOC; + + memmove(pSNMPMIB->Value, &ucTemp, sizeof(u8)); + + break; + + case OID_802_11D_ENABLE: + { + u32 ulTemp; + + pSNMPMIB->OID = wlan_cpu_to_le16((u16) Dot11D_i); + + if (cmd_action == HostCmd_ACT_SET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_SET); + pSNMPMIB->BufSize = wlan_cpu_to_le16(sizeof(u16)); + ulTemp = *(u32 *) pdata_buf; + *((PUSHORT) (pSNMPMIB->Value)) = + wlan_cpu_to_le16((u16) ulTemp); + } + break; + } + + case OID_802_11_FRAGMENTATION_THRESHOLD: + { + WLAN_802_11_FRAGMENTATION_THRESHOLD ulTemp; + + pSNMPMIB->OID = wlan_cpu_to_le16((u16) FragThresh_i); + + if (cmd_action == HostCmd_ACT_GET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_GET); + } else if (cmd_action == HostCmd_ACT_SET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_SET); + pSNMPMIB->BufSize = wlan_cpu_to_le16(sizeof(u16)); + ulTemp = *((WLAN_802_11_FRAGMENTATION_THRESHOLD *) + pdata_buf); + *((PUSHORT) (pSNMPMIB->Value)) = + wlan_cpu_to_le16((u16) ulTemp); + + } + + break; + } + + case OID_802_11_RTS_THRESHOLD: + { + + WLAN_802_11_RTS_THRESHOLD ulTemp; + pSNMPMIB->OID = wlan_cpu_to_le16((u16) RtsThresh_i); + + if (cmd_action == HostCmd_ACT_GET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_GET); + } else if (cmd_action == HostCmd_ACT_SET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_SET); + pSNMPMIB->BufSize = wlan_cpu_to_le16(sizeof(u16)); + ulTemp = *((WLAN_802_11_RTS_THRESHOLD *) + pdata_buf); + *(PUSHORT) (pSNMPMIB->Value) = wlan_cpu_to_le16((u16) ulTemp); + } + break; + } + case OID_802_11_TX_RETRYCOUNT: + pSNMPMIB->OID = wlan_cpu_to_le16((u16) ShortRetryLim_i); + + if (cmd_action == HostCmd_ACT_GET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_GET); + } else if (cmd_action == HostCmd_ACT_SET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_SET); + pSNMPMIB->BufSize = wlan_cpu_to_le16(sizeof(u16)); + *((PUSHORT) (pSNMPMIB->Value)) = + wlan_cpu_to_le16((u16) Adapter->TxRetryCount); + } + break; + + case OID_802_11_DTIM: + pSNMPMIB->OID = wlan_cpu_to_le16((u16) DtimPeriod_i); + + if (cmd_action == HostCmd_ACT_GET) { + pSNMPMIB->QueryType = wlan_cpu_to_le16(HostCmd_ACT_GEN_GET); + } + break; + + default: + break; + } + + PRINTM(INFO, + "SNMP_CMD: Command=0x%x, Size=0x%x, SeqNum=0x%x, Result=0x%x\n", + cmd->Command, cmd->Size, cmd->SeqNum, cmd->Result); + + PRINTM(INFO, + "SNMP_CMD: Action=0x%x, OID=0x%x, OIDSize=0x%x, Value=0x%x\n", + pSNMPMIB->QueryType, pSNMPMIB->OID, pSNMPMIB->BufSize, + *(u16 *) pSNMPMIB->Value); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of radio_control. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_radio_control(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, int cmd_action) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_RADIO_CONTROL *pRadioControl = &cmd->params.radio; + + ENTER(); + + cmd->Size = wlan_cpu_to_le16((sizeof(HostCmd_DS_802_11_RADIO_CONTROL)) + + S_DS_GEN); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_RADIO_CONTROL); + + pRadioControl->Action = wlan_cpu_to_le16(cmd_action); + pRadioControl->Control = wlan_cpu_to_le16(Adapter->RadioOn); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of bca_timeshare. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param user_bca_ts A pointer to HostCmd_DS_802_11_BCA_TIMESHARE structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_bca_timeshare(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + u16 cmd_action, + HostCmd_DS_802_11_BCA_TIMESHARE * user_bca_ts) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_BCA_TIMESHARE *bca_ts = &cmd->params.bca_timeshare; + + ENTER(); + + cmd->Size = wlan_cpu_to_le16((sizeof(HostCmd_DS_802_11_BCA_TIMESHARE)) + + S_DS_GEN); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_BCA_CONFIG_TIMESHARE); + + if (cmd_action == HostCmd_ACT_GEN_GET) { + memset(&Adapter->bca_ts, 0, sizeof(bca_ts)); + memset(bca_ts, 0, sizeof(HostCmd_DS_802_11_BCA_TIMESHARE)); + bca_ts->Action = wlan_cpu_to_le16(cmd_action); + bca_ts->TrafficType = wlan_cpu_to_le16(user_bca_ts->TrafficType); + } else if (cmd_action == HostCmd_ACT_GEN_SET) { + bca_ts->Action = wlan_cpu_to_le16(cmd_action); + bca_ts->TrafficType = wlan_cpu_to_le16(user_bca_ts->TrafficType); + bca_ts->TimeShareInterval = + wlan_cpu_to_le32(user_bca_ts->TimeShareInterval); + bca_ts->BTTime = wlan_cpu_to_le32(user_bca_ts->BTTime); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of rf_tx_power. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_rf_tx_power(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + u16 cmd_action, void *pdata_buf) +{ + + HostCmd_DS_802_11_RF_TX_POWER *pRTP = &cmd->params.txp; + + ENTER(); + + cmd->Size = + wlan_cpu_to_le16((sizeof(HostCmd_DS_802_11_RF_TX_POWER)) + S_DS_GEN); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_RF_TX_POWER); + pRTP->Action = cmd_action; + + PRINTM(INFO, "RF_TX_POWER_CMD: Size:%d Cmd:0x%x Act:%d\n", cmd->Size, + cmd->Command, pRTP->Action); + + switch (cmd_action) { + case HostCmd_ACT_GEN_GET: + pRTP->Action = wlan_cpu_to_le16(HostCmd_ACT_GEN_GET); + pRTP->CurrentLevel = 0; + break; + + case HostCmd_ACT_GEN_SET: + pRTP->Action = wlan_cpu_to_le16(HostCmd_ACT_GEN_SET); + pRTP->CurrentLevel = wlan_cpu_to_le16(*((u16 *) pdata_buf)); + break; + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of rf_antenna. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_rf_antenna(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + u16 cmd_action, void *pdata_buf) +{ + HostCmd_DS_802_11_RF_ANTENNA *rant = &cmd->params.rant; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_RF_ANTENNA); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_RF_ANTENNA) + S_DS_GEN); + + rant->Action = wlan_cpu_to_le16(cmd_action); + if ((cmd_action == HostCmd_ACT_SET_RX) || + (cmd_action == HostCmd_ACT_SET_TX)) { + rant->AntennaMode = + wlan_cpu_to_le16((u16) (*(WLAN_802_11_ANTENNA *) pdata_buf)); + } + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of rate_adapt_rateset. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_rate_adapt_rateset(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmd_action) +{ + HostCmd_DS_802_11_RATE_ADAPT_RATESET * rateadapt = &cmd->params.rateset; + wlan_adapter *Adapter = priv->adapter; + + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_RATE_ADAPT_RATESET) + + S_DS_GEN); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_RATE_ADAPT_RATESET); + + ENTER(); + + rateadapt->Action = wlan_cpu_to_le16(cmd_action); + rateadapt->HWRateDropMode = wlan_cpu_to_le16(Adapter->HWRateDropMode); + rateadapt->Threshold = wlan_cpu_to_le16(Adapter->Threshold); + rateadapt->FinalRate = wlan_cpu_to_le16(Adapter->FinalRate); + rateadapt->Bitmap = wlan_cpu_to_le16(Adapter->RateBitmap); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of mac_multicast_adr. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_mac_multicast_adr(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmd_action) +{ + HostCmd_DS_MAC_MULTICAST_ADR *pMCastAdr = &cmd->params.madr; + wlan_adapter *Adapter = priv->adapter; + + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_MAC_MULTICAST_ADR) + S_DS_GEN); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_MAC_MULTICAST_ADR); + + pMCastAdr->Action = wlan_cpu_to_le16(cmd_action); + pMCastAdr->NumOfAdrs = + wlan_cpu_to_le16((u16) Adapter->NumOfMulticastMACAddr); + memcpy(pMCastAdr->MACList, Adapter->MulticastList, + Adapter->NumOfMulticastMACAddr * ETH_ALEN); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of rf_channel. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_rf_channel(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int option, void *pdata_buf) +{ + HostCmd_DS_802_11_RF_CHANNEL *rfchan = &cmd->params.rfchannel; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_RF_CHANNEL); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_RF_CHANNEL) + + S_DS_GEN); + + if (option == HostCmd_OPT_802_11_RF_CHANNEL_SET) { + rfchan->CurrentChannel = wlan_cpu_to_le16(*((u16 *) pdata_buf)); + } + + rfchan->Action = wlan_cpu_to_le16(option); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of rssi. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_rssi(wlan_private * priv, HostCmd_DS_COMMAND * cmd) +{ + wlan_adapter *Adapter = priv->adapter; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_RSSI); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_RSSI) + S_DS_GEN); + cmd->params.rssi.N = wlan_cpu_to_le16(Adapter->bcn_avg_factor); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of reg_access. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_reg_access(wlan_private * priv, + HostCmd_DS_COMMAND * CmdPtr, + u8 cmd_action, void *pdata_buf) +{ + wlan_offset_value *offval; + + ENTER(); + + offval = (wlan_offset_value *) pdata_buf; + + switch (CmdPtr->Command) { + case HostCmd_CMD_MAC_REG_ACCESS: + { + HostCmd_DS_MAC_REG_ACCESS *macreg; + + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_MAC_REG_ACCESS) + + S_DS_GEN); + macreg = (HostCmd_DS_MAC_REG_ACCESS *) & CmdPtr->params.macreg; + + macreg->Action = wlan_cpu_to_le16(cmd_action); + macreg->Offset = wlan_cpu_to_le16((u16) offval->offset); + macreg->Value = wlan_cpu_to_le32(offval->value); + + break; + } + + case HostCmd_CMD_BBP_REG_ACCESS: + { + HostCmd_DS_BBP_REG_ACCESS *bbpreg; + + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_BBP_REG_ACCESS) + + S_DS_GEN); + bbpreg = (HostCmd_DS_BBP_REG_ACCESS *) & CmdPtr->params.bbpreg; + + bbpreg->Action = wlan_cpu_to_le16(cmd_action); + bbpreg->Offset = wlan_cpu_to_le16((u16) offval->offset); + bbpreg->Value = (u8) offval->value; + + break; + } + + case HostCmd_CMD_RF_REG_ACCESS: + { + HostCmd_DS_RF_REG_ACCESS *rfreg; + + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_RF_REG_ACCESS) + S_DS_GEN); + rfreg = (HostCmd_DS_RF_REG_ACCESS *) & CmdPtr->params.rfreg; + + rfreg->Action = wlan_cpu_to_le16(cmd_action); + rfreg->Offset = wlan_cpu_to_le16((u16) offval->offset); + rfreg->Value = (u8) offval->value; + + break; + } + + default: + break; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of mac_address. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_mac_address(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, u16 cmd_action) +{ + wlan_adapter *Adapter = priv->adapter; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_MAC_ADDRESS); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_MAC_ADDRESS) + + S_DS_GEN); + cmd->Result = 0; + + cmd->params.macadd.Action = wlan_cpu_to_le16(cmd_action); + + if (cmd_action == HostCmd_ACT_SET) { + memcpy(cmd->params.macadd.MacAdd, Adapter->CurrentAddr, ETH_ALEN); + HEXDUMP("SET_CMD: MAC ADDRESS-", Adapter->CurrentAddr, 6); + } + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of cal_data_ext. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_cal_data_ext(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pdata_buf) +{ + HostCmd_DS_802_11_CAL_DATA_EXT *PCalDataext = pdata_buf; + + HostCmd_DS_802_11_CAL_DATA_EXT *pCmdCalData = + (HostCmd_DS_802_11_CAL_DATA_EXT *) & cmd->params.caldataext; + + ENTER(); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_CAL_DATA_EXT); + + PRINTM(INFO, "CalDataLen = %d(d)\n", PCalDataext->CalDataLen); + + if (PCalDataext->CalDataLen > + MAX_SETGET_CONF_CMD_LEN - CAL_DATA_HEADER_LEN) { + PRINTM(MSG, "CAL_DATA_EXT: Cal data lenght too large!\n"); + return WLAN_STATUS_FAILURE; + } + + memcpy(pCmdCalData, PCalDataext, + PCalDataext->CalDataLen + CAL_DATA_HEADER_LEN); + + pCmdCalData->Action = wlan_cpu_to_le16(pCmdCalData->Action); + pCmdCalData->Revision = wlan_cpu_to_le16(pCmdCalData->Revision); + pCmdCalData->CalDataLen = wlan_cpu_to_le16(pCmdCalData->CalDataLen); + + cmd->Size = wlan_cpu_to_le16(PCalDataext->CalDataLen + + CAL_DATA_HEADER_LEN + S_DS_GEN); + + PRINTM(INFO, "CAL_DATA_EXT: cmd->Size = %d(d)\n", cmd->Size); + + cmd->Result = 0; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of eeprom_access. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_802_11_eeprom_access(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int cmd_action, void *pdata_buf) +{ + wlan_ioctl_regrdwr *ea = pdata_buf; + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_EEPROM_ACCESS); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_EEPROM_ACCESS) + S_DS_GEN); + cmd->Result = 0; + + cmd->params.rdeeprom.Action = wlan_cpu_to_le16(ea->Action); + cmd->params.rdeeprom.Offset = wlan_cpu_to_le16(ea->Offset); + cmd->params.rdeeprom.ByteCount = wlan_cpu_to_le16(ea->NOB); + cmd->params.rdeeprom.Value = 0; + + return WLAN_STATUS_SUCCESS; +} + +static int +wlan_cmd_802_11_IBSS_Coalesced_Status(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int cmd_action, void *pdata_buf) +{ + HostCmd_DS_802_11_IBSS_Status *pIBSSReq = &(cmd->params.ibssCoalescing); + u16 *enable = pdata_buf; + + PRINTM(INFO, "HostCmd_CMD_802_11_BSSID_QUERY request"); + + cmd->Command = + wlan_cpu_to_le16(HostCmd_CMD_802_11_IBSS_COALESCING_STATUS); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_IBSS_Status) + S_DS_GEN); + cmd->Result = 0; + pIBSSReq->Action = wlan_cpu_to_le16(cmd_action); + + switch (cmd_action) { + case HostCmd_ACT_SET: + pIBSSReq->Enable = wlan_cpu_to_le16(*enable); + break; + + /* In other case.. Noting to do */ + case HostCmd_ACT_GET: + default: + break; + } + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function queues the command to cmd list. + * + * @param Adapter A pointer to wlan_adapter structure + * @param CmdNode A pointer to CmdCtrlNode structure + * @param addtail specify if the cmd needs to be queued in the header or tail + * @return n/a + */ +void +QueueCmd(wlan_adapter * Adapter, CmdCtrlNode * CmdNode, BOOLEAN addtail) +{ + ulong flags; + HostCmd_DS_COMMAND *CmdPtr; + u16 command; + + ENTER(); + + if (!CmdNode) { + PRINTM(WARN, "QUEUE_CMD: CmdNode is NULL\n"); + goto done; + } + + CmdPtr = (HostCmd_DS_COMMAND *) CmdNode->BufVirtualAddr; + if (!CmdPtr) { + PRINTM(WARN, "QUEUE_CMD: CmdPtr is NULL\n"); + goto done; + } + + command = wlan_le16_to_cpu(CmdPtr->Command); + + /* Exit_PS command needs to be queued in the header always. */ + if (command == HostCmd_CMD_802_11_PS_MODE) { + HostCmd_DS_802_11_PS_MODE *psm = &CmdPtr->params.psmode; + if (wlan_le16_to_cpu(psm->Action) == HostCmd_SubCmd_Exit_PS) { + if (Adapter->PSState != PS_STATE_FULL_POWER) + addtail = FALSE; + } + } + + if ((command == HostCmd_CMD_802_11_WAKEUP_CONFIRM) + || (command == HostCmd_CMD_802_11_HOST_SLEEP_ACTIVATE) + || (command == HostCmd_CMD_802_11_HOST_SLEEP_CFG) + ) { + addtail = FALSE; + } + + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + + if (addtail) + list_add_tail((struct list_head *) CmdNode, &Adapter->CmdPendingQ); + else + list_add((struct list_head *) CmdNode, &Adapter->CmdPendingQ); + + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + PRINTM(INFO, "QUEUE_CMD: cmd=0x%x is queued\n", command); + + done: + LEAVE(); + return; +} + +#ifdef MFG_CMD_SUPPORT +/** + * @brief This function sends general command to firmware. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf A pointer to data buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_mfg_cmd(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pdata_buf) +{ + HostCmd_DS_GEN *pCmdPtr; + + ENTER(); + + pCmdPtr = (HostCmd_DS_GEN *) pdata_buf; + + /* copy the MFG command to command buffer */ + memcpy((void *) cmd, pdata_buf, pCmdPtr->Size); + + PRINTM(INFO, "MFG command size = %d\n", pCmdPtr->Size); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_MFG_COMMAND); + cmd->Size = wlan_cpu_to_le16(cmd->Size); + cmd->Result = 0; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} +#endif + +/** + * @brief This function downloads the command to firmware. + * + * @param priv A pointer to wlan_private structure + * @param CmdNode A pointer to CmdCtrlNode structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +DownloadCommandToStation(wlan_private * priv, CmdCtrlNode * CmdNode) +{ + ulong flags; + HostCmd_DS_COMMAND *CmdPtr; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + u16 CmdSize; + u16 Command; + + OS_INTERRUPT_SAVE_AREA; + + ENTER(); + + if (!Adapter || !CmdNode) { + PRINTM(ERROR, "DNLD_CMD: Adapter = %#x, CmdNode = %#x\n", + (int) Adapter, (int) CmdNode); + if (CmdNode) + CleanupAndInsertCmd(priv, CmdNode); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + CmdPtr = (HostCmd_DS_COMMAND *) CmdNode->BufVirtualAddr; + + if (!CmdPtr || !CmdPtr->Size) { + PRINTM(ERROR, "DNLD_CMD: CmdPtr is Null or Cmd Size is Zero, " + "Not sending\n"); + CleanupAndInsertCmd(priv, CmdNode); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Set command sequence number */ + Adapter->SeqNum++; + CmdPtr->SeqNum = wlan_cpu_to_le16(Adapter->SeqNum); + + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = CmdNode; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + Command = wlan_le16_to_cpu(CmdPtr->Command); + CmdSize = wlan_le16_to_cpu(CmdPtr->Size); + + CmdNode->CmdWaitQWoken = FALSE; + + ret = sbi_host_to_card(priv, MVMS_CMD, (u8 *) CmdPtr, CmdSize); + + /* clear TxDone interrupt bit */ + OS_INT_DISABLE; + Adapter->HisRegCpy &= ~HIS_TxDnLdRdy; + OS_INT_RESTORE; + + if (ret != 0) { + PRINTM(ERROR, "DNLD_CMD: Host to Card Failed\n"); + /* set error code that will be transferred back to PrepareAndSendCommand() */ + Adapter->CurCmdRetCode = WLAN_STATUS_FAILURE; + CleanupAndInsertCmd(priv, Adapter->CurCmd); + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = NULL; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + Adapter->dbg.num_cmd_host_to_card_failure++; + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Save the last command id and action to debug log */ + Adapter->dbg.LastCmdIndex = (Adapter->dbg.LastCmdIndex + 1) % DBG_CMD_NUM; + Adapter->dbg.LastCmdId[Adapter->dbg.LastCmdIndex] = Command; + Adapter->dbg.LastCmdAct[Adapter->dbg.LastCmdIndex] = + wlan_le16_to_cpu(*(u16 *) ((u8 *) CmdPtr + S_DS_GEN)); + + PRINTM(CMND, "DNLD_CMD: 0x%x, act 0x%x, len %d, seqno %d @ %lu\n", + Command, wlan_le16_to_cpu(*(u16 *) ((u8 *) CmdPtr + S_DS_GEN)), + CmdSize, wlan_le16_to_cpu(CmdPtr->SeqNum), os_time_get()); + DBG_HEXDUMP(CMD_D, "DNLD_CMD", CmdNode->BufVirtualAddr, CmdSize); + + /* Setup the timer after transmit command */ + if (Command == HostCmd_CMD_802_11_SCAN + || Command == HostCmd_CMD_802_11_DEAUTHENTICATE + || Command == HostCmd_CMD_802_11_ASSOCIATE + || Command == HostCmd_CMD_WMM_ADDTS_REQ) { + ModTimer(&Adapter->MrvDrvCommandTimer, MRVDRV_TIMER_10S); + } else { + ModTimer(&Adapter->MrvDrvCommandTimer, MRVDRV_TIMER_5S); + } + + Adapter->CommandTimerIsSet = TRUE; + + if (Command == HostCmd_CMD_802_11_DEEP_SLEEP) { + if (Adapter->IntCounter || Adapter->CurrentTxSkb) + PRINTM(INFO, "DNLD_CMD: DS- IntCnt=%d CurTxSkb=%s\n", + Adapter->IntCounter, (Adapter->CurrentTxSkb) ? "Y" : "N"); + + if (Adapter->IntCounter) { + OS_INT_DISABLE; + Adapter->IntCounterSaved = Adapter->IntCounter; + Adapter->IntCounter = 0; + OS_INT_RESTORE; + } + if (Adapter->CurrentTxSkb) { + kfree_skb(Adapter->CurrentTxSkb); + OS_INT_DISABLE; + Adapter->CurrentTxSkb = NULL; + OS_INT_RESTORE; + priv->stats.tx_dropped++; + } + /* 1. change the PS state to DEEP_SLEEP + * 2. since there is no response for this command, so + * delete the command timer and free the Node. */ + + Adapter->IsDeepSleep = TRUE; + + CleanupAndInsertCmd(priv, CmdNode); + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = NULL; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + if (Adapter->CommandTimerIsSet) { + CancelTimer(&Adapter->MrvDrvCommandTimer); + Adapter->CommandTimerIsSet = FALSE; + } + + if (!Adapter->IsAutoDeepSleepEnabled + || (Adapter->bHostSleepConfigured && + (Adapter->HSCfg.gpio != HOST_SLEEP_CFG_WAKEUP_THRU_INTERFACE)) + ) + /* stop clock to save more power */ + sbi_set_bus_clock(priv, FALSE); + + if (Adapter->IsAutoDeepSleepEnabled) { + Adapter->bWakeupDevRequired = TRUE; + /* For auto deep sleep mode, after entering deep sleep state, + * dnld_sent flag should be cleared so that the commands in + * pending queue can be handled by main thread. */ + priv->wlan_dev.dnld_sent = DNLD_RES_RECEIVED; + } + + if (Adapter->bHostSleepConfigured) { + Adapter->bWakeupDevRequired = TRUE; + wlan_host_sleep_activated_event(priv); + } + + } + + ret = WLAN_STATUS_SUCCESS; + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function prepares command of mac_control. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_cmd_mac_control(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf) +{ + HostCmd_DS_MAC_CONTROL *mac = &cmd->params.macctrl; + u16 Action = *((u16 *) InfoBuf); + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_MAC_CONTROL); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_MAC_CONTROL) + S_DS_GEN); + mac->Action = wlan_cpu_to_le16(Action); + + PRINTM(INFO, "wlan_cmd_mac_control(): Action=0x%X Size=%d\n", + mac->Action, cmd->Size); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function inserts command node to CmdFreeQ + * after cleans it. + * + * @param priv A pointer to wlan_private structure + * @param pTempCmd A pointer to CmdCtrlNode structure + * @return n/a + */ +void +CleanupAndInsertCmd(wlan_private * priv, CmdCtrlNode * pTempCmd) +{ + ulong flags; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!pTempCmd) + goto done; + + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + CleanUpCmdCtrlNode(pTempCmd); + list_add_tail((struct list_head *) pTempCmd, &Adapter->CmdFreeQ); + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + done: + LEAVE(); +} + +/** + * @brief This function prepare the command before send to firmware. + * + * @param priv A pointer to wlan_private structure + * @param cmd_no command number + * @param cmd_action command action: GET or SET + * @param wait_option wait option: wait response or not + * @param cmd_oid cmd oid: treated as sub command + * @param pdata_buf A pointer to informaion buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +PrepareAndSendCommand(wlan_private * priv, + u16 cmd_no, + u16 cmd_action, + u16 wait_option, WLAN_OID cmd_oid, void *pdata_buf) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + CmdCtrlNode *CmdNode; + HostCmd_DS_COMMAND *CmdPtr; + + ENTER(); + + if (!Adapter) { + PRINTM(ERROR, "PREP_CMD: Adapter is Null\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if (Adapter->SurpriseRemoved) { + PRINTM(ERROR, "PREP_CMD: Card is Removed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + CmdNode = GetFreeCmdCtrlNode(priv); + + if (CmdNode == NULL) { + PRINTM(MSG, "PREP_CMD: No free CmdNode\n"); + + /* Wake up main thread to execute next command */ + wake_up_interruptible(&priv->MainThread.waitQ); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + SetCmdCtrlNode(priv, CmdNode, cmd_oid, wait_option, pdata_buf); + + CmdPtr = (HostCmd_DS_COMMAND *) CmdNode->BufVirtualAddr; + + if (!CmdPtr) { + PRINTM(MSG, "PREP_CMD: BufVirtualAddr of CmdNode is NULL\n"); + CleanupAndInsertCmd(priv, CmdNode); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + CmdPtr->Command = cmd_no; + CmdPtr->Result = 0; + + TX_EVENT_FLAGS_SET(&CmdNode->cmdwait_q, 0, TX_AND); + switch (cmd_no) { + case HostCmd_CMD_GET_HW_SPEC: + ret = wlan_cmd_hw_spec(priv, CmdPtr); + break; + case HostCmd_CMD_802_11_PS_MODE: + ret = wlan_cmd_802_11_ps_mode(priv, CmdPtr, cmd_action); + break; + + case HostCmd_CMD_802_11_SCAN: + ret = wlan_cmd_802_11_scan(priv, CmdPtr, pdata_buf); + break; + + case HostCmd_CMD_MAC_CONTROL: + ret = wlan_cmd_mac_control(priv, CmdPtr, pdata_buf); + break; + + case HostCmd_CMD_802_11_ASSOCIATE: + ret = wlan_cmd_802_11_associate(priv, CmdPtr, pdata_buf); + break; + + case HostCmd_CMD_802_11_DEAUTHENTICATE: + ret = wlan_cmd_802_11_deauthenticate(priv, CmdPtr); + break; + + case HostCmd_CMD_802_11_SET_WEP: + ret = wlan_cmd_802_11_set_wep(priv, CmdPtr, cmd_oid); + break; + + case HostCmd_CMD_802_11_AD_HOC_START: + ret = wlan_cmd_802_11_ad_hoc_start(priv, CmdPtr, pdata_buf); + break; + case HostCmd_CMD_802_11_RESET: + CmdPtr->Command = wlan_cpu_to_le16(cmd_no); + CmdPtr->Size = wlan_cpu_to_le16(S_DS_GEN); + break; + + case HostCmd_CMD_802_11_GET_LOG: + ret = wlan_cmd_802_11_get_log(priv, CmdPtr); + break; + + case HostCmd_CMD_802_11_SNMP_MIB: + ret = wlan_cmd_802_11_snmp_mib(priv, CmdPtr, + cmd_action, cmd_oid, pdata_buf); + break; + + case HostCmd_CMD_MAC_REG_ACCESS: + case HostCmd_CMD_BBP_REG_ACCESS: + case HostCmd_CMD_RF_REG_ACCESS: + ret = wlan_cmd_reg_access(priv, CmdPtr, cmd_action, pdata_buf); + break; + + case HostCmd_CMD_802_11_RF_CHANNEL: + ret = wlan_cmd_802_11_rf_channel(priv, CmdPtr, cmd_action, pdata_buf); + break; + + case HostCmd_CMD_802_11_RF_TX_POWER: + ret = wlan_cmd_802_11_rf_tx_power(priv, CmdPtr, + cmd_action, pdata_buf); + break; + + case HostCmd_CMD_802_11_RADIO_CONTROL: + ret = wlan_cmd_802_11_radio_control(priv, CmdPtr, cmd_action); + break; + + case HostCmd_CMD_802_11_RF_ANTENNA: + ret = wlan_cmd_802_11_rf_antenna(priv, CmdPtr, cmd_action, pdata_buf); + break; + + case HostCmd_CMD_802_11_RATE_ADAPT_RATESET: + ret = wlan_cmd_802_11_rate_adapt_rateset(priv, CmdPtr, cmd_action); + break; + + case HostCmd_CMD_MAC_MULTICAST_ADR: + ret = wlan_cmd_mac_multicast_adr(priv, CmdPtr, cmd_action); + break; + + case HostCmd_CMD_802_11_AD_HOC_JOIN: + ret = wlan_cmd_802_11_ad_hoc_join(priv, CmdPtr, pdata_buf); + break; + + case HostCmd_CMD_802_11_RSSI: + ret = wlan_cmd_802_11_rssi(priv, CmdPtr); + break; + + case HostCmd_CMD_802_11_AD_HOC_STOP: + ret = wlan_cmd_802_11_ad_hoc_stop(priv, CmdPtr); + break; + case HostCmd_CMD_802_11_KEY_MATERIAL: + ret = wlan_cmd_802_11_key_material(priv, CmdPtr, + cmd_action, cmd_oid, pdata_buf); + break; + + case HostCmd_CMD_802_11_MAC_ADDRESS: + ret = wlan_cmd_802_11_mac_address(priv, CmdPtr, cmd_action); + break; + case HostCmd_CMD_802_11_CAL_DATA_EXT: + ret = wlan_cmd_802_11_cal_data_ext(priv, CmdPtr, pdata_buf); + break; + + case HostCmd_CMD_802_11_DEEP_SLEEP: + CmdPtr->Command = wlan_cpu_to_le16(cmd_no); + CmdPtr->Size = wlan_cpu_to_le16((u16) + (sizeof + (HostCmd_DS_802_11_DEEP_SLEEP))); + break; + + case HostCmd_CMD_802_11_HOST_SLEEP_CFG: + ret = wlan_cmd_802_11_host_sleep_cfg(priv, CmdPtr, pdata_buf); + break; + case HostCmd_CMD_802_11_WAKEUP_CONFIRM: + case HostCmd_CMD_802_11_HOST_SLEEP_ACTIVATE: + CmdPtr->Command = wlan_cpu_to_le16(cmd_no); + CmdPtr->Size = wlan_cpu_to_le16(S_DS_GEN); + break; + + case HostCmd_CMD_802_11_EEPROM_ACCESS: + ret = wlan_cmd_802_11_eeprom_access(priv, CmdPtr, + cmd_action, pdata_buf); + break; + +#ifdef MFG_CMD_SUPPORT + case HostCmd_CMD_MFG_COMMAND: + ret = wlan_cmd_mfg_cmd(priv, CmdPtr, pdata_buf); + break; +#endif + + case HostCmd_CMD_802_11D_DOMAIN_INFO: + ret = wlan_cmd_802_11d_domain_info(priv, CmdPtr, cmd_no, cmd_action); + break; + + case HostCmd_CMD_802_11_SLEEP_PARAMS: + ret = wlan_cmd_802_11_sleep_params(priv, CmdPtr, cmd_action); + break; + case HostCmd_CMD_802_11_BCA_CONFIG_TIMESHARE: + ret = wlan_cmd_802_11_bca_timeshare(priv, CmdPtr, + cmd_action, pdata_buf); + break; + case HostCmd_CMD_802_11_INACTIVITY_TIMEOUT: + ret = wlan_cmd_802_11_inactivity_timeout(priv, CmdPtr, + cmd_action, pdata_buf); + break; + case HostCmd_CMD_802_11_BG_SCAN_CONFIG: + ret = wlan_cmd_802_11_bg_scan_config(priv, CmdPtr, + cmd_action, pdata_buf); + break; + + case HostCmd_CMD_802_11_BG_SCAN_QUERY: + ret = wlan_cmd_802_11_bg_scan_query(priv, CmdPtr); + break; + + case HostCmd_CMD_802_11_FW_WAKE_METHOD: + ret = wlan_cmd_802_11_fw_wakeup_method(priv, CmdPtr, + cmd_action, pdata_buf); + break; + + case HostCmd_CMD_WMM_GET_STATUS: + ret = wlan_cmd_wmm_get_status(priv, CmdPtr, pdata_buf); + break; + + case HostCmd_CMD_WMM_ADDTS_REQ: + ret = wlan_cmd_wmm_addts_req(priv, CmdPtr, pdata_buf); + break; + case HostCmd_CMD_WMM_DELTS_REQ: + ret = wlan_cmd_wmm_delts_req(priv, CmdPtr, pdata_buf); + break; + case HostCmd_CMD_WMM_QUEUE_CONFIG: + ret = wlan_cmd_wmm_queue_config(priv, CmdPtr, pdata_buf); + break; + case HostCmd_CMD_WMM_QUEUE_STATS: + ret = wlan_cmd_wmm_queue_stats(priv, CmdPtr, pdata_buf); + break; + case HostCmd_CMD_TX_PKT_STATS: + CmdPtr->Command = wlan_cpu_to_le16(HostCmd_CMD_TX_PKT_STATS); + CmdPtr->Size = wlan_cpu_to_le16(S_DS_GEN); + ret = WLAN_STATUS_SUCCESS; + break; + case HostCmd_CMD_802_11_TPC_CFG: + CmdPtr->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_TPC_CFG); + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_TPC_CFG) + S_DS_GEN); + + memmove(&CmdPtr->params.tpccfg, + pdata_buf, sizeof(HostCmd_DS_802_11_TPC_CFG)); + CmdPtr->params.tpccfg.Action = + wlan_cpu_to_le16(CmdPtr->params.tpccfg.Action); + + ret = WLAN_STATUS_SUCCESS; + break; + case HostCmd_CMD_802_11_LED_CONTROL: + { + HostCmd_DS_802_11_LED_CTRL *pLedCtrl = &CmdPtr->params.ledgpio; + MrvlIEtypes_LedGpio_t *gpio = &pLedCtrl->LedGpio; + MrvlIEtypes_LedBehavior_t *pLedBehavior = pLedCtrl->LedBehavior; + + memmove(&CmdPtr->params.ledgpio, + pdata_buf, sizeof(HostCmd_DS_802_11_LED_CTRL)); + + CmdPtr->Command = + wlan_cpu_to_le16(HostCmd_CMD_802_11_LED_CONTROL); + +#define ACTION_NUMLED_TLVTYPE_LEN_FIELDS_LEN 8 + CmdPtr->Size = wlan_cpu_to_le16(gpio->Header.Len + S_DS_GEN + + + ACTION_NUMLED_TLVTYPE_LEN_FIELDS_LEN); + + pLedCtrl->Action = wlan_cpu_to_le16(pLedCtrl->Action); + pLedCtrl->LedNums = wlan_cpu_to_le16(pLedCtrl->LedNums); + + gpio->Header.Type = wlan_cpu_to_le16(gpio->Header.Type); + gpio->Header.Len = wlan_cpu_to_le16(gpio->Header.Len); + + pLedBehavior->Header.Type = + wlan_cpu_to_le16(pLedBehavior->Header.Type); + pLedBehavior->Header.Len = + wlan_cpu_to_le16(pLedBehavior->Header.Len); + + ret = WLAN_STATUS_SUCCESS; + break; + } + case HostCmd_CMD_802_11_SLEEP_PERIOD: + ret = wlan_cmd_802_11_sleep_period(priv, CmdPtr, + cmd_action, pdata_buf); + break; + case HostCmd_CMD_GET_TSF: + CmdPtr->Command = wlan_cpu_to_le16(HostCmd_CMD_GET_TSF); + CmdPtr->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_GET_TSF) + + S_DS_GEN); + ret = WLAN_STATUS_SUCCESS; + break; + case HostCmd_CMD_802_11_TX_RATE_QUERY: + CmdPtr->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_TX_RATE_QUERY); + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_TX_RATE_QUERY) + S_DS_GEN); + Adapter->TxRate = 0; + ret = WLAN_STATUS_SUCCESS; + break; + case HostCmd_CMD_802_11_IBSS_COALESCING_STATUS: + ret = + wlan_cmd_802_11_IBSS_Coalesced_Status(priv, CmdPtr, cmd_action, + pdata_buf); + break; + + case HostCmd_CMD_SDIO_GPIO_INT_CONFIG: + { + HostCmd_DS_SDIO_INT_CONFIG *pSdioIntConf = + &CmdPtr->params.sdio_int; + + CmdPtr->Command = + wlan_cpu_to_le16(HostCmd_CMD_SDIO_GPIO_INT_CONFIG); + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_SDIO_INT_CONFIG) + + S_DS_GEN); + + memcpy(pSdioIntConf, pdata_buf, + sizeof(HostCmd_DS_SDIO_INT_CONFIG)); + pSdioIntConf->Action = wlan_cpu_to_le16(pSdioIntConf->Action); + pSdioIntConf->Gpio_pin = wlan_cpu_to_le16(pSdioIntConf->Gpio_pin); + pSdioIntConf->Gpio_int_edge = + wlan_cpu_to_le16(pSdioIntConf->Gpio_int_edge); + pSdioIntConf->Gpio_pulse_width = + wlan_cpu_to_le16(pSdioIntConf->Gpio_pulse_width); + + ret = WLAN_STATUS_SUCCESS; + break; + } + + case HostCmd_CMD_SDIO_PULL_CTRL: + { + HostCmd_DS_SDIO_PULL_CTRL *pSdiopullctl = + &CmdPtr->params.sdiopullctl; + + CmdPtr->Command = wlan_cpu_to_le16(HostCmd_CMD_SDIO_PULL_CTRL); + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_SDIO_PULL_CTRL) + + S_DS_GEN); + + memcpy(pSdiopullctl, pdata_buf, + sizeof(HostCmd_DS_SDIO_PULL_CTRL)); + pSdiopullctl->Action = wlan_cpu_to_le16(pSdiopullctl->Action); + pSdiopullctl->PullUp = wlan_cpu_to_le16(pSdiopullctl->PullUp); + pSdiopullctl->PullDown = wlan_cpu_to_le16(pSdiopullctl->PullDown); + + ret = WLAN_STATUS_SUCCESS; + break; + } + + case HostCmd_CMD_802_11_LDO_CONFIG: + CmdPtr->Command = wlan_cpu_to_le16(cmd_no); + CmdPtr->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_LDO_CONFIG) + S_DS_GEN); + memcpy(&CmdPtr->params.ldocfg, pdata_buf, + sizeof(HostCmd_DS_802_11_LDO_CONFIG)); + CmdPtr->params.ldocfg.Action = + wlan_cpu_to_le16(CmdPtr->params.ldocfg.Action); + CmdPtr->params.ldocfg.PMSource = + wlan_cpu_to_le16(CmdPtr->params.ldocfg.PMSource); + break; + + case HostCmd_CMD_VERSION_EXT: + CmdPtr->Command = wlan_cpu_to_le16(cmd_no); + memcpy(&CmdPtr->params, pdata_buf, sizeof(HostCmd_DS_VERSION_EXT)); + CmdPtr->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_VERSION_EXT) + + S_DS_GEN); + break; + + default: + PRINTM(INFO, "PREP_CMD: unknown command- %#x\n", cmd_no); + ret = WLAN_STATUS_FAILURE; + break; + } + + /* return error, since the command preparation failed */ + if (ret != WLAN_STATUS_SUCCESS) { + PRINTM(ERROR, "PREP_CMD: Command 0x%x preparation failed\n", cmd_no); + CleanupAndInsertCmd(priv, CmdNode); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + PRINTM(CMND, "PREP_CMD: 0x%x\n", cmd_no); + + CmdNode->CmdWaitQWoken = FALSE; + QueueCmd(Adapter, CmdNode, TRUE); + wake_up_interruptible(&priv->MainThread.waitQ); + + if (wait_option & HostCmd_OPTION_WAITFORRSP) { + PRINTM(INFO, "PREP_CMD: Wait for CMD response...\n"); + wait_event_interruptible(CmdNode->cmdwait_q, CmdNode->CmdWaitQWoken); + if (Adapter->CurCmdRetCode) { + PRINTM(INFO, "PREP_CMD: Command failed with return code=%d\n", + Adapter->CurCmdRetCode); + Adapter->CurCmdRetCode = 0; + ret = WLAN_STATUS_FAILURE; + } + } + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function allocates the command buffer and link + * it to command free queue. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +AllocateCmdBuffer(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + u32 ulBufSize; + u32 i; + CmdCtrlNode *TempCmdArray; + u8 *pTempVirtualAddr; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + /* Allocate and initialize CmdCtrlNode */ + ulBufSize = sizeof(CmdCtrlNode) * MRVDRV_NUM_OF_CMD_BUFFER; + + if (!(TempCmdArray = kmalloc(ulBufSize, GFP_KERNEL))) { + PRINTM(INFO, "ALLOC_CMD_BUF: Failed to allocate TempCmdArray\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + Adapter->CmdArray = TempCmdArray; + memset(Adapter->CmdArray, 0, ulBufSize); + + /* Allocate and initialize command buffers */ + ulBufSize = MRVDRV_SIZE_OF_CMD_BUFFER; + for (i = 0; i < MRVDRV_NUM_OF_CMD_BUFFER; i++) { + if (!(pTempVirtualAddr = kmalloc(ulBufSize, GFP_KERNEL))) { + PRINTM(INFO, "ALLOC_CMD_BUF: pTempVirtualAddr: out of memory\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + memset(pTempVirtualAddr, 0, ulBufSize); + + /* Update command buffer virtual */ + TempCmdArray[i].BufVirtualAddr = pTempVirtualAddr; + } + + for (i = 0; i < MRVDRV_NUM_OF_CMD_BUFFER; i++) { + init_waitqueue_head(&TempCmdArray[i].cmdwait_q); + CleanupAndInsertCmd(priv, &TempCmdArray[i]); + } + + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + return ret; +} + +/** + * @brief This function frees the command buffer. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +FreeCmdBuffer(wlan_private * priv) +{ + u32 ulBufSize; + UINT i; + CmdCtrlNode *TempCmdArray; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + /* need to check if cmd array is allocated or not */ + if (Adapter->CmdArray == NULL) { + PRINTM(INFO, "FREE_CMD_BUF: CmdArray is Null\n"); + goto done; + } + + TempCmdArray = Adapter->CmdArray; + + /* Release shared memory buffers */ + ulBufSize = MRVDRV_SIZE_OF_CMD_BUFFER; + for (i = 0; i < MRVDRV_NUM_OF_CMD_BUFFER; i++) { + if (TempCmdArray[i].BufVirtualAddr) { + PRINTM(INFO, "Free all the array\n"); + kfree(TempCmdArray[i].BufVirtualAddr); + TempCmdArray[i].BufVirtualAddr = NULL; + } + } + + /* Release CmdCtrlNode */ + if (Adapter->CmdArray) { + PRINTM(INFO, "Free CmdArray\n"); + kfree(Adapter->CmdArray); + Adapter->CmdArray = NULL; + } + + done: + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function gets a free command node if available in + * command free queue. + * + * @param priv A pointer to wlan_private structure + * @return CmdCtrlNode A pointer to CmdCtrlNode structure or NULL + */ +CmdCtrlNode * +GetFreeCmdCtrlNode(wlan_private * priv) +{ + CmdCtrlNode *TempNode; + wlan_adapter *Adapter = priv->adapter; + ulong flags; + + ENTER(); + + if (!Adapter) + return NULL; + + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + + if (!list_empty(&Adapter->CmdFreeQ)) { + TempNode = (CmdCtrlNode *) Adapter->CmdFreeQ.next; + list_del((struct list_head *) TempNode); + } else { + PRINTM(WARN, "GET_CMD_NODE: CmdCtrlNode is not available\n"); + TempNode = NULL; + } + + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + if (TempNode) { + CleanUpCmdCtrlNode(TempNode); + } + + LEAVE(); + return TempNode; +} + +/** + * @brief This function cleans command node. + * + * @param pTempNode A pointer to CmdCtrlNode structure + * @return n/a + */ +void +CleanUpCmdCtrlNode(CmdCtrlNode * pTempNode) +{ + ENTER(); + + if (!pTempNode) + return; + pTempNode->CmdWaitQWoken = TRUE; + + if (pTempNode->wait_option & HostCmd_OPTION_WAITFORRSP) { + wake_up_interruptible(&pTempNode->cmdwait_q); + } + pTempNode->Status = 0; + pTempNode->cmd_oid = (WLAN_OID) 0; + pTempNode->wait_option = 0; + pTempNode->CmdFlags = 0; + pTempNode->pdata_buf = NULL; + + if (pTempNode->BufVirtualAddr != NULL) + memset(pTempNode->BufVirtualAddr, 0, MRVDRV_SIZE_OF_CMD_BUFFER); + + LEAVE(); + return; +} + +/** + * @brief This function initializes the command node. + * + * @param priv A pointer to wlan_private structure + * @param pTempNode A pointer to CmdCtrlNode structure + * @param cmd_oid cmd oid: treated as sub command + * @param wait_option wait option: wait response or not + * @param pdata_buf A pointer to informaion buffer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +void +SetCmdCtrlNode(wlan_private * priv, + CmdCtrlNode * pTempNode, + WLAN_OID cmd_oid, u16 wait_option, void *pdata_buf) +{ + ENTER(); + + if (!pTempNode) + return; + + pTempNode->cmd_oid = cmd_oid; + pTempNode->wait_option = wait_option; + pTempNode->pdata_buf = pdata_buf; + + LEAVE(); +} + +/** + * @brief This function executes next command in command + * pending queue. It will put fimware back to PS mode + * if applicable. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +ExecuteNextCommand(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + CmdCtrlNode *CmdNode = NULL; + HostCmd_DS_COMMAND *CmdPtr; + ulong flags; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (!Adapter) { + PRINTM(MSG, "EXEC_NEXT_CMD: Adapter is NULL\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + + if (Adapter->CurCmd) { + PRINTM(MSG, "EXEC_NEXT_CMD: there is command in processing!\n"); + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if (!list_empty(&Adapter->CmdPendingQ)) { + CmdNode = (CmdCtrlNode *) + Adapter->CmdPendingQ.next; + } + + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + if (CmdNode) { + CmdPtr = (HostCmd_DS_COMMAND *) CmdNode->BufVirtualAddr; + + if (Is_Command_Allowed_In_PS(CmdPtr->Command)) { + if ((Adapter->PSState == PS_STATE_SLEEP) + || (Adapter->PSState == PS_STATE_PRE_SLEEP) + ) { + PRINTM(INFO, + "EXEC_NEXT_CMD: Cannot send cmd 0x%x in PSState %d\n", + CmdPtr->Command, Adapter->PSState); + ret = WLAN_STATUS_FAILURE; + goto done; + } + PRINTM(INFO, "EXEC_NEXT_CMD: OK to send command " + "0x%x in PSState %d\n", CmdPtr->Command, Adapter->PSState); + } else if (Adapter->PSState != PS_STATE_FULL_POWER) { + /* + * 1. Non-PS command: + * Queue it. set NeedToWakeup to TRUE if current state + * is SLEEP, otherwise call PSWakeup to send Exit_PS. + * 2. PS command but not Exit_PS: + * Ignore it. + * 3. PS command Exit_PS: + * Set NeedToWakeup to TRUE if current state is SLEEP, + * otherwise send this command down to firmware + * immediately. + */ + if (CmdPtr->Command != + wlan_cpu_to_le16(HostCmd_CMD_802_11_PS_MODE)) { + /* Prepare to send Exit PS, + * this non PS command will be sent later */ + if ((Adapter->PSState == PS_STATE_SLEEP) + || (Adapter->PSState == PS_STATE_PRE_SLEEP) + ) { + /* w/ new scheme, it will not reach here. + since it is blocked in main_thread. */ + Adapter->NeedToWakeup = TRUE; + } else + PSWakeup(priv, 0); + + ret = WLAN_STATUS_SUCCESS; + goto done; + } else { + /* + * PS command. Ignore it if it is not Exit_PS. + * otherwise send it down immediately. + */ + HostCmd_DS_802_11_PS_MODE *psm = &CmdPtr->params.psmode; + + PRINTM(INFO, "EXEC_NEXT_CMD: PS cmd- Action=0x%x\n", + psm->Action); + if (psm->Action != wlan_cpu_to_le16(HostCmd_SubCmd_Exit_PS)) { + PRINTM(INFO, "EXEC_NEXT_CMD: Ignore Enter PS cmd\n"); + list_del((struct list_head *) CmdNode); + CleanupAndInsertCmd(priv, CmdNode); + + ret = WLAN_STATUS_SUCCESS; + goto done; + } + + if ((Adapter->PSState == PS_STATE_SLEEP) + || (Adapter->PSState == PS_STATE_PRE_SLEEP) + ) { + PRINTM(INFO, + "EXEC_NEXT_CMD: Ignore ExitPS cmd in sleep\n"); + list_del((struct list_head *) CmdNode); + CleanupAndInsertCmd(priv, CmdNode); + Adapter->NeedToWakeup = TRUE; + + ret = WLAN_STATUS_SUCCESS; + goto done; + } + + PRINTM(INFO, "EXEC_NEXT_CMD: Sending Exit_PS down...\n"); + } + } + list_del((struct list_head *) CmdNode); + DownloadCommandToStation(priv, CmdNode); + } else { + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + /* + * check if in power save mode, if yes, put the device back + * to PS mode + */ + if ((Adapter->PSMode != Wlan802_11PowerModeCAM) && + (Adapter->PSState == PS_STATE_FULL_POWER)) { + if (Adapter->SecInfo.WPAEnabled + || Adapter->SecInfo.WPA2Enabled) { + if (Adapter->IsGTK_SET) { + PRINTM(INFO, "EXEC_NEXT_CMD: WPA enabled and GTK_SET" + " go back to PS_SLEEP"); + PSSleep(priv, 0); + } + } else { + if ((Adapter->InfrastructureMode != Wlan802_11IBSS) + || Adapter->CurBssParams.BSSDescriptor.ATIMWindow) { + PRINTM(INFO, "EXEC_NEXT_CMD: Command PendQ is empty," + " go back to PS_SLEEP"); + PSSleep(priv, 0); + } + } + } + } else { + /* + * check if in auto deep sleep mode, if yes, put the device back + * to DS mode + */ + if (Adapter->IsAutoDeepSleepEnabled && !Adapter->IntCounter) { + PRINTM(INFO, "Entering Auto Deep Sleep mode...\n"); + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_DEEP_SLEEP, 0, + 0, 0, NULL); + } + } + /* The hs_activate command is sent when Host Sleep is configured + and de-activated in full power mode. */ + if (Adapter->bHostSleepConfigured && !Adapter->HS_Activated + && ((Adapter->MediaConnectStatus == WlanMediaStateConnected) + || (!Adapter->IsAutoDeepSleepEnabled)) + && (((Adapter->PSMode == Wlan802_11PowerModeCAM) + && (Adapter->PSState == PS_STATE_FULL_POWER)) + || ((Adapter->InfrastructureMode == Wlan802_11IBSS) + && !Adapter->CurBssParams.BSSDescriptor.ATIMWindow) + )) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_HOST_SLEEP_ACTIVATE, + 0, 0, 0, NULL); + } + } + + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + return ret; +} + +/** + * @brief This function handles the timeout of command sending. + * It will re-send the same command again. + * + * @param FunctionContext A pointer to FunctionContext + * @return n/a + */ +void +MrvDrvCommandTimerFunction(void *FunctionContext) +{ + wlan_private *priv = (wlan_private *) FunctionContext; + wlan_adapter *Adapter = priv->adapter; + CmdCtrlNode *pTempNode; + HostCmd_DS_COMMAND *CmdPtr; + + ENTER(); + + PRINTM(CMND, "Command timeout.\n"); + + Adapter->CommandTimerIsSet = FALSE; + + if (!Adapter->num_cmd_timeout) + Adapter->dbg.num_cmd_timeout++; + + pTempNode = Adapter->CurCmd; + + if (pTempNode == NULL) { + PRINTM(INFO, "CurCmd Empty\n"); + goto exit; + } + + CmdPtr = (HostCmd_DS_COMMAND *) pTempNode->BufVirtualAddr; + if (CmdPtr == NULL) { + goto exit; + } + + if (CmdPtr->Size) { + Adapter->dbg.TimeoutCmdId = wlan_cpu_to_le16(CmdPtr->Command); + Adapter->dbg.TimeoutCmdAct = + wlan_cpu_to_le16(*(u16 *) ((u8 *) CmdPtr + S_DS_GEN)); + PRINTM(CMND, "Timeout cmd = 0x%x, act = 0x%x\n", + Adapter->dbg.TimeoutCmdId, Adapter->dbg.TimeoutCmdAct); + } +#define MAX_CMD_TIMEOUT_COUNT 3 + Adapter->num_cmd_timeout++; + if (Adapter->num_cmd_timeout > MAX_CMD_TIMEOUT_COUNT) { + PRINTM(FATAL, "num_cmd_timeout=%d\n", Adapter->num_cmd_timeout); + goto exit; + } + + /* Restart the timer to trace command response again */ + ModTimer(&Adapter->MrvDrvCommandTimer, MRVDRV_TIMER_1S); + Adapter->CommandTimerIsSet = TRUE; + + /* Wake up main thread to read int status register */ + Adapter->IntCounter++; + wake_up_interruptible(&priv->MainThread.waitQ); + + exit: + LEAVE(); + return; +} + +/** + * @brief This function sends sleep confirm command to firmware. + * + * @param priv A pointer to wlan_private structure + * @param cmdptr A pointer to the command + * @param size the size of command + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +SendConfirmSleep(wlan_private * priv, u8 * CmdPtr, u16 size) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + static u32 i = 0; + + ENTER(); + + HEXDUMP("SLEEP_CFM", CmdPtr, size); + + ret = sbi_host_to_card(priv, MVMS_CMD, CmdPtr, size); + priv->wlan_dev.dnld_sent = DNLD_RES_RECEIVED; + + if (ret) { + PRINTM(MSG, "SLEEP_CFM: sbi_host_to_card() failed\n"); + Adapter->dbg.num_cmd_sleep_cfm_host_to_card_failure++; + } else { + Adapter->PSState = PS_STATE_SLEEP; + + if (Adapter->bHostSleepConfigured && + (Adapter->sleep_period.period == 0)) { + Adapter->bWakeupDevRequired = TRUE; + + wlan_host_sleep_activated_event(priv); + } +#define NUM_SC_PER_LINE 16 + if (++i % NUM_SC_PER_LINE == 0) { + PRINTM(EVENT, "+\n"); + } else { + PRINTM(EVENT, "+"); + } + + /* check if interrupt is received after sleep confirm */ + if (Adapter->IntCounter) { + PRINTM(INFO, "SLEEP_CFM: After sent, IntCnt=%d\n", + Adapter->IntCounter); + Adapter->PSState = PS_STATE_AWAKE; + + } + + } + + LEAVE(); + return ret; +} + +/** + * @brief This function sends Enter_PS command to firmware. + * + * @param priv A pointer to wlan_private structure + * @param wait_option wait response or not + * @return n/a + */ +void +PSSleep(wlan_private * priv, int wait_option) +{ + + ENTER(); + + PrepareAndSendCommand(priv, HostCmd_CMD_802_11_PS_MODE, + HostCmd_SubCmd_Enter_PS, wait_option, 0, NULL); + + LEAVE(); + return; +} + +/** + * @brief This function sends Eixt_PS command to firmware. + * + * @param priv A pointer to wlan_private structure + * @param wait_option wait response or not + * @return n/a + */ +void +PSWakeup(wlan_private * priv, int wait_option) +{ + WLAN_802_11_POWER_MODE LocalPSMode; + + ENTER(); + + LocalPSMode = Wlan802_11PowerModeCAM; + + PrepareAndSendCommand(priv, HostCmd_CMD_802_11_PS_MODE, + HostCmd_SubCmd_Exit_PS, + wait_option, 0, &LocalPSMode); + + LEAVE(); + return; +} + +/** + * @brief This function checks condition and prepares to + * send sleep confirm command to firmware if ok. + * + * @param priv A pointer to wlan_private structure + * @param PSMode Power Saving mode + * @return n/a + */ +void +PSConfirmSleep(wlan_private * priv, u16 PSMode) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!priv->wlan_dev.dnld_sent && !Adapter->CurCmd && !Adapter->IntCounter) { + SendConfirmSleep(priv, (u8 *) & Adapter->PSConfirmSleep, + sizeof(PS_CMD_ConfirmSleep)); + + os_start_queue(priv); + } else { + PRINTM(INFO, "Delay Sleep Confirm (%s%s%s)\n", + (priv->wlan_dev.dnld_sent) ? "D" : "", + (Adapter->CurCmd) ? "C" : "", + (Adapter->IntCounter) ? "I" : ""); + } + + LEAVE(); +} diff --git a/drivers/net/wireless/marvell8686/wlan_cmdresp.c b/drivers/net/wireless/marvell8686/wlan_cmdresp.c new file mode 100644 index 0000000..780c997 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_cmdresp.c @@ -0,0 +1,1595 @@ +/** @file wlan_cmdresp.c + * @brief This file contains the handling of command + * responses as well as events generated by firmware. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/10/05: Add Doxygen format comments + 11/11/05: Add support for WMM Status change event + 12/13/05: Add Proprietary periodic sleep support + 12/23/05: Fix bug in adhoc start where the current index was + not properly being assigned before it was used. + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join structures. + Update assoc response handling; entire IEEE response returned + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support + 04/10/06: Add hostcmd generic API + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/04/06: Add IBSS coalescing related new hostcmd response handling + 05/08/06: Remove PermanentAddr from Adapter + 06/08/06: Remove function HandleMICFailureEvent() + 08/29/06: Add ledgpio private command +********************************************************/ + +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function handles disconnect event. it + * reports disconnect to upper layer, clean tx/rx packets, + * reset link state etc. + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +void +MacEventDisconnected(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + union iwreq_data wrqu; + + ENTER(); + + if (Adapter->MediaConnectStatus != WlanMediaStateConnected) + return; + + PRINTM(INFO, "Handles disconnect event.\n"); + + /* Free Tx and Rx packets, report disconnect to upper layer */ + wlan_clean_txrx(priv); + + memset(wrqu.ap_addr.sa_data, 0x00, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWAP, &wrqu, NULL); + + /* reset SNR/NF/RSSI values */ + memset(Adapter->SNR, 0x00, sizeof(Adapter->SNR)); + memset(Adapter->NF, 0x00, sizeof(Adapter->NF)); + memset(Adapter->RSSI, 0x00, sizeof(Adapter->RSSI)); + memset(Adapter->rawSNR, 0x00, sizeof(Adapter->rawSNR)); + memset(Adapter->rawNF, 0x00, sizeof(Adapter->rawNF)); + Adapter->nextSNRNF = 0; + Adapter->numSNRNF = 0; + Adapter->RxPDRate = 0; + PRINTM(INFO, "Current SSID=%s, Ssid Length=%u\n", + Adapter->CurBssParams.BSSDescriptor.Ssid.Ssid, + Adapter->CurBssParams.BSSDescriptor.Ssid.SsidLength); + PRINTM(INFO, "Previous SSID=%s, Ssid Length=%u\n", + Adapter->PreviousSSID.Ssid, Adapter->PreviousSSID.SsidLength); + + Adapter->SecInfo.WPAEnabled = FALSE; + Adapter->SecInfo.WPA2Enabled = FALSE; + Adapter->Wpa_ie_len = 0; + Adapter->SecInfo.EncryptionMode = CIPHER_NONE; + + Adapter->MediaConnectStatus = WlanMediaStateDisconnected; + + Adapter->AdhocLinkSensed = FALSE; + + /* + * memorize the previous SSID and BSSID + * it could be used for re-assoc + */ + memcpy(&Adapter->PreviousSSID, + &Adapter->CurBssParams.BSSDescriptor.Ssid, + sizeof(WLAN_802_11_SSID)); + memcpy(Adapter->PreviousBSSID, + Adapter->CurBssParams.BSSDescriptor.MacAddress, ETH_ALEN); + + /* need to erase the current SSID and BSSID info */ + memset(&Adapter->CurBssParams, 0x00, sizeof(Adapter->CurBssParams)); + + if (Adapter->PSState != PS_STATE_FULL_POWER) { + /* make firmware to exit PS mode */ + PRINTM(INFO, "Disconnected, so exit PS mode.\n"); + PSWakeup(priv, 0); + } + + LEAVE(); +} + +/** + * @brief This function handles link lost, deauth and + * disassoc events. + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +static void +HandleDisconnectEvent(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + MacEventDisconnected(priv); +#ifdef REASSOCIATION + if (Adapter->Reassoc_on == TRUE) { + PRINTM(INFO, "RE-ASSOC: trigger the timer\n"); + Adapter->ReassocTimerIsSet = TRUE; + ModTimer(&Adapter->MrvDrvTimer, 0); + } +#endif /* REASSOCIATION */ + } +} + +/** + * @brief This function handles the command response of reg_access + * + * @param priv A pointer to wlan_private structure + * @param type the type of reg access (MAC, BBP or RF) + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_reg_access(wlan_private * priv, u16 type, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_offset_value *pOffsetValue = + (wlan_offset_value *) Adapter->CurCmd->pdata_buf; + + ENTER(); + + switch (type) { + case HostCmd_RET_MAC_REG_ACCESS: + { + HostCmd_DS_MAC_REG_ACCESS *reg; + + reg = (HostCmd_DS_MAC_REG_ACCESS *) & resp->params.macreg; + + pOffsetValue->offset = wlan_le16_to_cpu(reg->Offset); + pOffsetValue->value = wlan_le32_to_cpu(reg->Value); + break; + } + + case HostCmd_RET_BBP_REG_ACCESS: + { + HostCmd_DS_BBP_REG_ACCESS *reg; + reg = (HostCmd_DS_BBP_REG_ACCESS *) & resp->params.bbpreg; + + pOffsetValue->offset = wlan_le16_to_cpu(reg->Offset); + pOffsetValue->value = (u8) reg->Value; + break; + } + + case HostCmd_RET_RF_REG_ACCESS: + { + HostCmd_DS_RF_REG_ACCESS *reg; + reg = (HostCmd_DS_RF_REG_ACCESS *) & resp->params.rfreg; + + pOffsetValue->offset = wlan_le16_to_cpu(reg->Offset); + pOffsetValue->value = (u8) reg->Value; + break; + } + + default: + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of get_hw_spec + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_get_hw_spec(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + u32 i; + HostCmd_DS_GET_HW_SPEC *hwspec = &resp->params.hwspec; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + Adapter->fwCapInfo = wlan_le32_to_cpu(hwspec->fwCapInfo); + + Adapter->FWReleaseNumber = wlan_le32_to_cpu(hwspec->FWReleaseNumber); + + PRINTM(INFO, "GET_HW_SPEC: FWReleaseVersion- 0x%X\n", + Adapter->FWReleaseNumber); + PRINTM(INFO, "GET_HW_SPEC: Permanent addr- %2x:%2x:%2x:%2x:%2x:%2x\n", + hwspec->PermanentAddr[0], hwspec->PermanentAddr[1], + hwspec->PermanentAddr[2], hwspec->PermanentAddr[3], + hwspec->PermanentAddr[4], hwspec->PermanentAddr[5]); + PRINTM(INFO, "GET_HW_SPEC: HWIfVersion=0x%X Version=0x%X\n", + wlan_le16_to_cpu(hwspec->HWIfVersion), + wlan_le16_to_cpu(hwspec->Version)); + + Adapter->RegionCode = wlan_le16_to_cpu(hwspec->RegionCode); + + for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { + /* use the region code to search for the index */ + if (Adapter->RegionCode == RegionCodeToIndex[i]) { + break; + } + } + + /* if it's unidentified region code, use the default (USA) */ + if (i >= MRVDRV_MAX_REGION_CODE) { + Adapter->RegionCode = 0x10; + PRINTM(WARN, "unidentified region code, use the default (USA)\n"); + } + + /* HACK IT MAKE RegionCode always equals 0x30 */ + Adapter->RegionCode = 0x30; + + if (Adapter->CurrentAddr[0] == 0xff) { + memmove(Adapter->CurrentAddr, hwspec->PermanentAddr, ETH_ALEN); + } + /* HACK PermanentAddr[0] Equals 0xFF */ + if (Adapter->CurrentAddr[0] == 0xff) { + Adapter->CurrentAddr[0] = 0x00; + Adapter->CurrentAddr[1] = 0x1a; + Adapter->CurrentAddr[2] = 0x6b; + } + memcpy(priv->wlan_dev.netdev->dev_addr, Adapter->CurrentAddr, ETH_ALEN); + + if (wlan_set_regiontable(priv, Adapter->RegionCode, 0)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if (wlan_set_universaltable(priv, 0)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function handles the command response of host_sleep_cfg + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_host_sleep_cfg(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_HOST_SLEEP_CFG *hscfg = &resp->params.hostsleepcfg; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (hscfg->conditions != HOST_SLEEP_CFG_CANCEL) { + Adapter->bHostSleepConfigured = TRUE; + } else { + Adapter->bHostSleepConfigured = FALSE; + if (Adapter->PSState == PS_STATE_FULL_POWER && Adapter->HS_Activated) { + wlan_host_sleep_deactivated_event(priv); + } + os_start_queue(priv); + os_carrier_on(priv); + wmm_start_queue(priv); + } + + LEAVE(); + return ret; +} + +/** + * @brief This function handles the command response of fw_wakeup_method + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_fw_wakeup_method(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_FW_WAKEUP_METHOD *fwwm = &resp->params.fwwakeupmethod; + u16 action; + + ENTER(); + + action = wlan_le16_to_cpu(fwwm->Action); + + switch (action) { + case HostCmd_ACT_GET: + case HostCmd_ACT_SET: + Adapter->fwWakeupMethod = wlan_le16_to_cpu(fwwm->Method); + break; + default: + break; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of sleep_params + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_sleep_params(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_SLEEP_PARAMS *sp = &resp->params.sleep_params; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + PRINTM(INFO, "error=%x offset=%x stabletime=%x calcontrol=%x\n" + " extsleepclk=%x\n", sp->Error, sp->Offset, + sp->StableTime, sp->CalControl, sp->ExternalSleepClk); + Adapter->sp.sp_error = wlan_le16_to_cpu(sp->Error); + Adapter->sp.sp_offset = wlan_le16_to_cpu(sp->Offset); + Adapter->sp.sp_stabletime = wlan_le16_to_cpu(sp->StableTime); + Adapter->sp.sp_calcontrol = wlan_le16_to_cpu(sp->CalControl); + Adapter->sp.sp_extsleepclk = wlan_le16_to_cpu(sp->ExternalSleepClk); + Adapter->sp.sp_reserved = wlan_le16_to_cpu(sp->Reserved); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of sleep_params + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_sleep_period(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_SLEEP_PERIOD *sp_period = &resp->params.ps_sleeppd; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + Adapter->sleep_period.period = wlan_le16_to_cpu(sp_period->Period); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of bca_timeshare + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_bca_timeshare(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_BCA_TIMESHARE *bca_ts = &resp->params.bca_timeshare; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + PRINTM(MSG, "TrafficType=%x TimeShareInterva=%x BTTime=%x\n", + bca_ts->TrafficType, bca_ts->TimeShareInterval, bca_ts->BTTime); + + Adapter->bca_ts.TrafficType = wlan_le16_to_cpu(bca_ts->TrafficType); + Adapter->bca_ts.TimeShareInterval = + wlan_le32_to_cpu(bca_ts->TimeShareInterval); + Adapter->bca_ts.BTTime = wlan_le32_to_cpu(bca_ts->BTTime); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of mac_control + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_mac_control(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of set_wep + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_set_wep(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of reset + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_reset(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + ENTER(); + PRINTM(INFO, "HWAC - Reset command successful\n"); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of snmp_mib + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_snmp_mib(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_SNMP_MIB *smib = &resp->params.smib; + u16 OID = wlan_le16_to_cpu(smib->OID); + u16 QueryType = wlan_le16_to_cpu(smib->QueryType); + + ENTER(); + + PRINTM(INFO, "SNMP_RESP: value of the OID = %x, QueryType=%x\n", OID, + QueryType); + PRINTM(INFO, "SNMP_RESP: Buf size = %x\n", + wlan_le16_to_cpu(smib->BufSize)); + + if (QueryType == HostCmd_ACT_GEN_GET) { + switch (OID) { + case FragThresh_i: + priv->adapter->FragThsd = + wlan_le16_to_cpu(*((PUSHORT) (smib->Value))); + PRINTM(INFO, "SNMP_RESP: FragThsd =%u\n", + priv->adapter->FragThsd); + break; + case RtsThresh_i: + priv->adapter->RTSThsd = + wlan_le16_to_cpu(*((PUSHORT) (smib->Value))); + PRINTM(INFO, "SNMP_RESP: RTSThsd =%u\n", priv->adapter->RTSThsd); + break; + case ShortRetryLim_i: + priv->adapter->TxRetryCount = + wlan_le16_to_cpu(*((PUSHORT) (smib->Value))); + PRINTM(INFO, "SNMP_RESP: TxRetryCount =%u\n", + priv->adapter->RTSThsd); + break; + case DtimPeriod_i: + priv->adapter->Dtim = + wlan_le16_to_cpu(*((PUSHORT) (smib->Value))); + PRINTM(INFO, "SNMP_RESP: DtimPeriod =%u\n", priv->adapter->Dtim); + break; + default: + break; + } + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of radio_control + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_radio_control(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + ENTER(); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of key_material + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_key_material(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_KEY_MATERIAL *pKey = &resp->params.keymaterial; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wlan_le16_to_cpu(pKey->Action) == HostCmd_ACT_SET) { + if ((wlan_le16_to_cpu(pKey->KeyParamSet.KeyInfo) & + KEY_INFO_TKIP_MCAST) + || (wlan_le16_to_cpu(pKey->KeyParamSet.KeyInfo) & + KEY_INFO_AES_MCAST)) { + PRINTM(INFO, "Key: GTK is set\n"); + Adapter->IsGTK_SET = TRUE; + } + } + + memcpy(Adapter->aeskey.KeyParamSet.Key, pKey->KeyParamSet.Key, + sizeof(pKey->KeyParamSet.Key)); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of mac_address + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_mac_address(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_MAC_ADDRESS *MacAdd = &resp->params.macadd; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + memcpy(Adapter->CurrentAddr, MacAdd->MacAdd, ETH_ALEN); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of rf_tx_power + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_rf_tx_power(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_RF_TX_POWER *rtp = &resp->params.txp; + wlan_adapter *Adapter = priv->adapter; + u16 Action = wlan_le16_to_cpu(rtp->Action); + + ENTER(); + + Adapter->TxPowerLevel = wlan_le16_to_cpu(rtp->CurrentLevel); + + if (Action == HostCmd_ACT_GET) { + Adapter->MaxTxPowerLevel = rtp->MaxPower; + Adapter->MinTxPowerLevel = rtp->MinPower; + } + + PRINTM(INFO, "Current TxPower Level = %d,Max Power=%d, Min Power=%d\n", + Adapter->TxPowerLevel, Adapter->MaxTxPowerLevel, + Adapter->MinTxPowerLevel); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of rf_antenna + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_rf_antenna(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_RF_ANTENNA *pAntenna = &resp->params.rant; + wlan_adapter *Adapter = priv->adapter; + u16 Action = wlan_le16_to_cpu(pAntenna->Action); + + if (Action == HostCmd_ACT_GET_RX) + Adapter->RxAntennaMode = wlan_le16_to_cpu(pAntenna->AntennaMode); + + if (Action == HostCmd_ACT_GET_TX) + Adapter->TxAntennaMode = wlan_le16_to_cpu(pAntenna->AntennaMode); + + PRINTM(INFO, "RF_ANT_RESP: Action = 0x%x, Mode = 0x%04x\n", + Action, wlan_le16_to_cpu(pAntenna->AntennaMode)); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of multicast_address + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_mac_multicast_adr(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of rate_adapt_rateset + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_rate_adapt_rateset(wlan_private * priv, + HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_RATE_ADAPT_RATESET *rates = &resp->params.rateset; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wlan_le16_to_cpu(rates->Action) == HostCmd_ACT_GET) { + Adapter->HWRateDropMode = wlan_le16_to_cpu(rates->HWRateDropMode); + Adapter->Threshold = wlan_le16_to_cpu(rates->Threshold); + Adapter->FinalRate = wlan_le16_to_cpu(rates->FinalRate); + Adapter->RateBitmap = wlan_le16_to_cpu(rates->Bitmap); + } + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of rf_channel + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_rf_channel(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_RF_CHANNEL *rfchannel = &resp->params.rfchannel; + wlan_adapter *Adapter = priv->adapter; + u16 Action = wlan_le16_to_cpu(rfchannel->Action); + u16 newChannel = wlan_le16_to_cpu(rfchannel->CurrentChannel); + + ENTER(); + + if (Action == HostCmd_OPT_802_11_RF_CHANNEL_GET + && Adapter->CurBssParams.BSSDescriptor.Channel != newChannel) { + PRINTM(INFO, "Channel Switch: %d to %d\n", + Adapter->CurBssParams.BSSDescriptor.Channel, newChannel); + + /* Update the channel again */ + Adapter->CurBssParams.BSSDescriptor.Channel = newChannel; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of rssi + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_rssi(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_RSSI_RSP *rssirsp = &resp->params.rssirsp; + wlan_adapter *Adapter = priv->adapter; + + /* store the non average value */ + Adapter->SNR[TYPE_BEACON][TYPE_NOAVG] = wlan_le16_to_cpu(rssirsp->SNR); + Adapter->NF[TYPE_BEACON][TYPE_NOAVG] = + wlan_le16_to_cpu(rssirsp->NoiseFloor); + + Adapter->SNR[TYPE_BEACON][TYPE_AVG] = wlan_le16_to_cpu(rssirsp->AvgSNR); + Adapter->NF[TYPE_BEACON][TYPE_AVG] = + wlan_le16_to_cpu(rssirsp->AvgNoiseFloor); + + Adapter->RSSI[TYPE_BEACON][TYPE_NOAVG] = + CAL_RSSI(Adapter->SNR[TYPE_BEACON][TYPE_NOAVG], + Adapter->NF[TYPE_BEACON][TYPE_NOAVG]); + + Adapter->RSSI[TYPE_BEACON][TYPE_AVG] = + CAL_RSSI(Adapter->SNR[TYPE_BEACON][TYPE_AVG] / AVG_SCALE, + Adapter->NF[TYPE_BEACON][TYPE_AVG] / AVG_SCALE); + + PRINTM(INFO, "Beacon RSSI value = 0x%x\n", + Adapter->RSSI[TYPE_BEACON][TYPE_AVG]); + + return WLAN_STATUS_SUCCESS; +} + +#ifdef MFG_CMD_SUPPORT +/** + * @brief This function handles the command response of mfg_cmd + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_mfg_cmd(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + PRINTM(INFO, "MFG command response size = %d\n", resp->Size); + + resp->Size = MIN(resp->Size, MRVDRV_SIZE_OF_CMD_BUFFER); + memcpy(Adapter->CurCmd->pdata_buf, (void *) resp, resp->Size); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} +#endif /* MFG_CMD_SUPPORT */ + +/** + * @brief This function handles the command response of cal_data_ext. + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_802_11_cal_data_ext(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_CAL_DATA_EXT *pCalDataExt = &resp->params.caldataext; + + ENTER(); + + if (wlan_le16_to_cpu(pCalDataExt->Action) == HostCmd_ACT_GEN_GET) { + pCalDataExt->Action = wlan_le16_to_cpu(pCalDataExt->Action); + pCalDataExt->Revision = wlan_le16_to_cpu(pCalDataExt->Revision); + pCalDataExt->CalDataLen = wlan_le16_to_cpu(pCalDataExt->CalDataLen); + + memmove(Adapter->CurCmd->pdata_buf, + pCalDataExt, pCalDataExt->CalDataLen + CAL_DATA_HEADER_LEN); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of eeprom_access + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11_eeprom_access(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_ioctl_regrdwr *pBuf = + (wlan_ioctl_regrdwr *) Adapter->CurCmd->pdata_buf; + + PRINTM(INFO, "eeprom read len=%x\n", + wlan_le16_to_cpu(resp->params.rdeeprom.ByteCount)); + if (pBuf->NOB < wlan_le16_to_cpu(resp->params.rdeeprom.ByteCount)) { + pBuf->NOB = 0; + PRINTM(INFO, "eeprom read return length is too big\n"); + return WLAN_STATUS_FAILURE; + } + pBuf->NOB = wlan_le16_to_cpu(resp->params.rdeeprom.ByteCount); + if (pBuf->NOB > 0) { + memcpy(&pBuf->Value, (u8 *) & resp->params.rdeeprom.Value, pBuf->NOB); + HEXDUMP("EEPROM", (char *) &pBuf->Value, pBuf->NOB); + } + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of get_log + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_ret_get_log(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_GET_LOG *LogMessage = + (HostCmd_DS_802_11_GET_LOG *) & resp->params.glog; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + /* TODO Convert it to Big Endian before copy */ + memcpy(&Adapter->LogMsg, LogMessage, sizeof(HostCmd_DS_802_11_GET_LOG)); + endian_convert_GET_LOG(Adapter->LogMsg); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +static void +wlan_ret_802_11_IBSS_Coalesced_Status(wlan_private * priv, + HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_802_11_IBSS_Status *IBSSStatusRsp; + wlan_adapter *Adapter; + union iwreq_data wrqu; + u8 nullMac[6] = { 0, 0, 0, 0, 0, 0 }; + + Adapter = priv->adapter; + IBSSStatusRsp = &(resp->params.ibssCoalescing); + + if (Adapter->CurCmd->pdata_buf) + *(int *) Adapter->CurCmd->pdata_buf = IBSSStatusRsp->Enable; + + if (wlan_le16_to_cpu(IBSSStatusRsp->Action) == HostCmd_ACT_SET) { + return; + } + + PRINTM(INFO, "New BSSID %x:%x:%x:%x:%x:%x\n", + IBSSStatusRsp->BSSID[0], + IBSSStatusRsp->BSSID[1], + IBSSStatusRsp->BSSID[2], + IBSSStatusRsp->BSSID[3], + IBSSStatusRsp->BSSID[4], IBSSStatusRsp->BSSID[5]); + + /* if rsp has NULL BSSID, Just return.. No Action */ + if (!memcmp(IBSSStatusRsp->BSSID, nullMac, MRVDRV_ETH_ADDR_LEN)) { + PRINTM(MSG, "New BSSID is NULL\n"); + return; + } + + /* if BSSID is diff, Send evnet to Linux */ + if (memcmp(Adapter->CurBssParams.BSSDescriptor.MacAddress, + IBSSStatusRsp->BSSID, ETH_ALEN)) { + memcpy((void *) Adapter->CurBssParams.BSSDescriptor.MacAddress, + (void *) IBSSStatusRsp->BSSID, MRVDRV_ETH_ADDR_LEN); + + /* Beacon Interval and ATIM window */ + Adapter->CurBssParams.BSSDescriptor.BeaconPeriod + = IBSSStatusRsp->BeaconInterval; + Adapter->CurBssParams.BSSDescriptor.ATIMWindow + = IBSSStatusRsp->ATIMWindow; + //ERP Information + Adapter->CurBssParams.BSSDescriptor.ERPFlags = + (u8) IBSSStatusRsp->UseGRateProtection; + + memset(&wrqu, 0, sizeof(wrqu)); + memcpy(wrqu.ap_addr.sa_data, + Adapter->CurBssParams.BSSDescriptor.MacAddress, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWAP, &wrqu, NULL); + + } +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function stop tx/rx queue and free skb + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +void +wlan_clean_txrx(wlan_private * priv) +{ + os_stop_queue(priv); + os_carrier_off(priv); + + wmm_stop_queue(priv); + wmm_cleanup_queues(priv); + + /* Free Tx and Rx packets */ + os_free_tx_packet(priv); + wlan_send_rxskbQ(priv); +} + +/** + * @brief This function handles the command response + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_process_rx_command(wlan_private * priv) +{ + u16 RespCmd; + HostCmd_DS_COMMAND *resp; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + ulong flags; + u16 Result; + + ENTER(); + + /* Now we got response from FW, cancel the command timer */ + if (Adapter->CommandTimerIsSet) { + CancelTimer(&Adapter->MrvDrvCommandTimer); + Adapter->CommandTimerIsSet = FALSE; + } + + if (!Adapter->CurCmd) { + resp = (HostCmd_DS_COMMAND *) priv->wlan_dev.upld_buf; + resp->Command = wlan_le16_to_cpu(resp->Command); + PRINTM(ERROR, "CMD_RESP: NULL CurCmd, 0x%x\n", resp->Command); + ret = WLAN_STATUS_FAILURE; + goto done; + } + Adapter->num_cmd_timeout = 0; + + DBG_HEXDUMP(CMD_D, "CMD_RESP", Adapter->CurCmd->BufVirtualAddr, + priv->wlan_dev.upld_len); + + resp = (HostCmd_DS_COMMAND *) (Adapter->CurCmd->BufVirtualAddr); + + resp->Command = wlan_le16_to_cpu(resp->Command); + resp->Size = wlan_le16_to_cpu(resp->Size); + resp->SeqNum = wlan_le16_to_cpu(resp->SeqNum); + resp->Result = wlan_le16_to_cpu(resp->Result); + + RespCmd = resp->Command; + Result = resp->Result; + + /* Save the last command response to debug log */ + Adapter->dbg.LastCmdRespIndex = + (Adapter->dbg.LastCmdRespIndex + 1) % DBG_CMD_NUM; + Adapter->dbg.LastCmdRespId[Adapter->dbg.LastCmdRespIndex] = RespCmd; + + PRINTM(CMND, "CMD_RESP: 0x%x, result %d, len %d, seqno %d @ %lu\n", + RespCmd, Result, resp->Size, resp->SeqNum, os_time_get()); + + if (!(RespCmd & 0x8000)) { + PRINTM(ERROR, "CMD_RESP: Invalid response to command!"); + Adapter->CurCmdRetCode = WLAN_STATUS_FAILURE; + CleanupAndInsertCmd(priv, Adapter->CurCmd); + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = NULL; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Store the response code to CurCmdRetCode. */ + Adapter->CurCmdRetCode = resp->Result; + + if (RespCmd == HostCmd_RET_802_11_PS_MODE) { + HostCmd_DS_802_11_PS_MODE *psmode; + + psmode = &resp->params.psmode; + PRINTM(INFO, "CMD_RESP: PS_MODE cmd reply result=%#x action=0x%X\n", + resp->Result, psmode->Action); + psmode->Action = wlan_cpu_to_le16(psmode->Action); + + if (Result) { + PRINTM(ERROR, "CMD_RESP: PS command failed- %#x \n", + resp->Result); + if (Adapter->InfrastructureMode == Wlan802_11IBSS) { + /* + * We should not re-try enter-ps command in + * ad-hoc mode. It takes place in + * ExecuteNextCommand(). + */ + if (psmode->Action == HostCmd_SubCmd_Enter_PS) + Adapter->PSMode = Wlan802_11PowerModeCAM; + } + } else if (psmode->Action == HostCmd_SubCmd_Enter_PS) { + Adapter->NeedToWakeup = FALSE; + Adapter->PSState = PS_STATE_AWAKE; + + if (Adapter->MediaConnectStatus != WlanMediaStateConnected) { + /* + * When Deauth Event received before Enter_PS command + * response, We need to wake up the firmware. + */ + PRINTM(INFO, + "CMD_RESP: Disconnected, Going to invoke PSWakeup\n"); + PSWakeup(priv, 0); + } + } else if (psmode->Action == HostCmd_SubCmd_Exit_PS) { + Adapter->NeedToWakeup = FALSE; + Adapter->PSState = PS_STATE_FULL_POWER; + + } else { + PRINTM(INFO, "CMD_RESP: PS- Action=0x%X\n", psmode->Action); + } + + CleanupAndInsertCmd(priv, Adapter->CurCmd); + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = NULL; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + ret = WLAN_STATUS_SUCCESS; + goto done; + } + + if (Adapter->CurCmd->CmdFlags & CMD_F_HOSTCMD) { + /* Copy the response back to response buffer */ + memcpy(Adapter->CurCmd->pdata_buf, resp, resp->Size); + + Adapter->CurCmd->CmdFlags &= ~CMD_F_HOSTCMD; + + if ((Result == HostCmd_RESULT_OK) + && (RespCmd == HostCmd_RET_802_11_HOST_SLEEP_CFG)) { + ret = wlan_ret_host_sleep_cfg(priv, resp); + } + } else { + /* If the command is not successful, cleanup and return failure */ + if ((Result != HostCmd_RESULT_OK || !(RespCmd & 0x8000))) { + PRINTM(ERROR, "CMD_RESP: cmd %#x error, result=%#x\n", + resp->Command, resp->Result); + /* + * Handling errors here + */ + switch (RespCmd) { + case HostCmd_RET_HW_SPEC_INFO: + PRINTM(INFO, "CMD_RESP: HW spec command Failed\n"); + break; + + } + + CleanupAndInsertCmd(priv, Adapter->CurCmd); + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = NULL; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + return WLAN_STATUS_FAILURE; + } + + switch (RespCmd) { + case HostCmd_RET_MAC_REG_ACCESS: + case HostCmd_RET_BBP_REG_ACCESS: + case HostCmd_RET_RF_REG_ACCESS: + ret = wlan_ret_reg_access(priv, RespCmd, resp); + + break; + + case HostCmd_RET_HW_SPEC_INFO: + ret = wlan_ret_get_hw_spec(priv, resp); + break; + + case HostCmd_RET_802_11_BG_SCAN_QUERY: + { + union iwreq_data wrqu; + + ret = wlan_ret_802_11_scan(priv, resp); + memset(&wrqu, 0, sizeof(union iwreq_data)); + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWSCAN, &wrqu, + NULL); + PRINTM(INFO, "CMD_RESP: BG_SCAN result is ready!\n"); + break; + } + case HostCmd_RET_802_11_SCAN: + ret = wlan_ret_802_11_scan(priv, resp); + break; + + case HostCmd_RET_MAC_CONTROL: + ret = wlan_ret_mac_control(priv, resp); + break; + + case HostCmd_RET_802_11_GET_LOG: + ret = wlan_ret_get_log(priv, resp); + break; + + case HostCmd_RET_802_11_ASSOCIATE: + ret = wlan_ret_802_11_associate(priv, resp); + break; + + case HostCmd_RET_802_11_DEAUTHENTICATE: + ret = wlan_ret_802_11_disassociate(priv, resp); + break; + + case HostCmd_RET_802_11_SET_WEP: + ret = wlan_ret_802_11_set_wep(priv, resp); + break; + + case HostCmd_RET_802_11_AD_HOC_START: + case HostCmd_RET_802_11_AD_HOC_JOIN: + ret = wlan_ret_802_11_ad_hoc(priv, resp); + break; + + case HostCmd_RET_802_11_RESET: + ret = wlan_ret_802_11_reset(priv, resp); + break; + + case HostCmd_RET_802_11_SNMP_MIB: + ret = wlan_ret_802_11_snmp_mib(priv, resp); + break; + + case HostCmd_RET_802_11_RF_TX_POWER: + ret = wlan_ret_802_11_rf_tx_power(priv, resp); + break; + + case HostCmd_RET_802_11_RADIO_CONTROL: + ret = wlan_ret_802_11_radio_control(priv, resp); + break; + + case HostCmd_RET_802_11_HOST_SLEEP_CFG: + ret = wlan_ret_host_sleep_cfg(priv, resp); + break; + case HostCmd_RET_802_11_WAKEUP_CONFIRM: + wlan_host_sleep_deactivated_event(priv); + break; + case HostCmd_RET_802_11_HOST_SLEEP_ACTIVATE: + if (Adapter->bHostSleepConfigured + && Adapter->HSCfg.gap == HOST_SLEEP_CFG_GAP_FF) + Adapter->bWakeupDevRequired = TRUE; + wlan_host_sleep_activated_event(priv); + break; + + case HostCmd_RET_802_11_RF_ANTENNA: + ret = wlan_ret_802_11_rf_antenna(priv, resp); + break; + + case HostCmd_RET_MAC_MULTICAST_ADR: + ret = wlan_ret_mac_multicast_adr(priv, resp); + break; + + case HostCmd_RET_802_11_RATE_ADAPT_RATESET: + ret = wlan_ret_802_11_rate_adapt_rateset(priv, resp); + break; + case HostCmd_RET_802_11_RF_CHANNEL: + ret = wlan_ret_802_11_rf_channel(priv, resp); + break; + + case HostCmd_RET_802_11_RSSI: + ret = wlan_ret_802_11_rssi(priv, resp); + break; + + case HostCmd_RET_802_11_MAC_ADDRESS: + ret = wlan_ret_802_11_mac_address(priv, resp); + break; + +#ifdef MFG_CMD_SUPPORT + case HostCmd_RET_MFG_COMMAND: + ret = wlan_ret_mfg_cmd(priv, resp); + break; +#endif + case HostCmd_RET_802_11_AD_HOC_STOP: + ret = wlan_ret_802_11_ad_hoc_stop(priv, resp); + break; + + case HostCmd_RET_802_11_CAL_DATA_EXT: + ret = wlan_ret_802_11_cal_data_ext(priv, resp); + break; + + case HostCmd_RET_802_11_KEY_MATERIAL: + ret = wlan_ret_802_11_key_material(priv, resp); + break; + + case HostCmd_RET_802_11_EEPROM_ACCESS: + ret = wlan_ret_802_11_eeprom_access(priv, resp); + break; + + case HostCmd_RET_802_11D_DOMAIN_INFO: + ret = wlan_ret_802_11d_domain_info(priv, resp); + break; + + case HostCmd_RET_802_11_SLEEP_PARAMS: + ret = wlan_ret_802_11_sleep_params(priv, resp); + break; + case HostCmd_RET_802_11_BCA_CONFIG_TIMESHARE: + ret = wlan_ret_802_11_bca_timeshare(priv, resp); + break; + case HostCmd_RET_802_11_INACTIVITY_TIMEOUT: + *((u16 *) Adapter->CurCmd->pdata_buf) = + wlan_le16_to_cpu(resp->params.inactivity_timeout.Timeout); + break; + case HostCmd_RET_802_11_BG_SCAN_CONFIG: + break; + + case HostCmd_RET_802_11_FW_WAKE_METHOD: + ret = wlan_ret_fw_wakeup_method(priv, resp); + break; + + case HostCmd_RET_802_11_SLEEP_PERIOD: + ret = wlan_ret_802_11_sleep_period(priv, resp); + break; + case HostCmd_RET_WMM_GET_STATUS: + ret = wlan_cmdresp_wmm_get_status(priv, resp); + break; + case HostCmd_RET_WMM_ADDTS_REQ: + ret = wlan_cmdresp_wmm_addts_req(priv, resp); + break; + case HostCmd_RET_WMM_DELTS_REQ: + ret = wlan_cmdresp_wmm_delts_req(priv, resp); + break; + case HostCmd_RET_WMM_QUEUE_CONFIG: + ret = wlan_cmdresp_wmm_queue_config(priv, resp); + break; + case HostCmd_RET_WMM_QUEUE_STATS: + ret = wlan_cmdresp_wmm_queue_stats(priv, resp); + break; + case HostCmd_RET_TX_PKT_STATS: + memcpy(Adapter->CurCmd->pdata_buf, + &resp->params.txPktStats, sizeof(HostCmd_DS_TX_PKT_STATS)); + ret = WLAN_STATUS_SUCCESS; + break; + case HostCmd_RET_802_11_TPC_CFG: + memmove(Adapter->CurCmd->pdata_buf, + &resp->params.tpccfg, sizeof(HostCmd_DS_802_11_TPC_CFG)); + break; + case HostCmd_RET_802_11_LED_CONTROL: + { + HostCmd_DS_802_11_LED_CTRL *pLedCtrl = &resp->params.ledgpio; + MrvlIEtypes_LedGpio_t *pGpio = &pLedCtrl->LedGpio; + MrvlIEtypes_LedBehavior_t *pBehavior = pLedCtrl->LedBehavior; + + pLedCtrl->Action = wlan_le16_to_cpu(pLedCtrl->Action); + pLedCtrl->LedNums = wlan_le16_to_cpu(pLedCtrl->LedNums); + pGpio->Header.Type = wlan_le16_to_cpu(pGpio->Header.Type); + pGpio->Header.Len = wlan_le16_to_cpu(pGpio->Header.Len); + pBehavior->Header.Type = + wlan_le16_to_cpu(pBehavior->Header.Type); + pBehavior->Header.Len = + wlan_le16_to_cpu(pBehavior->Header.Len); + memmove(Adapter->CurCmd->pdata_buf, &resp->params.ledgpio, + sizeof(HostCmd_DS_802_11_LED_CTRL)); + break; + } + + case HostCmd_RET_GET_TSF: + resp->params.gettsf.TsfValue = + wlan_le64_to_cpu(resp->params.gettsf.TsfValue); + memcpy(priv->adapter->CurCmd->pdata_buf, + &resp->params.gettsf.TsfValue, sizeof(u64)); + break; + case HostCmd_RTE_802_11_TX_RATE_QUERY: + priv->adapter->TxRate = + wlan_le16_to_cpu(resp->params.txrate.TxRate); + break; + case HostCmd_RET_802_11_IBSS_COALESCING_STATUS: + wlan_ret_802_11_IBSS_Coalesced_Status(priv, resp); + break; + + case HostCmd_RET_SDIO_GPIO_INT_CONFIG: + memmove(Adapter->CurCmd->pdata_buf, + &resp->params.sdio_int, + sizeof(HostCmd_DS_SDIO_INT_CONFIG)); + break; + + case HostCmd_RET_SDIO_PULL_CTRL: + memmove(Adapter->CurCmd->pdata_buf, + &resp->params.sdiopullctl, + sizeof(HostCmd_DS_SDIO_PULL_CTRL)); + break; + + case HostCmd_RET_802_11_LDO_CONFIG: + resp->params.ldocfg.Action = + wlan_le16_to_cpu(resp->params.ldocfg.Action); + resp->params.ldocfg.PMSource = + wlan_le16_to_cpu(resp->params.ldocfg.PMSource); + memmove(Adapter->CurCmd->pdata_buf, &resp->params.ldocfg, + sizeof(HostCmd_DS_802_11_LDO_CONFIG)); + break; + + case HostCmd_RET_VERSION_EXT: + memcpy(Adapter->CurCmd->pdata_buf, + &resp->params.verext, sizeof(HostCmd_DS_VERSION_EXT)); + break; + + default: + PRINTM(INFO, "CMD_RESP: Unknown command response %#x\n", + resp->Command); + break; + } + } + + if (Adapter->CurCmd) { + /* Clean up and Put current command back to CmdFreeQ */ + CleanupAndInsertCmd(priv, Adapter->CurCmd); + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + Adapter->CurCmd = NULL; + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + } + + done: + LEAVE(); + return ret; +} + +#if WIRELESS_EXT >= 18 +/** + * @brief This function sends mic error event to application. + * + * @param priv A pointer to wlan_private structure + * @para event MIC ERROR EVENT. + * @return n/a + */ +void +send_mic_error_event(wlan_private * priv, u32 event) +{ + union iwreq_data iwrq; + struct iw_michaelmicfailure mic; + + ENTER(); + + memset(&iwrq, 0, sizeof(iwrq)); + memset(&mic, 0, sizeof(mic)); + if (event == MACREG_INT_CODE_MIC_ERR_UNICAST) { + mic.flags = IW_MICFAILURE_PAIRWISE; + } else { + mic.flags = IW_MICFAILURE_GROUP; + } + + iwrq.data.pointer = &mic; + iwrq.data.length = sizeof(mic); + + wireless_send_event(priv->wlan_dev.netdev, IWEVMICHAELMICFAILURE, &iwrq, + (u8 *) & mic); + + LEAVE(); + return; +} +#endif + +/** + * @brief This function handles events generated by firmware + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_process_event(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + u32 eventcause = Adapter->EventCause >> SBI_EVENT_CAUSE_SHIFT; + + ENTER(); + + /* Save the last event to debug log */ + Adapter->dbg.LastEventIndex = + (Adapter->dbg.LastEventIndex + 1) % DBG_CMD_NUM; + Adapter->dbg.LastEvent[Adapter->dbg.LastEventIndex] = eventcause; + + if (eventcause != MACREG_INT_CODE_PS_SLEEP && + eventcause != MACREG_INT_CODE_PS_AWAKE) + PRINTM(EVENT, "EVENT: 0x%x @ %lu\n", eventcause, os_time_get()); + + switch (eventcause) { + case MACREG_INT_CODE_DUMMY_HOST_WAKEUP_SIGNAL: + PRINTM(INFO, "EVENT: DUMMY_HOST_WAKEUP_SIGNAL\n"); + if (!priv->adapter->HS_Activated) { + PRINTM(WARN, "DUMMY_HOST_WAKEUP_SIGNAL (HS_Deactivated)\n"); + } else { + wlan_host_sleep_gpio_int_event(priv); + } + break; + case MACREG_INT_CODE_LINK_SENSED: + PRINTM(INFO, "EVENT: LINK_SENSED\n"); + Adapter->AdhocLinkSensed = TRUE; + os_carrier_on(priv); + os_start_queue(priv); + wmm_start_queue(priv); + send_iwevcustom_event(priv, CUS_EVT_ADHOC_LINK_SENSED); + break; + + case MACREG_INT_CODE_DEAUTHENTICATED: + PRINTM(INFO, "EVENT: Deauthenticated\n"); + Adapter->dbg.num_event_deauth++; + HandleDisconnectEvent(priv); + break; + + case MACREG_INT_CODE_DISASSOCIATED: + PRINTM(INFO, "EVENT: Disassociated\n"); + Adapter->dbg.num_event_disassoc++; + HandleDisconnectEvent(priv); + break; + + case MACREG_INT_CODE_LINK_LOST: + PRINTM(INFO, "EVENT: Link lost\n"); + Adapter->dbg.num_event_link_lost++; + HandleDisconnectEvent(priv); + break; + + case MACREG_INT_CODE_PS_SLEEP: + PRINTM(INFO, "EVENT: SLEEP\n"); + PRINTM(EVENT, "_"); + + /* handle unexpected PS SLEEP event */ + if (Adapter->PSState == PS_STATE_FULL_POWER) { + PRINTM(INFO, "EVENT: In FULL POWER mode - ignore PS SLEEP\n"); + break; + } + Adapter->PSState = PS_STATE_PRE_SLEEP; + PSConfirmSleep(priv, (u16) Adapter->PSMode); + break; + + case MACREG_INT_CODE_PS_AWAKE: + PRINTM(INFO, "EVENT: AWAKE \n"); + PRINTM(EVENT, "|"); + + /* handle unexpected PS AWAKE event */ + if (Adapter->PSState == PS_STATE_FULL_POWER) { + PRINTM(INFO, "EVENT: In FULL POWER mode - ignore PS AWAKE\n"); + break; + } + + Adapter->TxLockFlag = FALSE; + if (TRUE == CheckLastPacketIndication(priv)) { + if (!priv->wlan_dev.dnld_sent && Adapter->gen_null_pkg) { + SendNullPacket(priv, MRVDRV_TxPD_POWER_MGMT_NULL_PACKET | + MRVDRV_TxPD_POWER_MGMT_LAST_PACKET); + Adapter->TxLockFlag = TRUE; + } + } + + Adapter->PSState = PS_STATE_AWAKE; + + if (Adapter->NeedToWakeup == TRUE) { + /* + * wait for the command processing to finish + * before resuming sending + * Adapter->NeedToWakeup will be set to FALSE + * in PSWakup() + */ + PRINTM(INFO, "Waking up...\n"); + PSWakeup(priv, 0); + } + break; + + case MACREG_INT_CODE_DEEP_SLEEP_AWAKE: + sbi_reset_deepsleep_wakeup(priv); + PRINTM(INFO, "EVENT: DS_AWAKE\n"); + if (priv->adapter->IsDeepSleep == TRUE) { + Adapter->IsDeepSleep = FALSE; + Adapter->bWakeupDevRequired = FALSE; + Adapter->WakeupTries = 0; + + /* + * For auto DS + BGS case, some delay is needed to + * avoid going back to DS before getting BGS result + */ + if (Adapter->IsAutoDeepSleepEnabled && + Adapter->bgScanConfig->Enable) + os_sched_timeout(10); + priv->wlan_dev.dnld_sent = DNLD_RES_RECEIVED; + priv->adapter->HisRegCpy |= HIS_TxDnLdRdy; + } + send_iwevcustom_event(priv, CUS_EVT_DEEP_SLEEP_AWAKE); + wake_up_interruptible(&Adapter->ds_awake_q); + break; + + case MACREG_INT_CODE_HOST_SLEEP_AWAKE: + PRINTM(INFO, "EVENT: HS_AWAKE\n"); + Adapter->bWakeupDevRequired = FALSE; + + Adapter->WakeupTries = 0; + + sbi_reset_deepsleep_wakeup(priv); + + /* + * in BG SCAN mode w/ deep sleep, WAKE UP event + * will be sent first, Deep Sleep Awake will + * be sent later. + */ + if (priv->adapter->IsDeepSleep == TRUE) { + priv->adapter->IsDeepSleep = FALSE; + priv->wlan_dev.dnld_sent = DNLD_RES_RECEIVED; + priv->adapter->HisRegCpy |= HIS_TxDnLdRdy; + } + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_WAKEUP_CONFIRM, + 0, 0, 0, NULL); + break; + + case MACREG_INT_CODE_MIC_ERR_UNICAST: + PRINTM(INFO, "EVENT: UNICAST MIC ERROR\n"); +#if WIRELESS_EXT >= 18 + send_mic_error_event(priv, MACREG_INT_CODE_MIC_ERR_UNICAST); +#else + send_iwevcustom_event(priv, CUS_EVT_MLME_MIC_ERR_UNI); +#endif + break; + + case MACREG_INT_CODE_MIC_ERR_MULTICAST: + PRINTM(INFO, "EVENT: MULTICAST MIC ERROR\n"); +#if WIRELESS_EXT >= 18 + send_mic_error_event(priv, MACREG_INT_CODE_MIC_ERR_MULTICAST); +#else + send_iwevcustom_event(priv, CUS_EVT_MLME_MIC_ERR_MUL); +#endif + break; + case MACREG_INT_CODE_MIB_CHANGED: + case MACREG_INT_CODE_INIT_DONE: + break; + + case MACREG_INT_CODE_ADHOC_BCN_LOST: + PRINTM(INFO, "EVENT: ADHOC_BCN_LOST\n"); + Adapter->AdhocLinkSensed = FALSE; + wlan_clean_txrx(priv); + send_iwevcustom_event(priv, CUS_EVT_ADHOC_BCN_LOST); + break; + + case MACREG_INT_CODE_BG_SCAN_REPORT: + PRINTM(INFO, "EVENT: BGS_REPORT\n"); + Adapter->bgScanConfig->Enable = FALSE; + ret = sendBgScanQueryCmd(priv); + break; + case MACREG_INT_CODE_WMM_STATUS_CHANGE: + PRINTM(INFO, "EVENT: WMM status changed\n"); + ret = sendWMMStatusChangeCmd(priv); + break; + + case MACREG_INT_CODE_RSSI_LOW: + PRINTM(INFO, "EVENT: RSSI_LOW\n"); + send_iwevcustom_event(priv, CUS_EVT_BEACON_RSSI_LOW); + break; + case MACREG_INT_CODE_SNR_LOW: + PRINTM(INFO, "EVENT: SNR_LOW\n"); + send_iwevcustom_event(priv, CUS_EVT_BEACON_SNR_LOW); + break; + case MACREG_INT_CODE_MAX_FAIL: + PRINTM(INFO, "EVENT: MAX_FAIL\n"); + send_iwevcustom_event(priv, CUS_EVT_MAX_FAIL); + break; + case MACREG_INT_CODE_RSSI_HIGH: + PRINTM(INFO, "EVENT: RSSI_HIGH\n"); + send_iwevcustom_event(priv, CUS_EVT_BEACON_RSSI_HIGH); + break; + case MACREG_INT_CODE_SNR_HIGH: + PRINTM(INFO, "EVENT: SNR_HIGH\n"); + send_iwevcustom_event(priv, CUS_EVT_BEACON_SNR_HIGH); + break; + case MACREG_INT_CODE_PRE_BEACON_LOST: + PRINTM(INFO, "EVENT: Pre-Beacon Lost\n"); + send_iwevcustom_event(priv, CUS_EVT_PRE_BEACON_LOST); + break; + case MACREG_INT_CODE_IBSS_COALESCED: + PRINTM(INFO, "EVENT: IBSS_COALESCED\n"); + ret = sendADHOCBSSIDQuery(priv); + break; + default: + PRINTM(INFO, "EVENT: unknown event id: %#x\n", eventcause); + break; + } + + Adapter->EventCause = 0; + LEAVE(); + return ret; +} diff --git a/drivers/net/wireless/marvell8686/wlan_debug.c b/drivers/net/wireless/marvell8686/wlan_debug.c new file mode 100644 index 0000000..8a980aa --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_debug.c @@ -0,0 +1,281 @@ +/** @file wlan_debug.c + * @brief This file contains functions for debug proc file. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/04/05: Add Doxygen format comments + 01/05/06: Add kernel 2.6.x support + +********************************************************/ + +#include "include.h" + +#ifdef CONFIG_MARVELL_8686_PROC_FS +/******************************************************** + Local Variables +********************************************************/ + +#define item_size(n) (sizeof ((wlan_adapter *)0)->n) +#define item_addr(n) ((u32) &((wlan_adapter *)0)->n) + +#define item_dbg_size(n) (sizeof (((wlan_adapter *)0)->dbg.n)) +#define item_dbg_addr(n) ((u32) &(((wlan_adapter *)0)->dbg.n)) + +#define item1_size(n) (sizeof ((wlan_dev_t *)0)->n) +#define item1_addr(n) ((u32) &((wlan_dev_t *)0)->n) + +struct debug_data +{ + char name[32]; + u32 size; + u32 addr; + u32 offset; +}; + +/* To debug any member of wlan_adapter or wlan_dev_t, simply add one line here. + */ +#define ITEMS_FROM_WLAN_DEV 1 + +static struct debug_data items[] = { + {"IntCounter", item_size(IntCounter), 0, item_addr(IntCounter)}, + {"ConnectStatus", item_size(MediaConnectStatus), 0, + item_addr(MediaConnectStatus)}, + {"wmmQStp", item_size(wmm.queueStopped), 0, item_addr(wmm.queueStopped)}, + {"wmmPkts", item_size(wmm.packetsQueued), 0, + item_addr(wmm.packetsQueued)}, + {"wmmAcVo", item_size(wmm.packetsOut[WMM_AC_VO]), 0, + item_addr(wmm.packetsOut[WMM_AC_VO])}, + {"wmmAcVi", item_size(wmm.packetsOut[WMM_AC_VI]), 0, + item_addr(wmm.packetsOut[WMM_AC_VI])}, + {"wmmAcBE", item_size(wmm.packetsOut[WMM_AC_BE]), 0, + item_addr(wmm.packetsOut[WMM_AC_BE])}, + {"wmmAcBK", item_size(wmm.packetsOut[WMM_AC_BK]), 0, + item_addr(wmm.packetsOut[WMM_AC_BK])}, + {"PSMode", item_size(PSMode), 0, item_addr(PSMode)}, + {"PSState", item_size(PSState), 0, item_addr(PSState)}, + {"IsDeepSleep", item_size(IsDeepSleep), 0, item_addr(IsDeepSleep)}, + {"IsAutoDeepSleepEnabled", item_size(IsAutoDeepSleepEnabled), 0, + item_addr(IsAutoDeepSleepEnabled)}, + {"WakeupDevReq", item_size(bWakeupDevRequired), 0, + item_addr(bWakeupDevRequired)}, + {"WakeupTries", item_size(WakeupTries), 0, item_addr(WakeupTries)}, + {"HS_Configured", item_size(bHostSleepConfigured), 0, + item_addr(bHostSleepConfigured)}, + {"HS_Activated", item_size(HS_Activated), 0, item_addr(HS_Activated)}, + {"num_tx_timeout", item_dbg_size(num_tx_timeout), 0, + item_dbg_addr(num_tx_timeout)}, + {"num_cmd_timeout", item_dbg_size(num_cmd_timeout), 0, + item_dbg_addr(num_cmd_timeout)}, + {"TimeoutCmdId", item_dbg_size(TimeoutCmdId), 0, + item_dbg_addr(TimeoutCmdId)}, + {"TimeoutCmdAct", item_dbg_size(TimeoutCmdAct), 0, + item_dbg_addr(TimeoutCmdAct)}, + {"LastCmdId", item_dbg_size(LastCmdId), 0, item_dbg_addr(LastCmdId)}, + {"LastCmdAct", item_dbg_size(LastCmdAct), 0, item_dbg_addr(LastCmdAct)}, + {"LastCmdIndex", item_dbg_size(LastCmdIndex), 0, + item_dbg_addr(LastCmdIndex)}, + {"LastCmdRespId", item_dbg_size(LastCmdRespId), 0, + item_dbg_addr(LastCmdRespId)}, + {"LastCmdRespIndex", item_dbg_size(LastCmdRespIndex), 0, + item_dbg_addr(LastCmdRespIndex)}, + {"LastEvent", item_dbg_size(LastEvent), 0, item_dbg_addr(LastEvent)}, + {"LastEventIndex", item_dbg_size(LastEventIndex), 0, + item_dbg_addr(LastEventIndex)}, + {"num_cmd_h2c_fail", item_dbg_size(num_cmd_host_to_card_failure), 0, + item_dbg_addr(num_cmd_host_to_card_failure)}, + {"num_cmd_sleep_cfm_fail", + item_dbg_size(num_cmd_sleep_cfm_host_to_card_failure), 0, + item_dbg_addr(num_cmd_sleep_cfm_host_to_card_failure)}, + {"num_tx_h2c_fail", item_dbg_size(num_tx_host_to_card_failure), 0, + item_dbg_addr(num_tx_host_to_card_failure)}, + {"num_evt_deauth", item_dbg_size(num_event_deauth), 0, + item_dbg_addr(num_event_deauth)}, + {"num_evt_disassoc", item_dbg_size(num_event_disassoc), 0, + item_dbg_addr(num_event_disassoc)}, + {"num_evt_link_lost", item_dbg_size(num_event_link_lost), 0, + item_dbg_addr(num_event_link_lost)}, + {"num_cmd_deauth", item_dbg_size(num_cmd_deauth), 0, + item_dbg_addr(num_cmd_deauth)}, + {"num_cmd_assoc_ok", item_dbg_size(num_cmd_assoc_success), 0, + item_dbg_addr(num_cmd_assoc_success)}, + {"num_cmd_assoc_fail", item_dbg_size(num_cmd_assoc_failure), 0, + item_dbg_addr(num_cmd_assoc_failure)}, + + {"dnld_sent", item1_size(dnld_sent), 0, item1_addr(dnld_sent)}, +}; + +static int num_of_items = sizeof(items) / sizeof(items[0]); + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ +/** + * @brief proc read function + * + * @param page pointer to buffer + * @param s read data starting position + * @param off offset + * @param cnt counter + * @param eof end of file flag + * @param data data to output + * @return number of output data + */ +static int +wlan_debug_read(char *page, char **s, off_t off, int cnt, int *eof, + void *data) +{ + int val = 0; + char *p = page; + int i; + + struct debug_data *d = (struct debug_data *) data; + + MODULE_GET; + + for (i = 0; i < num_of_items; i++) { + if (d[i].size == 1) + val = *((u8 *) d[i].addr); + else if (d[i].size == 2) + val = *((u16 *) d[i].addr); + else if (d[i].size == 4) + val = *((u32 *) d[i].addr); + else { + int j; + p += sprintf(p, "%s=", d[i].name); + for (j = 0; j < d[i].size; j += 2) { + val = *(u16 *) (d[i].addr + j); + p += sprintf(p, "0x%x ", val); + } + p += sprintf(p, "\n"); + continue; + } + + if (strstr(d[i].name, "Id")) + p += sprintf(p, "%s=0x%x\n", d[i].name, val); + else + p += sprintf(p, "%s=%d\n", d[i].name, val); + } + MODULE_PUT; + return p - page; +} + +/** + * @brief proc write function + * + * @param f file pointer + * @param buf pointer to data buffer + * @param cnt data number to write + * @param data data to write + * @return number of data + */ +static int +wlan_debug_write(struct file *f, const char *buf, unsigned long cnt, + void *data) +{ + int r, i; + char *pdata; + char *p; + char *p0; + char *p1; + char *p2; + struct debug_data *d = (struct debug_data *) data; + + MODULE_GET; + + pdata = (char *) kmalloc(cnt, GFP_KERNEL); + if (pdata == NULL) { + MODULE_PUT; + return 0; + } + + if (copy_from_user(pdata, buf, cnt)) { + PRINTM(INFO, "Copy from user failed\n"); + kfree(pdata); + MODULE_PUT; + return 0; + } + + p0 = pdata; + for (i = 0; i < num_of_items; i++) { + do { + p = strstr(p0, d[i].name); + if (p == NULL) + break; + p1 = strchr(p, '\n'); + if (p1 == NULL) + break; + p0 = p1++; + p2 = strchr(p, '='); + if (!p2) + break; + p2++; + r = string_to_number(p2); + if (d[i].size == 1) + *((u8 *) d[i].addr) = (u8) r; + else if (d[i].size == 2) + *((u16 *) d[i].addr) = (u16) r; + else if (d[i].size == 4) + *((u32 *) d[i].addr) = (u32) r; + break; + } while (TRUE); + } + kfree(pdata); + MODULE_PUT; + return cnt; +} + +/******************************************************** + Global Functions +********************************************************/ +/** + * @brief create debug proc file + * + * @param priv pointer wlan_private + * @param dev pointer net_device + * @return N/A + */ +void +wlan_debug_entry(wlan_private * priv, struct net_device *dev) +{ + int i; + struct proc_dir_entry *r; + + if (priv->proc_entry == NULL) + return; + + for (i = 0; i < (num_of_items - ITEMS_FROM_WLAN_DEV); i++) { + items[i].addr = items[i].offset + (u32) priv->adapter; + } + for (i = num_of_items - ITEMS_FROM_WLAN_DEV; i < num_of_items; i++) { + items[i].addr = items[i].offset + (u32) & priv->wlan_dev; + } + r = create_proc_entry("debug", 0644, priv->proc_entry); + if (r == NULL) + return; + + r->data = &items[0]; + r->read_proc = wlan_debug_read; + r->write_proc = wlan_debug_write; + r->owner = THIS_MODULE; + +} + +/** + * @brief remove proc file + * + * @param priv pointer wlan_private + * @return N/A + */ +void +wlan_debug_remove(wlan_private * priv) +{ + remove_proc_entry("debug", priv->proc_entry); +} + +#endif diff --git a/drivers/net/wireless/marvell8686/wlan_decl.h b/drivers/net/wireless/marvell8686/wlan_decl.h new file mode 100644 index 0000000..73aaad4 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_decl.h @@ -0,0 +1,109 @@ +/** @file wlan_decl.h + * @brief This file contains declaration referring to + * functions defined in other source files + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/****************************************************** +Change log: + 09/29/05: add Doxygen format comments + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join structures. + Move wlan_wext statics to their source file. +******************************************************/ + +#ifndef _WLAN_DECL_H_ +#define _WLAN_DECL_H_ + +/** Function Prototype Declaration */ +int wlan_init_fw(wlan_private * priv); +int wlan_tx_packet(wlan_private * priv, struct sk_buff *skb); +void wlan_free_adapter(wlan_private * priv); + +int SendNullPacket(wlan_private * priv, u8 flags); +BOOLEAN CheckLastPacketIndication(wlan_private * priv); + +void Wep_encrypt(wlan_private * priv, u8 * Buf, u32 Len); +int FreeCmdBuffer(wlan_private * priv); +void CleanUpCmdCtrlNode(CmdCtrlNode * pTempNode); +CmdCtrlNode *GetFreeCmdCtrlNode(wlan_private * priv); + +void SetCmdCtrlNode(wlan_private * priv, + CmdCtrlNode * pTempNode, + WLAN_OID cmd_oid, u16 wait_option, void *pdata_buf); + +BOOLEAN Is_Command_Allowed(wlan_private * priv); + +int PrepareAndSendCommand(wlan_private * priv, + u16 cmd_no, + u16 cmd_action, + u16 wait_option, WLAN_OID cmd_oid, void *pdata_buf); + +void QueueCmd(wlan_adapter * Adapter, CmdCtrlNode * CmdNode, BOOLEAN addtail); + +int SetDeepSleep(wlan_private * priv, BOOLEAN bDeepSleep); +int AllocateCmdBuffer(wlan_private * priv); +int ExecuteNextCommand(wlan_private * priv); +int wlan_process_event(wlan_private * priv); +void wlan_interrupt(struct net_device *); +u32 index_to_data_rate(u8 index); +u8 data_rate_to_index(u32 rate); +void HexDump(char *prompt, u8 * data, int len); +void get_version(wlan_adapter * adapter, char *version, int maxlen); +void wlan_read_write_rfreg(wlan_private * priv); + +#ifdef CONFIG_MARVELL_8686_PROC_FS +/** The proc fs interface */ +void wlan_proc_entry(wlan_private * priv, struct net_device *dev); +void wlan_proc_remove(wlan_private * priv); +int string_to_number(char *s); +#ifdef CONFIG_MARVELL_8686_DEBUG +void wlan_debug_entry(wlan_private * priv, struct net_device *dev); +void wlan_debug_remove(wlan_private * priv); +#endif +#endif +int wlan_process_rx_command(wlan_private * priv); +void wlan_process_tx(wlan_private * priv); +void CleanupAndInsertCmd(wlan_private * priv, CmdCtrlNode * pTempCmd); +void MrvDrvCommandTimerFunction(void *FunctionContext); + +#ifdef REASSOCIATION +void MrvDrvReassocTimerFunction(void *FunctionContext); +#endif /* REASSOCIATION */ + +int wlan_set_essid(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra); +int wlan_set_regiontable(wlan_private * priv, u8 region, u8 band); + +void wlan_clean_txrx(wlan_private * priv); + +int wlan_host_sleep_activated_event(wlan_private * priv); +int wlan_host_sleep_deactivated_event(wlan_private * priv); +int wlan_host_sleep_gpio_int_event(wlan_private * priv); +int wlan_deep_sleep_ioctl(wlan_private * priv, struct ifreq *rq); + +int ProcessRxedPacket(wlan_private * priv, struct sk_buff *); + +void PSSleep(wlan_private * priv, int wait_option); +void PSConfirmSleep(wlan_private * priv, u16 PSMode); +void PSWakeup(wlan_private * priv, int wait_option); + +void wlan_send_rxskbQ(wlan_private * priv); + +extern CHANNEL_FREQ_POWER *find_cfp_by_band_and_channel(wlan_adapter * + adapter, u8 band, + u16 channel); +extern CHANNEL_FREQ_POWER *get_cfp_by_band_and_channel(u8 band, u16 channel, + REGION_CHANNEL * + region_channnel); + +extern void MacEventDisconnected(wlan_private * priv); + +#if WIRELESS_EXT > 14 +void send_iwevcustom_event(wlan_private * priv, s8 * str); +#endif + +int fw_read(const char *name, u8 ** addr, u32 * len); +void fw_buffer_free(u8 * addr); + +#endif /* _WLAN_DECL_H_ */ diff --git a/drivers/net/wireless/marvell8686/wlan_defs.h b/drivers/net/wireless/marvell8686/wlan_defs.h new file mode 100644 index 0000000..0f6202b --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_defs.h @@ -0,0 +1,623 @@ +/** @file wlan_defs.h + * @brief This header file contains global constant/enum definitions, + * global variable declaration. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/************************************************************* +Change log: + 10/11/05: add Doxygen format comments + 01/11/06: Add NELEMENTS, BAND_XX defines + 04/10/06: Add hostcmd generic API and power_adapt_cfg_ext command +************************************************************/ + +#ifndef _WLAN_DEFS_H_ +#define _WLAN_DEFS_H_ + +#include "os_defs.h" + +/** Double-Word(32Bit) Bit definition */ +#define DW_BIT_0 0x00000001 +#define DW_BIT_1 0x00000002 +#define DW_BIT_2 0x00000004 +#define DW_BIT_3 0x00000008 +#define DW_BIT_4 0x00000010 +#define DW_BIT_5 0x00000020 +#define DW_BIT_6 0x00000040 +#define DW_BIT_7 0x00000080 +#define DW_BIT_8 0x00000100 +#define DW_BIT_9 0x00000200 +#define DW_BIT_10 0x00000400 +#define DW_BIT_11 0x00000800 +#define DW_BIT_12 0x00001000 +#define DW_BIT_13 0x00002000 +#define DW_BIT_14 0x00004000 +#define DW_BIT_15 0x00008000 +#define DW_BIT_16 0x00010000 +#define DW_BIT_17 0x00020000 +#define DW_BIT_18 0x00040000 +#define DW_BIT_19 0x00080000 +#define DW_BIT_20 0x00100000 +#define DW_BIT_21 0x00200000 +#define DW_BIT_22 0x00400000 +#define DW_BIT_23 0x00800000 +#define DW_BIT_24 0x01000000 +#define DW_BIT_25 0x02000000 +#define DW_BIT_26 0x04000000 +#define DW_BIT_27 0x08000000 +#define DW_BIT_28 0x10000000 +#define DW_BIT_29 0x20000000 +#define DW_BIT_30 0x40000000 +#define DW_BIT_31 0x80000000 + +/** Word (16bit) Bit Definition*/ +#define W_BIT_0 0x0001 +#define W_BIT_1 0x0002 +#define W_BIT_2 0x0004 +#define W_BIT_3 0x0008 +#define W_BIT_4 0x0010 +#define W_BIT_5 0x0020 +#define W_BIT_6 0x0040 +#define W_BIT_7 0x0080 +#define W_BIT_8 0x0100 +#define W_BIT_9 0x0200 +#define W_BIT_10 0x0400 +#define W_BIT_11 0x0800 +#define W_BIT_12 0x1000 +#define W_BIT_13 0x2000 +#define W_BIT_14 0x4000 +#define W_BIT_15 0x8000 + +/** Byte (8Bit) Bit definition*/ +#define B_BIT_0 0x01 +#define B_BIT_1 0x02 +#define B_BIT_2 0x04 +#define B_BIT_3 0x08 +#define B_BIT_4 0x10 +#define B_BIT_5 0x20 +#define B_BIT_6 0x40 +#define B_BIT_7 0x80 + +/** Debug Macro definition*/ +#ifdef DEBUG_LEVEL1 + +extern u32 drvdbg; +extern u32 ifdbg; + +/* Debug message control bit definition for drvdbg */ +#define DBG_MSG DW_BIT_0 +#define DBG_FATAL DW_BIT_1 +#define DBG_ERROR DW_BIT_2 +#define DBG_DATA DW_BIT_3 +#define DBG_CMND DW_BIT_4 +#define DBG_EVENT DW_BIT_5 +#define DBG_INTR DW_BIT_6 + +#define DBG_DAT_D DW_BIT_16 +#define DBG_CMD_D DW_BIT_17 +#define DBG_FW_D DW_BIT_18 + +#define DBG_ENTRY DW_BIT_28 +#define DBG_WARN DW_BIT_29 +#define DBG_INFO DW_BIT_30 + +/* Debug message control bit definition for ifdbg */ +#define DBG_IF_D DW_BIT_0 + +#ifdef DEBUG_LEVEL2 +#define PRINTM_INFO(msg...) {if (drvdbg & DBG_INFO) printk(KERN_DEBUG msg);} +#define PRINTM_WARN(msg...) {if (drvdbg & DBG_WARN) printk(KERN_DEBUG msg);} +#define PRINTM_ENTRY(msg...) {if (drvdbg & DBG_ENTRY) printk(KERN_DEBUG msg);} +#else +#define PRINTM_INFO(msg...) do {} while (0) +#define PRINTM_WARN(msg...) do {} while (0) +#define PRINTM_ENTRY(msg...) do {} while (0) +#endif /* DEBUG_LEVEL2 */ + +#define PRINTM_FW_D(msg...) {if (drvdbg & DBG_FW_D) printk(KERN_DEBUG msg);} +#define PRINTM_CMD_D(msg...) {if (drvdbg & DBG_CMD_D) printk(KERN_DEBUG msg);} +#define PRINTM_DAT_D(msg...) {if (drvdbg & DBG_DAT_D) printk(KERN_DEBUG msg);} + +#define PRINTM_INTR(msg...) {if (drvdbg & DBG_INTR) printk(KERN_DEBUG msg);} +#define PRINTM_EVENT(msg...) {if (drvdbg & DBG_EVENT) printk(msg);} +#define PRINTM_CMND(msg...) {if (drvdbg & DBG_CMND) printk(KERN_DEBUG msg);} +#define PRINTM_DATA(msg...) {if (drvdbg & DBG_DATA) printk(KERN_DEBUG msg);} +#define PRINTM_ERROR(msg...) {if (drvdbg & DBG_ERROR) printk(KERN_DEBUG msg);} +#define PRINTM_FATAL(msg...) {if (drvdbg & DBG_FATAL) printk(KERN_DEBUG msg);} +#define PRINTM_MSG(msg...) {if (drvdbg & DBG_MSG) printk(KERN_ALERT msg);} + +#define PRINTM_IF_D(msg...) {if (ifdbg & DBG_IF_D) printk(KERN_DEBUG msg);} + +#define PRINTM(level,msg...) PRINTM_##level(msg) + +#else + +#define PRINTM(level,msg...) do {} while (0) + +#endif /* DEBUG_LEVEL1 */ + +#define ASSERT(cond) \ +do { \ + if (!(cond)) \ + PRINTM(INFO, "ASSERT: %s, %s:%i\n", \ + __FUNCTION__, __FILE__, __LINE__); \ +} while(0) + +#define ENTER() PRINTM(ENTRY, "Enter: %s, %s:%i\n", __FUNCTION__, \ + __FILE__, __LINE__) +#define LEAVE() PRINTM(ENTRY, "Leave: %s, %s:%i\n", __FUNCTION__, \ + __FILE__, __LINE__) + +#if defined(DEBUG_LEVEL1) && defined(__KERNEL__) +#define DBG_DUMP_BUF_LEN 64 +#define MAX_DUMP_PER_LINE 16 +#define MAX_DATA_DUMP_LEN 48 + +static inline void +hexdump(char *prompt, u8 * buf, int len) +{ + int i; + char dbgdumpbuf[DBG_DUMP_BUF_LEN]; + char *ptr = dbgdumpbuf; + + printk(KERN_DEBUG "%s:\n", prompt); + for (i = 1; i <= len; i++) { + ptr += sprintf(ptr, "%02x ", *buf); + buf++; + if (i % MAX_DUMP_PER_LINE == 0) { + *ptr = 0; + printk(KERN_DEBUG "%s\n", dbgdumpbuf); + ptr = dbgdumpbuf; + } + } + if (len % MAX_DUMP_PER_LINE) { + *ptr = 0; + printk(KERN_DEBUG "%s\n", dbgdumpbuf); + } +} + +#define DBG_HEXDUMP_CMD_D(x,y,z) {if (drvdbg & DBG_CMD_D) hexdump(x,y,z);} +#define DBG_HEXDUMP_DAT_D(x,y,z) {if (drvdbg & DBG_DAT_D) hexdump(x,y,z);} +#define DBG_HEXDUMP_IF_D(x,y,z) {if (ifdbg & DBG_IF_D) hexdump(x,y,z);} +#define DBG_HEXDUMP_FW_D(x,y,z) {if (drvdbg & DBG_FW_D) hexdump(x,y,z);} + +#define DBG_HEXDUMP(level,x,y,z) DBG_HEXDUMP_##level(x,y,z) + +#else +#define DBG_HEXDUMP(level,x,y,z) do {} while (0) +#endif + +#if defined(DEBUG_LEVEL2) && defined(__KERNEL__) +#define HEXDUMP(x,y,z) {if (drvdbg & DBG_INFO) hexdump(x,y,z);} +#else +#define HEXDUMP(x,y,z) do {} while (0) +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef NELEMENTS +#define NELEMENTS(x) (sizeof(x)/sizeof(x[0])) +#endif + +/** Buffer Constants */ + +/* The size of SQ memory PPA, DPA are 8 DWORDs, that keep the physical +* addresses of TxPD buffers. Station has only 8 TxPD available, Whereas +* driver has more local TxPDs. Each TxPD on the host memory is associated +* with a Tx control node. The driver maintains 8 RxPD descriptors for +* station firmware to store Rx packet information. +* +* Current version of MAC has a 32x6 multicast address buffer. +* +* 802.11b can have up to 14 channels, the driver keeps the +* BSSID(MAC address) of each APs or Ad hoc stations it has sensed. +*/ + +#define MRVDRV_SIZE_OF_PPA 0x00000008 +#define MRVDRV_SIZE_OF_DPA 0x00000008 +#define MRVDRV_NUM_OF_TxPD 0x00000020 +#define MRVDRV_NUM_OF_CMD_BUFFER 10 +#define MRVDRV_SIZE_OF_CMD_BUFFER (2 * 1024) +#define MRVDRV_MAX_BSSID_LIST 64 +#define MRVDRV_TIMER_10S 10000 +#define MRVDRV_TIMER_5S 5000 +#define MRVDRV_TIMER_1S 1000 +#define MRVDRV_SNAP_HEADER_LEN 8 +#define MRVDRV_ETH_HEADER_SIZE 14 + +#define ARP_FILTER_MAX_BUF_SIZE 68 + +#define WLAN_UPLD_SIZE 2312 +#define DEV_NAME_LEN 32 + +#ifndef ETH_ALEN +#define ETH_ALEN 6 +#endif + +/** Misc constants */ +/* This section defines 802.11 specific contants */ +#define SDIO_HEADER_LEN 4 + +#define MRVDRV_MAX_REGION_CODE 6 +#define MRVDRV_IGNORE_MULTIPLE_DTIM 0xfffe +#define MRVDRV_MIN_MULTIPLE_DTIM 1 +#define MRVDRV_MAX_MULTIPLE_DTIM 5 +#define MRVDRV_DEFAULT_MULTIPLE_DTIM 1 + +#define MRVDRV_DEFAULT_LISTEN_INTERVAL 10 +#define MRVDRV_DEFAULT_LOCAL_LISTEN_INTERVAL 0 + +#define MRVDRV_CHANNELS_PER_ACTIVE_SCAN 14 +#define MRVDRV_MIN_BEACON_INTERVAL 20 +#define MRVDRV_MAX_BEACON_INTERVAL 1000 +#define MRVDRV_BEACON_INTERVAL 100 + +#define MRVDRV_DEFAULT_WATCHDOG_TIMEOUT (5 * HZ) +#define MRVDRV_SCAN_WATCHDOG_TIMEOUT (10 * HZ) +#define MRVDRV_DEEP_SLEEP_EXIT_TIMEOUT (10 * HZ) + +/** TxPD Status */ + +/* Station firmware use TxPD status field to report final Tx transmit +* result, Bit masks are used to present combined situations. +*/ + +#define MRVDRV_TxPD_POWER_MGMT_NULL_PACKET 0x01 +#define MRVDRV_TxPD_POWER_MGMT_LAST_PACKET 0x08 + +/** Tx control node status */ + +#define MRVDRV_TX_CTRL_NODE_STATUS_IDLE 0x0000 + +/* Link spped */ +#define MRVDRV_LINK_SPEED_1mbps 10000 /* in unit of 100bps */ +#define MRVDRV_LINK_SPEED_11mbps 110000 + +/** RSSI-related defines */ +/* RSSI constants are used to implement 802.11 RSSI threshold +* indication. if the Rx packet signal got too weak for 5 consecutive +* times, miniport driver (driver) will report this event to wrapper +*/ + +#define MRVDRV_NF_DEFAULT_SCAN_VALUE (-96) + +/** RTS/FRAG related defines */ +#define MRVDRV_RTS_MIN_VALUE 0 +#define MRVDRV_RTS_MAX_VALUE 2347 +#define MRVDRV_FRAG_MIN_VALUE 256 +#define MRVDRV_FRAG_MAX_VALUE 2346 + +/* Fixed IE size is 8 bytes time stamp + 2 bytes beacon interval + + * 2 bytes cap */ +#define MRVL_FIXED_IE_SIZE 12 + +/* This is for firmware specific length */ +#define EXTRA_LEN 36 +#define MRVDRV_MAXIMUM_ETH_PACKET_SIZE 1514 + +#define MRVDRV_ETH_TX_PACKET_BUFFER_SIZE \ + (MRVDRV_MAXIMUM_ETH_PACKET_SIZE + sizeof(TxPD) + EXTRA_LEN) + +#define MRVDRV_ETH_RX_PACKET_BUFFER_SIZE \ + (MRVDRV_MAXIMUM_ETH_PACKET_SIZE + sizeof(RxPD) \ + + MRVDRV_SNAP_HEADER_LEN + EXTRA_LEN) + +#define CMD_F_HOSTCMD (1 << 0) + +/* to resolve CISCO AP extension */ +#define MRVDRV_SCAN_LIST_VAR_IE_SPACE 256 +#define FW_IS_WPA_ENABLED(_adapter) \ + (_adapter->fwCapInfo & FW_CAPINFO_WPA) + +#define FW_CAPINFO_WPA (1 << 0) +#define WLAN_802_11_AI_REQFI_CAPABILITIES 1 +#define WLAN_802_11_AI_REQFI_LISTENINTERVAL 2 +#define WLAN_802_11_AI_REQFI_CURRENTAPADDRESS 4 + +#define WLAN_802_11_AI_RESFI_CAPABILITIES 1 +#define WLAN_802_11_AI_RESFI_STATUSCODE 2 +#define WLAN_802_11_AI_RESFI_ASSOCIATIONID 4 + +#define MRVL_NUM_WEP_KEY 4 + +/** WPA Key LENGTH*/ +/* Support 4 keys per key set */ +#define MRVL_NUM_WPA_KEY_PER_SET 4 +#define MRVL_MAX_WPA_KEY_LENGTH 32 + +#define WPA_AES_KEY_LEN 16 +#define WPA_TKIP_KEY_LEN 32 + +/* A few details needed for WEP (Wireless Equivalent Privacy) */ +/* 104 bits */ +#define MAX_WEP_KEY_SIZE 13 +/*40 bits RC4 - WEP*/ +#define MIN_WEP_KEY_SIZE 5 + +#define RF_ANTENNA_1 0x1 +#define RF_ANTENNA_2 0x2 +#define RF_ANTENNA_AUTO 0xFFFF + +#define KEY_INFO_ENABLED 0x01 + +#define SNR_BEACON 0 +#define SNR_RXPD 1 +#define NF_BEACON 2 +#define NF_RXPD 3 + +/** MACRO DEFINITIONS */ +#define CAL_NF(NF) ((s32)(-(s32)(NF))) +#define CAL_RSSI(SNR, NF) ((s32)((s32)(SNR) + CAL_NF(NF))) +#define SCAN_RSSI(RSSI) (0x100 - ((u8)(RSSI))) + +#define DEFAULT_BCN_AVG_FACTOR 8 +#define DEFAULT_DATA_AVG_FACTOR 8 +#define MIN_BCN_AVG_FACTOR 1 +#define MAX_BCN_AVG_FACTOR 8 +#define MIN_DATA_AVG_FACTOR 1 +#define MAX_DATA_AVG_FACTOR 8 +#define AVG_SCALE 100 +#define CAL_AVG_SNR_NF(AVG, SNRNF, N) \ + (((AVG) == 0) ? ((u16)(SNRNF) * AVG_SCALE) : \ + ((((int)(AVG) * (N -1)) + ((u16)(SNRNF) * \ + AVG_SCALE)) / N)) + +#define WLAN_STATUS_SUCCESS (0) +#define WLAN_STATUS_FAILURE (-1) +#define WLAN_STATUS_NOT_ACCEPTED (-2) + +#define MAX_LEDS 3 +#define LED_DISABLED 16 +#define LED_BLINKING 2 + +/* S_SWAP : To swap 2 u8 */ +#define S_SWAP(a,b) do { \ + u8 t = SArr[a]; \ + SArr[a] = SArr[b]; SArr[b] = t; \ + } while(0) + +/* SWAP: swap u8 */ +#define SWAP_U8(a,b) {u8 t; t=a; a=b; b=t;} + +/* SWAP: swap u8 */ +#define SWAP_U16(a,b) {u16 t; t=a; a=b; b=t;} + +#define wlan_le16_to_cpu(x) x +#define wlan_le32_to_cpu(x) x +#define wlan_le64_to_cpu(x) x +#define wlan_cpu_to_le16(x) x +#define wlan_cpu_to_le32(x) x +#define wlan_cpu_to_le64(x) x + +#define endian_convert_TxPD(x) +#define endian_convert_RxPD(x) +#define endian_convert_GET_LOG(x) + +/** Global Varibale Declaration */ +typedef struct _wlan_private wlan_private; +typedef struct _wlan_adapter wlan_adapter; +typedef struct _HostCmd_DS_COMMAND HostCmd_DS_COMMAND; + +extern u32 DSFreqList[15]; +extern const char driver_version[]; +extern u32 DSFreqList[]; +extern u16 RegionCodeToIndex[MRVDRV_MAX_REGION_CODE]; + +extern u8 WlanDataRates[WLAN_SUPPORTED_RATES]; + +extern u8 SupportedRates[G_SUPPORTED_RATES]; + +extern u8 AdhocRates_G[G_SUPPORTED_RATES]; + +extern u8 AdhocRates_B[4]; +extern wlan_private *wlanpriv; + +#ifdef MFG_CMD_SUPPORT +#define SIOCCFMFG SIOCDEVPRIVATE +#endif /* MFG_CMD_SUPPORT */ + +#define INTMODE_SDIO 0 +#define INTMODE_GPIO 1 +extern int intmode; +extern int gpiopin; + +/** ENUM definition*/ +/** SNRNF_TYPE */ +typedef enum _SNRNF_TYPE +{ + TYPE_BEACON = 0, + TYPE_RXPD, + MAX_TYPE_B +} SNRNF_TYPE; + +/** SNRNF_DATA*/ +typedef enum _SNRNF_DATA +{ + TYPE_NOAVG = 0, + TYPE_AVG, + MAX_TYPE_AVG +} SNRNF_DATA; + +/** WLAN_802_11_AUTH_ALG*/ +typedef enum _WLAN_802_11_AUTH_ALG +{ + AUTH_ALG_OPEN_SYSTEM = 1, + AUTH_ALG_SHARED_KEY = 2, + AUTH_ALG_NETWORK_EAP = 8, +} WLAN_802_11_AUTH_ALG; + +/** WLAN_802_11_ENCRYPTION_MODE */ +typedef enum _WLAN_802_11_ENCRYPTION_MODE +{ + CIPHER_NONE, + CIPHER_WEP40, + CIPHER_TKIP, + CIPHER_CCMP, + CIPHER_WEP104, +} WLAN_802_11_ENCRYPTION_MODE; + +/** WLAN_802_11_POWER_MODE */ +typedef enum _WLAN_802_11_POWER_MODE +{ + Wlan802_11PowerModeCAM, + Wlan802_11PowerModeMAX_PSP, + Wlan802_11PowerModeFast_PSP, + + /*not a real mode, defined as an upper bound */ + Wlan802_11PowerModeMax +} WLAN_802_11_POWER_MODE; + +/** PS_STATE */ +typedef enum _PS_STATE +{ + PS_STATE_FULL_POWER, + PS_STATE_AWAKE, + PS_STATE_PRE_SLEEP, + PS_STATE_SLEEP +} PS_STATE; + +/** DNLD_STATE */ +typedef enum _DNLD_STATE +{ + DNLD_RES_RECEIVED, + DNLD_DATA_SENT, + DNLD_CMD_SENT +} DNLD_STATE; + +/** WLAN_MEDIA_STATE */ +typedef enum _WLAN_MEDIA_STATE +{ + WlanMediaStateDisconnected, + WlanMediaStateConnected +} WLAN_MEDIA_STATE; + +/** WLAN_802_11_PRIVACY_FILTER */ +typedef enum _WLAN_802_11_PRIVACY_FILTER +{ + Wlan802_11PrivFilterAcceptAll, + Wlan802_11PrivFilter8021xWEP +} WLAN_802_11_PRIVACY_FILTER; + +/** mv_ms_type */ +typedef enum _mv_ms_type +{ + MVMS_DAT = 0, + MVMS_CMD = 1, + /* 2: reserved */ + MVMS_EVENT = 3 +} mv_ms_type; + +/* Hardware status codes */ +typedef enum _WLAN_HARDWARE_STATUS +{ + WlanHardwareStatusReady, + WlanHardwareStatusInitializing, + WlanHardwareStatusReset, + WlanHardwareStatusClosing, + WlanHardwareStatusNotReady +} WLAN_HARDWARE_STATUS; + +/** WLAN_802_11_AUTHENTICATION_MODE */ +typedef enum _WLAN_802_11_AUTHENTICATION_MODE +{ + Wlan802_11AuthModeOpen = 0x00, + Wlan802_11AuthModeShared = 0x01, + Wlan802_11AuthModeNetworkEAP = 0x80, +} WLAN_802_11_AUTHENTICATION_MODE; + +/** WLAN_802_11_WEP_STATUS */ +typedef enum _WLAN_802_11_WEP_STATUS +{ + Wlan802_11WEPEnabled, + Wlan802_11WEPDisabled, + Wlan802_11WEPKeyAbsent, + Wlan802_11WEPNotSupported +} WLAN_802_11_WEP_STATUS; + +/** SNMP_MIB_INDEX_e */ +typedef enum _SNMP_MIB_INDEX_e +{ + DesiredBssType_i = 0, + OpRateSet_i, + BcnPeriod_i, + DtimPeriod_i, + AssocRspTimeOut_i, + RtsThresh_i, + ShortRetryLim_i, + LongRetryLim_i, + FragThresh_i, + Dot11D_i, + Dot11H_i, + ManufId_i, + ProdId_i, + ManufOui_i, + ManufName_i, + ManufProdName_i, + ManufProdVer_i +} SNMP_MIB_INDEX_e; + +/** KEY_TYPE_ID */ +typedef enum _KEY_TYPE_ID +{ + KEY_TYPE_ID_WEP = 0, + KEY_TYPE_ID_TKIP, + KEY_TYPE_ID_AES +} KEY_TYPE_ID; + +/** KEY_INFO_WEP*/ +typedef enum _KEY_INFO_WEP +{ + KEY_INFO_WEP_DEFAULT_KEY = 0x01 +} KEY_INFO_WEP; + +/** KEY_INFO_TKIP */ +typedef enum _KEY_INFO_TKIP +{ + KEY_INFO_TKIP_MCAST = 0x01, + KEY_INFO_TKIP_UNICAST = 0x02, + KEY_INFO_TKIP_ENABLED = 0x04 +} KEY_INFO_TKIP; + +/** KEY_INFO_AES*/ +typedef enum _KEY_INFO_AES +{ + KEY_INFO_AES_MCAST = 0x01, + KEY_INFO_AES_UNICAST = 0x02, + KEY_INFO_AES_ENABLED = 0x04 +} KEY_INFO_AES; + +/** SNMP_MIB_VALUE_e */ +typedef enum _SNMP_MIB_VALUE_e +{ + SNMP_MIB_VALUE_INFRA = 1, + SNMP_MIB_VALUE_ADHOC +} SNMP_MIB_VALUE_e; + +/** HWRateDropMode */ +typedef enum _HWRateDropMode +{ + NO_HW_RATE_DROP, + HW_TABLE_RATE_DROP, + HW_SINGLE_RATE_DROP +} HWRateDropMode; + +#ifdef __KERNEL__ +extern struct iw_handler_def wlan_handler_def; +struct iw_statistics *wlan_get_wireless_stats(struct net_device *dev); +int wlan_do_ioctl(struct net_device *dev, struct ifreq *req, int i); +#endif + +#endif /* _WLAN_DEFS_H_ */ diff --git a/drivers/net/wireless/marvell8686/wlan_dev.h b/drivers/net/wireless/marvell8686/wlan_dev.h new file mode 100644 index 0000000..c818b0c --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_dev.h @@ -0,0 +1,468 @@ +/** @file wlan_dev.h + * @brief This file contains definitions and data structures specific + * to Marvell 802.11 NIC. It contains the Device Information + * structure wlan_adapter. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/************************************************************* +Change log: + 09/26/05: add Doxygen format comments + 01/11/06: Conditionalize new scan/join structures. + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/08/06: Remove PermanentAddr from Adapter + + ************************************************************/ + +#ifndef _WLAN_DEV_H_ +#define _WLAN_DEV_H_ + +#define MAX_BSSID_PER_CHANNEL 16 + +/* For the extended Scan */ +#define MAX_EXTENDED_SCAN_BSSID_LIST MAX_BSSID_PER_CHANNEL * \ + MRVDRV_MAX_CHANNEL_SIZE + 1 + +typedef struct _PER_CHANNEL_BSSID_LIST_DATA +{ + u8 ucStart; + u8 ucNumEntry; +} PER_CHANNEL_BSSID_LIST_DATA, *PPER_CHANNEL_BSSID_LIST_DATA; + +typedef struct _MRV_BSSID_IE_LIST +{ + WLAN_802_11_FIXED_IEs FixedIE; + u8 VariableIE[MRVDRV_SCAN_LIST_VAR_IE_SPACE]; +} MRV_BSSID_IE_LIST, *PMRV_BSSID_IE_LIST; + +#define MAX_REGION_CHANNEL_NUM 2 + +/** Chan-Freq-TxPower mapping table*/ +typedef struct _CHANNEL_FREQ_POWER +{ + /** Channel Number */ + u16 Channel; + /** Frequency of this Channel */ + u32 Freq; + /** Max allowed Tx power level */ + u16 MaxTxPower; + /** TRUE:channel unsupported; FLASE:supported*/ + BOOLEAN Unsupported; +} CHANNEL_FREQ_POWER; + +/** region-band mapping table*/ +typedef struct _REGION_CHANNEL +{ + /** TRUE if this entry is valid */ + BOOLEAN Valid; + /** Region code for US, Japan ... */ + u8 Region; + /** Band B/G/A, used for BAND_CONFIG cmd */ + u8 Band; + /** Actual No. of elements in the array below */ + u8 NrCFP; + /** chan-freq-txpower mapping table*/ + CHANNEL_FREQ_POWER *CFP; +} REGION_CHANNEL; + +typedef struct _wlan_802_11_security_t +{ + BOOLEAN WPAEnabled; + BOOLEAN WPA2Enabled; + WLAN_802_11_WEP_STATUS WEPStatus; + WLAN_802_11_AUTHENTICATION_MODE AuthenticationMode; + WLAN_802_11_ENCRYPTION_MODE EncryptionMode; +} wlan_802_11_security_t; + +/** Current Basic Service Set State Structure */ +typedef struct +{ + BSSDescriptor_t BSSDescriptor; + + /** band */ + u8 band; + + /** number of rates supported */ + int NumOfRates; + + /** supported rates*/ + u8 DataRates[WLAN_SUPPORTED_RATES]; + + /** wmm enable? */ + u8 wmm_enabled; + + /** uapsd enable?*/ + u8 wmm_uapsd_enabled; +} CurrentBSSParams_t; + +/** sleep_params */ +typedef struct SleepParams +{ + u16 sp_error; + u16 sp_offset; + u16 sp_stabletime; + u8 sp_calcontrol; + u8 sp_extsleepclk; + u16 sp_reserved; +} SleepParams; + +/** sleep_period */ +typedef struct SleepPeriod +{ + u16 period; + u16 reserved; +} SleepPeriod; + +#define DBG_CMD_NUM 5 + +/** info for debug purpose */ +typedef struct _wlan_dbg +{ + u32 num_cmd_host_to_card_failure; + u32 num_cmd_sleep_cfm_host_to_card_failure; + u32 num_tx_host_to_card_failure; + u32 num_event_deauth; + u32 num_event_disassoc; + u32 num_event_link_lost; + u32 num_cmd_deauth; + u32 num_cmd_assoc_success; + u32 num_cmd_assoc_failure; + u32 num_tx_timeout; + u32 num_cmd_timeout; + u16 TimeoutCmdId; + u16 TimeoutCmdAct; + u16 LastCmdId[DBG_CMD_NUM]; + u16 LastCmdAct[DBG_CMD_NUM]; + u16 LastCmdIndex; + u16 LastCmdRespId[DBG_CMD_NUM]; + u16 LastCmdRespIndex; + u16 LastEvent[DBG_CMD_NUM]; + u16 LastEventIndex; +} wlan_dbg; + +/** Data structure for the Marvell WLAN device */ +typedef struct _wlan_dev +{ + /** device name */ + char name[DEV_NAME_LEN]; + /** card pointer */ + void *card; + /** IO port */ + u32 ioport; + /** Upload received */ + u32 upld_rcv; + /** Upload type */ + u32 upld_typ; + /** Upload length */ + u32 upld_len; + /** netdev pointer */ + struct net_device *netdev; + /* Upload buffer */ + u8 upld_buf[WLAN_UPLD_SIZE]; + /* Download sent: + bit0 1/0=data_sent/data_tx_done, + bit1 1/0=cmd_sent/cmd_tx_done, + all other bits reserved 0 */ + u8 dnld_sent; +} wlan_dev_t, *pwlan_dev_t; + +/* Data structure for WPS information */ +typedef struct +{ + IEEEtypes_VendorSpecific_t wpsIe; + BOOLEAN SessionEnable; +} wps_t; + +/** Private structure for the MV device */ +struct _wlan_private +{ + int open; + + wlan_adapter *adapter; + wlan_dev_t wlan_dev; + + struct net_device_stats stats; + + struct iw_statistics wstats; +#ifdef CONFIG_MARVELL_8686_PROC_FS + struct proc_dir_entry *proc_entry; + struct proc_dir_entry *proc_dev; +#endif + + /** thread to service interrupts */ + wlan_thread MainThread; + +#ifdef REASSOCIATION + /** thread to service mac events */ + wlan_thread ReassocThread; +#endif /* REASSOCIATION */ +}; + +/** Wlan Adapter data structure*/ +struct _wlan_adapter +{ + u8 TmpTxBuf[WLAN_UPLD_SIZE] __ATTRIB_ALIGN__; + /** STATUS variables */ + WLAN_HARDWARE_STATUS HardwareStatus; + u32 FWReleaseNumber; + u32 fwCapInfo; + u8 chip_rev; + + /** Command-related variables */ + u16 SeqNum; + CmdCtrlNode *CmdArray; + /** Current Command */ + CmdCtrlNode *CurCmd; + int CurCmdRetCode; + + /** Command Queues */ + /** Free command buffers */ + struct list_head CmdFreeQ; + /** Pending command buffers */ + struct list_head CmdPendingQ; + + /** Variables brought in from private structure */ + int irq; + + /** Async and Sync Event variables */ + u32 IntCounter; + u32 IntCounterSaved; /* save int for DS/PS */ + u32 EventCause; + u8 nodeName[16]; /* nickname */ + + /** spin locks */ + spinlock_t QueueSpinLock __ATTRIB_ALIGN__; + + /** Timers */ + WLAN_DRV_TIMER MrvDrvCommandTimer __ATTRIB_ALIGN__; + BOOLEAN CommandTimerIsSet; + +#ifdef REASSOCIATION + /**Reassociation timer*/ + BOOLEAN ReassocTimerIsSet; + WLAN_DRV_TIMER MrvDrvTimer __ATTRIB_ALIGN__; +#endif /* REASSOCIATION */ + + /** Event Queues */ + wait_queue_head_t ds_awake_q __ATTRIB_ALIGN__; + + u8 HisRegCpy; + + /** bg scan related variable */ + HostCmd_DS_802_11_BG_SCAN_CONFIG *bgScanConfig; + u32 bgScanConfigSize; + + /** WMM related variable*/ + WMM_DESC wmm; + + /** current ssid/bssid related parameters*/ + CurrentBSSParams_t CurBssParams; + + WLAN_802_11_NETWORK_INFRASTRUCTURE InfrastructureMode; + + BSSDescriptor_t *pAttemptedBSSDesc; + + WLAN_802_11_SSID AttemptedSSIDBeforeScan; + WLAN_802_11_SSID PreviousSSID; + u8 PreviousBSSID[MRVDRV_ETH_ADDR_LEN]; + + BSSDescriptor_t *ScanTable; + u32 NumInScanTable; + + u8 ScanType; + u32 ScanMode; + u16 SpecificScanTime; + u16 ActiveScanTime; + u16 PassiveScanTime; + + u16 BeaconPeriod; + u8 AdhocCreate; + BOOLEAN AdhocLinkSensed; + +#ifdef REASSOCIATION + /** Reassociation on and off */ + BOOLEAN Reassoc_on; + SEMAPHORE ReassocSem; +#endif /* REASSOCIATION */ + + BOOLEAN ATIMEnabled; + + /** MAC address information */ + u8 CurrentAddr[MRVDRV_ETH_ADDR_LEN]; + u8 MulticastList[MRVDRV_MAX_MULTICAST_LIST_SIZE] + [MRVDRV_ETH_ADDR_LEN]; + u32 NumOfMulticastMACAddr; + + u16 HWRateDropMode; + u16 RateBitmap; + u16 Threshold; + u16 FinalRate; + /** control G Rates */ + BOOLEAN adhoc_grate_enabled; + + WLAN_802_11_ANTENNA TxAntenna; + WLAN_802_11_ANTENNA RxAntenna; + + u8 AdhocChannel; + WLAN_802_11_FRAGMENTATION_THRESHOLD FragThsd; + WLAN_802_11_RTS_THRESHOLD RTSThsd; + + u32 DataRate; + BOOLEAN Is_DataRate_Auto; + + /** number of association attempts for the current SSID cmd */ + u16 ListenInterval; + u16 TxRetryCount; + + u16 Dtim; + + /** Tx-related variables (for single packet tx) */ + struct sk_buff *CurrentTxSkb; + struct sk_buff RxSkbQ; + BOOLEAN TxLockFlag; + u16 gen_null_pkg; + spinlock_t CurrentTxLock __ATTRIB_ALIGN__; + + /** NIC Operation characteristics */ + u16 CurrentPacketFilter; + u32 MediaConnectStatus; + u16 RegionCode; + u16 TxPowerLevel; + u8 MaxTxPowerLevel; + u8 MinTxPowerLevel; + + /** POWER MANAGEMENT AND PnP SUPPORT */ + BOOLEAN SurpriseRemoved; + u16 AtimWindow; + + u16 PSMode; /* Wlan802_11PowerModeCAM=disable + Wlan802_11PowerModeMAX_PSP=enable */ + u16 MultipleDtim; + u16 BCNMissTimeOut; + u32 PSState; + BOOLEAN NeedToWakeup; + + PS_CMD_ConfirmSleep PSConfirmSleep; + u16 LocalListenInterval; + u16 NullPktInterval; + u16 AdhocAwakePeriod; + u16 fwWakeupMethod; + BOOLEAN IsDeepSleep; + BOOLEAN IsAutoDeepSleepEnabled; + BOOLEAN bWakeupDevRequired; + u32 WakeupTries; + BOOLEAN bHostSleepConfigured; + HostCmd_DS_802_11_HOST_SLEEP_CFG HSCfg; + /** ARP filter related variable */ + u8 ArpFilter[ARP_FILTER_MAX_BUF_SIZE]; + u32 ArpFilterSize; + BOOLEAN HS_Activated; + + /** Encryption parameter */ + wlan_802_11_security_t SecInfo; + + MRVL_WEP_KEY WepKey[MRVL_NUM_WEP_KEY]; + u16 CurrentWepKeyIndex; + + /** Buffer for TLVs passed from the application to be inserted into the + * association request to firmware + */ + u8 mrvlAssocTlvBuffer[MRVDRV_ASSOC_TLV_BUF_SIZE]; + + /** Length of the data stored in mrvlAssocTlvBuffer*/ + u8 mrvlAssocTlvBufferLen; + + /** Buffer to store the association response for application retrieval */ + u8 assocRspBuffer[MRVDRV_ASSOC_RSP_BUF_SIZE]; + + /** Length of the data stored in assocRspBuffer */ + int assocRspSize; + + /** Generice IEEE IEs passed from the application to be inserted into the + * association request to firmware + */ + u8 genIeBuffer[MRVDRV_GENIE_BUF_SIZE]; + + /** Length of the data stored in genIeBuffer */ + u8 genIeBufferLen; + + BOOLEAN IsGTK_SET; + + /** Encryption Key*/ + u8 Wpa_ie[256]; + u8 Wpa_ie_len; + + HostCmd_DS_802_11_KEY_MATERIAL aeskey; + + /* Advanced Encryption Standard */ + BOOLEAN AdhocAESEnabled; + wait_queue_head_t cmd_EncKey __ATTRIB_ALIGN__; + + u16 RxAntennaMode; + u16 TxAntennaMode; + + /** Requested Signal Strength*/ + u16 bcn_avg_factor; + u16 data_avg_factor; + u16 SNR[MAX_TYPE_B][MAX_TYPE_AVG]; + u16 NF[MAX_TYPE_B][MAX_TYPE_AVG]; + u8 RSSI[MAX_TYPE_B][MAX_TYPE_AVG]; + u8 rawSNR[DEFAULT_DATA_AVG_FACTOR]; + u8 rawNF[DEFAULT_DATA_AVG_FACTOR]; + u16 nextSNRNF; + u16 numSNRNF; + u32 RxPDAge; + u16 RxPDRate; + + BOOLEAN RadioOn; + + /** Blue Tooth Co-existence Arbitration */ + HostCmd_DS_802_11_BCA_TIMESHARE bca_ts; + + /** sleep_params */ + SleepParams sp; + + /** sleep_period (Enhanced Power Save) */ + SleepPeriod sleep_period; + +#define MAX_REGION_CHANNEL_NUM 2 + /** Region Channel data */ + REGION_CHANNEL region_channel[MAX_REGION_CHANNEL_NUM]; + + REGION_CHANNEL universal_channel[MAX_REGION_CHANNEL_NUM]; + + /** 11D and Domain Regulatory Data */ + wlan_802_11d_domain_reg_t DomainReg; + parsed_region_chan_11d_t parsed_region_chan; + + /** FSM variable for 11d support */ + wlan_802_11d_state_t State11D; + u8 beaconBuffer[MAX_SCAN_BEACON_BUFFER]; + u8 *pBeaconBufEnd; + + /** MISCELLANEOUS */ + /* Card Information Structure */ + u8 CisInfoBuf[512]; + u16 CisInfoLen; + + HostCmd_DS_802_11_GET_LOG LogMsg; + u16 ScanProbes; + + u32 PktTxCtrl; + + u8 *helper; + u32 helper_len; + u8 *fmimage; + u32 fmimage_len; + u16 TxRate; + + wps_t wps; + + wlan_dbg dbg; + wlan_subscribe_event subevent; + u8 sdiomode; + u32 num_cmd_timeout; +}; + +#endif /* _WLAN_DEV_H_ */ diff --git a/drivers/net/wireless/marvell8686/wlan_fops.c b/drivers/net/wireless/marvell8686/wlan_fops.c new file mode 100644 index 0000000..1e27d6f --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_fops.c @@ -0,0 +1,217 @@ +/** @file wlan_fops.c + * @brief This file contains the file read functions + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 01/06/06: Add Doxygen format comments + +********************************************************/ + +#include "include.h" + +#include +#include +#include +#include + +/******************************************************** + Local Variables +********************************************************/ + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function opens/create a file in kernel mode. + * + * @param filename Name of the file to be opened + * @param flags File flags + * @param mode File permissions + * @return file pointer if successful or NULL if failed. + */ +static struct file * +wlan_fopen(const char *filename, unsigned int flags, int mode) +{ + int orgfsuid, orgfsgid; + struct file *file_ret; + + /* Save uid and gid used for filesystem access. */ + + orgfsuid = current->fsuid; + orgfsgid = current->fsgid; + + /* Set user and group to 0 (root) */ + current->fsuid = 0; + current->fsgid = 0; + + /* Open the file in kernel mode */ + file_ret = filp_open(filename, flags, mode); + + /* Restore the uid and gid */ + current->fsuid = orgfsuid; + current->fsgid = orgfsgid; + + /* Check if the file was opened successfully + and return the file pointer of it was. */ + return ((IS_ERR(file_ret)) ? NULL : file_ret); +} + +/** + * @brief This function closes a file in kernel mode. + * + * @param file_ptr File pointer + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_fclose(struct file *file_ptr) +{ + int orgfsuid, orgfsgid; + int file_ret; + + if ((NULL == file_ptr) || (IS_ERR(file_ptr))) + return -ENOENT; + + /* Save uid and gid used for filesystem access. */ + orgfsuid = current->fsuid; + orgfsgid = current->fsgid; + + /* Set user and group to 0 (root) */ + current->fsuid = 0; + current->fsgid = 0; + + /* Close the file in kernel mode (user_id = 0) */ + file_ret = filp_close(file_ptr, 0); + + /* Restore the uid and gid */ + current->fsuid = orgfsuid; + current->fsgid = orgfsgid; + + return (file_ret); +} + +/** + * @brief This function reads data from files in kernel mode. + * + * @param file_ptr File pointer + * @param buf Buffers to read data into + * @param len Length of buffer + * @return number of characters read + */ +static int +wlan_fread(struct file *file_ptr, char *buf, int len) +{ + int orgfsuid, orgfsgid; + int file_ret; + mm_segment_t orgfs; + + /* Check if the file pointer is valid */ + if ((NULL == file_ptr) || (IS_ERR(file_ptr))) + return -ENOENT; + + /* Check for a valid file read function */ + if (file_ptr->f_op->read == NULL) + return -ENOSYS; + + /* Check for access permissions */ + if (((file_ptr->f_flags & O_ACCMODE) & (O_RDONLY | O_RDWR)) == 0) + return -EACCES; + + /* Check if there is a valid length */ + if (0 >= len) + return -EINVAL; + + /* Save uid and gid used for filesystem access. */ + orgfsuid = current->fsuid; + orgfsgid = current->fsgid; + + /* Set user and group to 0 (root) */ + current->fsuid = 0; + current->fsgid = 0; + + /* Save FS register and set FS register to kernel + space, needed for read and write to accept + buffer in kernel space. */ + orgfs = get_fs(); + + /* Set the FS register to KERNEL mode. */ + set_fs(KERNEL_DS); + + /* Read the actual data from the file */ + file_ret = file_ptr->f_op->read(file_ptr, buf, len, &file_ptr->f_pos); + + /* Restore the FS register */ + set_fs(orgfs); + + /* Restore the uid and gid */ + current->fsuid = orgfsuid; + current->fsgid = orgfsgid; + + return (file_ret); +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function free FW/Helper buffer. + * + * @param addr Pointer to buffer storing FW/Helper + * @return None + */ +void +fw_buffer_free(u8 * addr) +{ + vfree(addr); +} + +/** + * @brief This function reads FW/Helper. + * + * @param name File name + * @param addr Pointer to buffer storing FW/Helper + * @param len Pointer to length of FW/Helper + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +fw_read(const char *name, u8 ** addr, u32 * len) +{ + struct file *fp; + int ret; + u8 *ptr; + + fp = wlan_fopen(name, O_RDWR, 0); + + if (fp == NULL) { + PRINTM(MSG, "Could not open file:%s\n", name); + return WLAN_STATUS_FAILURE; + } + + /*calculate file length */ + *len = fp->f_dentry->d_inode->i_size - fp->f_pos; + + ptr = (u8 *) vmalloc(*len + 1023); + if (ptr == NULL) { + PRINTM(MSG, "vmalloc failure\n"); + return WLAN_STATUS_FAILURE; + } + if (wlan_fread(fp, ptr, *len) > 0) { + *addr = ptr; + ret = WLAN_STATUS_SUCCESS; + } else { + fw_buffer_free(ptr); + *addr = NULL; + PRINTM(MSG, "fail to read the file %s \n", name); + ret = WLAN_STATUS_FAILURE; + } + + wlan_fclose(fp); + return ret; +} diff --git a/drivers/net/wireless/marvell8686/wlan_fw.c b/drivers/net/wireless/marvell8686/wlan_fw.c new file mode 100644 index 0000000..0fc258d --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_fw.c @@ -0,0 +1,532 @@ +/** @file wlan_fw.c + * @brief This file contains the initialization for FW + * and HW + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 09/28/05: Add Doxygen format comments + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join functions. + Cleanup association response handler initialization. + 01/06/05: Add FW file read + 05/08/06: Remove the 2nd GET_HW_SPEC command and TempAddr/PermanentAddr + 06/30/06: replaced MODULE_PARM(name, type) with module_param(name, type, perm) + +********************************************************/ + +#include "include.h" +#include + +/******************************************************** + Local Variables +********************************************************/ + +extern const char *helper_name; +extern const char *fw_name; + +#if 0 +module_param(helper_name, charp, 0); +module_param(fw_name, charp, 0); +#endif + +#ifdef MFG_CMD_SUPPORT +int mfgmode = 0; +module_param(mfgmode, int, 0); +#endif + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function downloads firmware image, gets + * HW spec from firmware and set basic parameters to + * firmware. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_setup_station_hw(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *adapter = priv->adapter; + u8 *ptr = NULL; + u32 len = 0; + + //HostCmd_DS_SDIO_INT_CONFIG sdio_int_cfg; + + ENTER(); + + sbi_disable_host_int(priv); + +#if 0 + if ((intmode == INTMODE_GPIO) && (gpiopin == 0)) { + PRINTM(MSG, "Invalid gpio pin#\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } +#endif + + adapter->fmimage = NULL; + adapter->fmimage_len = 0; + adapter->helper = NULL; + adapter->helper_len = 0; + + if (helper_name != NULL) { + if (fw_read(helper_name, &ptr, &len) != WLAN_STATUS_FAILURE) { + adapter->helper = ptr; + adapter->helper_len = len; + PRINTM(INFO, "helper read success, len=%x\n", len); + } else { + PRINTM(MSG, "helper %s read fail.\n", helper_name); + ret = WLAN_STATUS_FAILURE; + goto done; + } + } + + if (fw_name != NULL) { + if (fw_read(fw_name, &ptr, &len) != WLAN_STATUS_FAILURE) { + adapter->fmimage = ptr; + adapter->fmimage_len = len; + PRINTM(INFO, "fw read success, len=%x\n", len); + } else { + PRINTM(MSG, "fw %s read fail.\n", fw_name); + ret = WLAN_STATUS_FAILURE; + goto done; + } + } + + /* Download the helper */ + ret = sbi_prog_helper(priv); + + if (ret) { + PRINTM(INFO, "Bootloader in invalid state!\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + /* Download the main firmware via the helper firmware */ + if (sbi_prog_firmware_w_helper(priv)) { + PRINTM(INFO, "Wlan FW download failed!\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* check if the fimware is downloaded successfully or not */ + if (sbi_verify_fw_download(priv)) { + PRINTM(INFO, "FW failed to be active in time!\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } +#define RF_REG_OFFSET 0x07 +#define RF_REG_VALUE 0xc8 + + sbi_enable_host_int(priv); + +#define INT_RASING_EDGE 0 +#define INT_FALLING_EDGE 1 + +#define DELAY_1_US 1 +#if 0 + if (intmode == INTMODE_GPIO) { + /* This command should be issued first */ + sdio_int_cfg.Action = HostCmd_ACT_GEN_SET; + sdio_int_cfg.Gpio_pin = gpiopin; + sdio_int_cfg.Gpio_int_edge = INT_FALLING_EDGE; + sdio_int_cfg.Gpio_pulse_width = DELAY_1_US; + ret = PrepareAndSendCommand(priv, HostCmd_CMD_SDIO_GPIO_INT_CONFIG, + 0, HostCmd_OPTION_WAITFORRSP, + 0, &sdio_int_cfg); + } +#endif +#ifdef MFG_CMD_SUPPORT + if (mfgmode == 0) { +#endif + + /* + * Read MAC address from HW + */ + memset(adapter->CurrentAddr, 0xff, MRVDRV_ETH_ADDR_LEN); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_GET_HW_SPEC, + 0, HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_MAC_CONTROL, + 0, HostCmd_OPTION_WAITFORRSP, 0, + &adapter->CurrentPacketFilter); + + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_FW_WAKE_METHOD, + HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, 0, + &priv->adapter->fwWakeupMethod); + + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } +#ifdef MFG_CMD_SUPPORT + } +#endif + +#ifdef MFG_CMD_SUPPORT + if (mfgmode == 0) { +#endif + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RATE_ADAPT_RATESET, + HostCmd_ACT_GEN_GET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + priv->adapter->DataRate = 0; + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RF_TX_POWER, + HostCmd_ACT_GEN_GET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } +#ifdef MFG_CMD_SUPPORT + } +#endif + + ret = WLAN_STATUS_SUCCESS; + done: + if (adapter->helper != NULL) { + fw_buffer_free(adapter->helper); + } + if (adapter->fmimage != NULL) { + fw_buffer_free(adapter->fmimage); + } + + LEAVE(); + + return (ret); +} + +/** + * @brief This function initializes timers. + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +static void +init_sync_objects(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + InitializeTimer(&Adapter->MrvDrvCommandTimer, + MrvDrvCommandTimerFunction, priv); + Adapter->CommandTimerIsSet = FALSE; + +#ifdef REASSOCIATION + /* Initialize the timer for the reassociation */ + InitializeTimer(&Adapter->MrvDrvTimer, MrvDrvReassocTimerFunction, priv); + Adapter->ReassocTimerIsSet = FALSE; +#endif /* REASSOCIATION */ + + return; +} + +/** + * @brief This function allocates buffer for the member of adapter + * structure like command buffer and BSSID list. + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_allocate_adapter(wlan_private * priv) +{ + u32 ulBufSize; + wlan_adapter *Adapter = priv->adapter; + + BSSDescriptor_t *pTempScanTable; + + /* Allocate buffer to store the BSSID list */ + ulBufSize = sizeof(BSSDescriptor_t) * MRVDRV_MAX_BSSID_LIST; + if (!(pTempScanTable = kmalloc(ulBufSize, GFP_KERNEL))) { + return WLAN_STATUS_FAILURE; + } + + Adapter->ScanTable = pTempScanTable; + memset(Adapter->ScanTable, 0, ulBufSize); + + if (!(Adapter->bgScanConfig = + kmalloc(sizeof(HostCmd_DS_802_11_BG_SCAN_CONFIG), GFP_KERNEL))) { + return WLAN_STATUS_FAILURE; + } + Adapter->bgScanConfigSize = sizeof(HostCmd_DS_802_11_BG_SCAN_CONFIG); + memset(Adapter->bgScanConfig, 0, Adapter->bgScanConfigSize); + + spin_lock_init(&Adapter->QueueSpinLock); + + /* Allocate the command buffers */ + if (AllocateCmdBuffer(priv) != WLAN_STATUS_SUCCESS) { + return WLAN_STATUS_FAILURE; + } + + memset(&Adapter->PSConfirmSleep, 0, sizeof(PS_CMD_ConfirmSleep)); + Adapter->PSConfirmSleep.SeqNum = wlan_cpu_to_le16(++Adapter->SeqNum); + Adapter->PSConfirmSleep.Command = + wlan_cpu_to_le16(HostCmd_CMD_802_11_PS_MODE); + Adapter->PSConfirmSleep.Size = + wlan_cpu_to_le16(sizeof(PS_CMD_ConfirmSleep)); + Adapter->PSConfirmSleep.Result = 0; + Adapter->PSConfirmSleep.Action = + wlan_cpu_to_le16(HostCmd_SubCmd_Sleep_Confirmed); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function initializes the adapter structure + * and set default value to the member of adapter. + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +static void +wlan_init_adapter(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int i; + + Adapter->ScanProbes = 0; + + Adapter->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR; + Adapter->data_avg_factor = DEFAULT_DATA_AVG_FACTOR; + + /* ATIM params */ + Adapter->AtimWindow = 0; + Adapter->ATIMEnabled = FALSE; + + Adapter->MediaConnectStatus = WlanMediaStateDisconnected; + + memset(Adapter->CurrentAddr, 0xff, MRVDRV_ETH_ADDR_LEN); + + /* Status variables */ + Adapter->HardwareStatus = WlanHardwareStatusInitializing; + + /* scan type */ + Adapter->ScanType = HostCmd_SCAN_TYPE_ACTIVE; + + /* scan mode */ + Adapter->ScanMode = HostCmd_BSS_TYPE_ANY; + + /* scan time */ + Adapter->SpecificScanTime = MRVDRV_SPECIFIC_SCAN_CHAN_TIME; + Adapter->ActiveScanTime = MRVDRV_ACTIVE_SCAN_CHAN_TIME; + Adapter->PassiveScanTime = MRVDRV_PASSIVE_SCAN_CHAN_TIME; + + /* 802.11 specific */ + Adapter->SecInfo.WEPStatus = Wlan802_11WEPDisabled; + for (i = 0; i < sizeof(Adapter->WepKey) / sizeof(Adapter->WepKey[0]); i++) + memset(&Adapter->WepKey[i], 0, sizeof(MRVL_WEP_KEY)); + Adapter->CurrentWepKeyIndex = 0; + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeOpen; + Adapter->SecInfo.EncryptionMode = CIPHER_NONE; + Adapter->AdhocAESEnabled = FALSE; + Adapter->InfrastructureMode = Wlan802_11Infrastructure; + + Adapter->NumInScanTable = 0; + Adapter->pAttemptedBSSDesc = NULL; +#ifdef REASSOCIATION + OS_INIT_SEMAPHORE(&Adapter->ReassocSem); +#endif + Adapter->pBeaconBufEnd = Adapter->beaconBuffer; + + Adapter->HisRegCpy |= HIS_TxDnLdRdy; + + memset(&Adapter->CurBssParams, 0, sizeof(Adapter->CurBssParams)); + + /* PnP and power profile */ + Adapter->SurpriseRemoved = FALSE; + + Adapter->CurrentPacketFilter = + HostCmd_ACT_MAC_RTS_CTS_ENABLE | + HostCmd_ACT_MAC_RX_ON | HostCmd_ACT_MAC_TX_ON; + + Adapter->RadioOn = RADIO_ON; +#ifdef REASSOCIATION +#if (WIRELESS_EXT >= 18) + Adapter->Reassoc_on = FALSE; +#else + Adapter->Reassoc_on = TRUE; +#endif +#endif /* REASSOCIATION */ + Adapter->TxAntenna = RF_ANTENNA_2; + Adapter->RxAntenna = RF_ANTENNA_AUTO; + + Adapter->HWRateDropMode = HW_TABLE_RATE_DROP; + Adapter->Is_DataRate_Auto = TRUE; + Adapter->BeaconPeriod = MRVDRV_BEACON_INTERVAL; + + Adapter->AdhocChannel = DEFAULT_AD_HOC_CHANNEL; + + Adapter->PSMode = Wlan802_11PowerModeCAM; + Adapter->MultipleDtim = MRVDRV_DEFAULT_MULTIPLE_DTIM; + + Adapter->ListenInterval = MRVDRV_DEFAULT_LISTEN_INTERVAL; + + Adapter->PSState = PS_STATE_FULL_POWER; + + Adapter->NeedToWakeup = FALSE; + Adapter->LocalListenInterval = 0; /* default value in firmware will be used */ + Adapter->fwWakeupMethod = WAKEUP_FW_UNCHANGED; + + Adapter->IsDeepSleep = FALSE; + Adapter->IsAutoDeepSleepEnabled = FALSE; + + Adapter->bWakeupDevRequired = FALSE; + + Adapter->WakeupTries = 0; + Adapter->bHostSleepConfigured = FALSE; + + Adapter->HSCfg.conditions = HOST_SLEEP_CFG_CANCEL; + Adapter->HSCfg.gpio = 0; + Adapter->HSCfg.gap = 0; + + Adapter->DataRate = 0; // Initially indicate the rate as auto + + Adapter->adhoc_grate_enabled = FALSE; + + Adapter->IntCounter = Adapter->IntCounterSaved = 0; + + INIT_LIST_HEAD((struct list_head *) &Adapter->RxSkbQ); + + Adapter->gen_null_pkg = TRUE; /*Enable NULL Pkg generation */ + + init_waitqueue_head(&Adapter->cmd_EncKey); + + spin_lock_init(&Adapter->CurrentTxLock); + + Adapter->CurrentTxSkb = NULL; + Adapter->PktTxCtrl = 0; + + return; +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function initializes firmware + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_init_fw(wlan_private * priv) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + /* Allocate adapter structure */ + if ((ret = wlan_allocate_adapter(priv)) != WLAN_STATUS_SUCCESS) { + goto done; + } + + /* init adapter structure */ + wlan_init_adapter(priv); + + /* init timer etc. */ + init_sync_objects(priv); + + /* download fimrware etc. */ + if ((ret = wlan_setup_station_hw(priv)) != WLAN_STATUS_SUCCESS) { + Adapter->HardwareStatus = WlanHardwareStatusNotReady; + ret = WLAN_STATUS_FAILURE; + goto done; + } + /* init 802.11d */ + wlan_init_11d(priv); + + Adapter->HardwareStatus = WlanHardwareStatusReady; + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + return ret; +} + +/** + * @brief This function frees the structure of adapter + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +void +wlan_free_adapter(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!Adapter) { + PRINTM(INFO, "Why double free adapter?:)\n"); + return; + } + + PRINTM(INFO, "Free Command buffer\n"); + FreeCmdBuffer(priv); + + PRINTM(INFO, "Free CommandTimer\n"); + if (Adapter->CommandTimerIsSet) { + CancelTimer(&Adapter->MrvDrvCommandTimer); + Adapter->CommandTimerIsSet = FALSE; + } + FreeTimer(&Adapter->MrvDrvCommandTimer); +#ifdef REASSOCIATION + PRINTM(INFO, "Free MrvDrvTimer\n"); + if (Adapter->ReassocTimerIsSet) { + CancelTimer(&Adapter->MrvDrvTimer); + Adapter->ReassocTimerIsSet = FALSE; + } + FreeTimer(&Adapter->MrvDrvTimer); +#endif /* REASSOCIATION */ + + if (Adapter->bgScanConfig) { + kfree(Adapter->bgScanConfig); + Adapter->bgScanConfig = NULL; + } + + OS_FREE_LOCK(&Adapter->CurrentTxLock); + OS_FREE_LOCK(&Adapter->QueueSpinLock); + + PRINTM(INFO, "Free ScanTable\n"); + if (Adapter->ScanTable) { + kfree(Adapter->ScanTable); + Adapter->ScanTable = NULL; + } + + PRINTM(INFO, "Free Adapter\n"); + + /* Free the adapter object itself */ + kfree(Adapter); + priv->adapter = NULL; + LEAVE(); +} diff --git a/drivers/net/wireless/marvell8686/wlan_join.c b/drivers/net/wireless/marvell8686/wlan_join.c new file mode 100644 index 0000000..46d5961 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_join.c @@ -0,0 +1,2031 @@ +/** @file wlan_join.c + * + * @brief Functions implementing wlan infrastructure and adhoc join routines + * + * IOCTL handlers as well as command preperation and response routines + * for sending adhoc start, adhoc join, and association commands + * to the firmware. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + * + * @sa wlan_join.h + */ +/************************************************************* +Change Log: + 01/11/06: Initial revision. Match new scan code, relocate related functions + 01/19/06: Fix failure to save adhoc ssid as current after adhoc start + 03/16/06: Add a semaphore to protect reassociation thread + +************************************************************/ + +#include "include.h" + +/** + * @brief This function finds out the common rates between rate1 and rate2. + * + * It will fill common rates in rate1 as output if found. + * + * NOTE: Setting the MSB of the basic rates need to be taken + * care, either before or after calling this function + * + * @param Adapter A pointer to wlan_adapter structure + * @param rate1 the buffer which keeps input and output + * @param rate1_size the size of rate1 buffer + * @param rate2 the buffer which keeps rate2 + * @param rate2_size the size of rate2 buffer. + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +get_common_rates(wlan_adapter * Adapter, u8 * rate1, + int rate1_size, u8 * rate2, int rate2_size) +{ + u8 *ptr = rate1; + int ret = WLAN_STATUS_SUCCESS; + u8 *tmp = NULL; + int i, j; + + if (!(tmp = kmalloc(rate1_size, GFP_KERNEL))) { + PRINTM(WARN, "Allocate buffer for common rates failed\n"); + return -ENOMEM; + } + + memcpy(tmp, rate1, rate1_size); + memset(rate1, 0, rate1_size); + + for (i = 0; rate2[i] && i < rate2_size; i++) { + for (j = 0; tmp[j] && j < rate1_size; j++) { + /* Check common rate, excluding the bit for basic rate */ + if ((rate2[i] & 0x7F) == (tmp[j] & 0x7F)) { + *rate1++ = tmp[j]; + break; + } + } + } + + HEXDUMP("rate1 (AP) Rates", tmp, rate1_size); + HEXDUMP("rate2 (Card) Rates", rate2, rate2_size); + HEXDUMP("Common Rates", ptr, rate1 - ptr); + PRINTM(INFO, "Tx DataRate is set to 0x%X\n", Adapter->DataRate); + + if (!Adapter->Is_DataRate_Auto) { + while (*ptr) { + if ((*ptr & 0x7f) == Adapter->DataRate) { + ret = WLAN_STATUS_SUCCESS; + goto done; + } + ptr++; + } + PRINTM(MSG, "Previously set fixed data rate %#x isn't " + "compatible with the network.\n", Adapter->DataRate); + + ret = WLAN_STATUS_FAILURE; + goto done; + } + + ret = WLAN_STATUS_SUCCESS; + done: + kfree(tmp); + return ret; +} + +/** + * @brief Create the intersection of the rates supported by a target BSS and + * our Adapter settings for use in an assoc/join command. + * + * @param Adapter A pointer to wlan_adapter structure + * @param pBSSDesc BSS Descriptor whose rates are used in the setup + * @param pOutRates Output: Octet array of rates common between the BSS + * and the Adapter supported rates settings + * @param pOutRatesSize Output: Number of rates/octets set in pOutRates + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + * + */ +static int +setup_rates_from_bssdesc(wlan_adapter * Adapter, + BSSDescriptor_t * pBSSDesc, + u8 * pOutRates, int *pOutRatesSize) +{ + u8 *card_rates; + int card_rates_size; + + ENTER(); + + memcpy(pOutRates, pBSSDesc->SupportedRates, WLAN_SUPPORTED_RATES); + + card_rates = SupportedRates; + card_rates_size = sizeof(SupportedRates); + + if (get_common_rates(Adapter, pOutRates, WLAN_SUPPORTED_RATES, + card_rates, card_rates_size)) { + *pOutRatesSize = 0; + PRINTM(INFO, "get_common_rates failed\n"); + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + *pOutRatesSize = MIN(strlen(pOutRates), WLAN_SUPPORTED_RATES); + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Retrieve the association response + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_get_assoc_rsp_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int copySize; + + /* + * Set the amount to copy back to the application as the minimum of the + * available assoc resp data or the buffer provided by the application + */ + copySize = MIN(Adapter->assocRspSize, wrq->u.data.length); + + /* Copy the (re)association response back to the application */ + if (copy_to_user(wrq->u.data.pointer, Adapter->assocRspBuffer, copySize)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + /* Returned copy length */ + wrq->u.data.length = copySize; + + /* Reset assoc buffer */ + Adapter->assocRspSize = 0; + + /* No error on return */ + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set an opaque block of Marvell TLVs for insertion into the + * association command + * + * Pass an opaque block of data, expected to be Marvell TLVs, to the driver + * for eventual passthrough to the firmware in an associate/join + * (and potentially start) command. + * + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_set_mrvl_tlv_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + /* If the passed length is zero, reset the buffer */ + if (wrq->u.data.length == 0) { + Adapter->mrvlAssocTlvBufferLen = 0; + } else { + /* + * Verify that the passed length is not larger than the available + * space remaining in the buffer + */ + if (wrq->u.data.length < (sizeof(Adapter->mrvlAssocTlvBuffer) + - Adapter->mrvlAssocTlvBufferLen)) { + /* Append the passed data to the end of the mrvlAssocTlvBuffer */ + if (copy_from_user(Adapter->mrvlAssocTlvBuffer + + Adapter->mrvlAssocTlvBufferLen, + wrq->u.data.pointer, wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + /* Increment the stored buffer length by the size passed */ + Adapter->mrvlAssocTlvBufferLen += wrq->u.data.length; + } else { + /* Passed data does not fit in the remaining buffer space */ + ret = WLAN_STATUS_FAILURE; + } + } + + /* Return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE */ + return ret; +} + +/** + * @brief Stop Adhoc Network + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_do_adhocstop_ioctl(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (Adapter->InfrastructureMode == Wlan802_11IBSS && + Adapter->MediaConnectStatus == WlanMediaStateConnected) { + + ret = StopAdhocNetwork(priv); + + } else { + LEAVE(); + return -ENOTSUPP; + } + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set essid + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS--success, otherwise--fail + */ +int +wlan_set_essid(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + WLAN_802_11_SSID reqSSID; + int i; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + /* Clear any past association response stored for application retrieval */ + Adapter->assocRspSize = 0; + +#ifdef REASSOCIATION + // cancel re-association timer if there's one + if (Adapter->ReassocTimerIsSet == TRUE) { + CancelTimer(&Adapter->MrvDrvTimer); + Adapter->ReassocTimerIsSet = FALSE; + } + + if (OS_ACQ_SEMAPHORE_BLOCK(&Adapter->ReassocSem)) { + PRINTM(ERROR, "Acquire semaphore error, wlan_set_essid\n"); + return -EBUSY; + } +#endif /* REASSOCIATION */ + + /* Check the size of the string */ + if (dwrq->length > IW_ESSID_MAX_SIZE + 1) { + ret = -E2BIG; + goto setessid_ret; + } + + memset(&reqSSID, 0, sizeof(WLAN_802_11_SSID)); + + /* + * Check if we asked for `any' or 'particular' + */ + if (!dwrq->flags) { + if (FindBestNetworkSsid(priv, &reqSSID)) { + PRINTM(INFO, "Could not find best network\n"); + ret = WLAN_STATUS_SUCCESS; + goto setessid_ret; + } + } else { + /* Set the SSID */ +#if WIRELESS_EXT > 20 + reqSSID.SsidLength = dwrq->length; +#else + reqSSID.SsidLength = dwrq->length - 1; +#endif + memcpy(reqSSID.Ssid, extra, + MIN(reqSSID.SsidLength, reqSSID.SsidLength)); + + } + + PRINTM(INFO, "Requested new SSID = %s\n", + (reqSSID.SsidLength > 0) ? (char *) reqSSID.Ssid : "NULL"); + if (!reqSSID.SsidLength || reqSSID.Ssid[0] < 0x20) { + PRINTM(INFO, "Invalid SSID - aborting set_essid\n"); + ret = -EINVAL; + goto setessid_ret; + } + + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + /* infrastructure mode */ + PRINTM(INFO, "SSID requested = %s\n", reqSSID.Ssid); + + if ((dwrq->flags & IW_ENCODE_INDEX) > 1) { + i = (dwrq->flags & IW_ENCODE_INDEX) - 1; /* convert to 0 based */ + + PRINTM(INFO, "Request SSID by index = %d\n", i); + + if (i > Adapter->NumInScanTable) { + /* Failed to find in table since index is > current max. */ + i = -EINVAL; + } + } else { + SendSpecificSSIDScan(priv, &reqSSID); + i = FindSSIDInList(Adapter, + &reqSSID, NULL, Wlan802_11Infrastructure); + } + + if (i >= 0) { + PRINTM(INFO, "SSID found in scan list ... associating...\n"); + + ret = wlan_associate(priv, &Adapter->ScanTable[i]); + + if (ret) { + goto setessid_ret; + } + } else { /* i >= 0 */ + ret = i; /* return -ENETUNREACH, passed from FindSSIDInList */ + goto setessid_ret; + } + } else { + /* ad hoc mode */ + /* If the requested SSID matches current SSID return */ + if (!SSIDcmp(&Adapter->CurBssParams.BSSDescriptor.Ssid, &reqSSID)) { + ret = WLAN_STATUS_SUCCESS; + goto setessid_ret; + } + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + /* + * Exit Adhoc mode + */ + PRINTM(INFO, "Sending Adhoc Stop\n"); + ret = StopAdhocNetwork(priv); + + if (ret) { + goto setessid_ret; + } + } + Adapter->AdhocLinkSensed = FALSE; + + if ((dwrq->flags & IW_ENCODE_INDEX) > 1) { + i = (dwrq->flags & IW_ENCODE_INDEX) - 1; /* 0 based */ + if (i > Adapter->NumInScanTable) { + /* Failed to find in table since index is > current max. */ + i = -EINVAL; + } + } else { + /* Scan for the network */ + SendSpecificSSIDScan(priv, &reqSSID); + + /* Search for the requested SSID in the scan table */ + i = FindSSIDInList(Adapter, &reqSSID, NULL, Wlan802_11IBSS); + } + + if (i >= 0) { + PRINTM(INFO, "SSID found at %d in List, so join\n", i); + JoinAdhocNetwork(priv, &Adapter->ScanTable[i]); + } else { + /* else send START command */ + PRINTM(INFO, "SSID not found in list, " + "so creating adhoc with ssid = %s\n", reqSSID.Ssid); + + StartAdhocNetwork(priv, &reqSSID); + } /* end of else (START command) */ + } /* end of else (Ad hoc mode) */ + + /* + * The MediaConnectStatus change can be removed later when + * the ret code is being properly returned. + */ + /* Check to see if we successfully connected */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + ret = WLAN_STATUS_SUCCESS; + } else { + ret = -ENETDOWN; + } + + setessid_ret: +#ifdef REASSOCIATION + OS_REL_SEMAPHORE(&Adapter->ReassocSem); +#endif + + LEAVE(); + return ret; +} + +/** + * @brief Connect to the AP or Ad-hoc Network with specific bssid + * + * NOTE: Scan should be issued by application before this function is called + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param awrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_set_wap(struct net_device *dev, struct iw_request_info *info, + struct sockaddr *awrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + const u8 bcast[ETH_ALEN] = { 255, 255, 255, 255, 255, 255 }; + u8 reqBSSID[ETH_ALEN]; + int i; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + /* Clear any past association response stored for application retrieval */ + Adapter->assocRspSize = 0; + + if (awrq->sa_family != ARPHRD_ETHER) + return -EINVAL; + + PRINTM(INFO, "ASSOC: WAP: sa_data: %02x:%02x:%02x:%02x:%02x:%02x\n", + (u8) awrq->sa_data[0], (u8) awrq->sa_data[1], + (u8) awrq->sa_data[2], (u8) awrq->sa_data[3], + (u8) awrq->sa_data[4], (u8) awrq->sa_data[5]); +#ifdef REASSOCIATION + // cancel re-association timer if there's one + if (Adapter->ReassocTimerIsSet == TRUE) { + CancelTimer(&Adapter->MrvDrvTimer); + Adapter->ReassocTimerIsSet = FALSE; + } +#endif /* REASSOCIATION */ + + if (!memcmp(bcast, awrq->sa_data, ETH_ALEN)) { + i = FindBestSSIDInList(Adapter); + } else { + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + if (memcmp + (awrq->sa_data, + Adapter->CurBssParams.BSSDescriptor.MacAddress, + ETH_ALEN) == 0) + return WLAN_STATUS_SUCCESS; + } + memcpy(reqBSSID, awrq->sa_data, ETH_ALEN); + + PRINTM(INFO, "ASSOC: WAP: Bssid = %02x:%02x:%02x:%02x:%02x:%02x\n", + reqBSSID[0], reqBSSID[1], reqBSSID[2], + reqBSSID[3], reqBSSID[4], reqBSSID[5]); + + /* Search for index position in list for requested MAC */ + i = FindBSSIDInList(Adapter, reqBSSID, Adapter->InfrastructureMode); + } + + if (i < 0) { + PRINTM(INFO, "ASSOC: WAP: MAC address not found in BSSID List\n"); + return -ENETUNREACH; + } + + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + + ret = wlan_associate(priv, &Adapter->ScanTable[i]); + + if (ret) { + LEAVE(); + return ret; + } + } else { + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + /* Exit Adhoc mode */ + ret = StopAdhocNetwork(priv); + + if (ret) { + LEAVE(); + return ret; + } + } + Adapter->AdhocLinkSensed = FALSE; + + JoinAdhocNetwork(priv, &Adapter->ScanTable[i]); + } + + /* Check to see if we successfully connected */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + ret = WLAN_STATUS_SUCCESS; + } else { + ret = -ENETDOWN; + } + + LEAVE(); + return ret; +} + +/** + * @brief Associated to a specific BSS discovered in a scan + * + * @param priv A pointer to wlan_private structure + * @param pBSSDesc Pointer to the BSS descriptor to associate with. + * + * @return WLAN_STATUS_SUCCESS-success, otherwise fail + */ +int +wlan_associate(wlan_private * priv, BSSDescriptor_t * pBSSDesc) +{ + wlan_adapter *Adapter = priv->adapter; + int enableData = TRUE; + union iwreq_data wrqu; + int ret; + IEEEtypes_AssocRsp_t *pAssocRsp; + u8 currentBSSID[MRVDRV_ETH_ADDR_LEN]; + int reassocAttempt = FALSE; + + ENTER(); + + /* Return error if the Adapter or table entry is not marked as infra */ + if ((Adapter->InfrastructureMode != Wlan802_11Infrastructure) + || (pBSSDesc->InfrastructureMode != Wlan802_11Infrastructure)) { + LEAVE(); + return -EINVAL; + } + + memcpy(¤tBSSID, + &Adapter->CurBssParams.BSSDescriptor.MacAddress, + sizeof(currentBSSID)); + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + reassocAttempt = TRUE; + PRINTM(INFO, "Attempting reassociation, stopping wmm queues\n"); + wmm_stop_queue(priv); + } + + /* Clear any past association response stored for application retrieval */ + Adapter->assocRspSize = 0; + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_ASSOCIATE, + 0, HostCmd_OPTION_WAITFORRSP, 0, pBSSDesc); + + if (Adapter->wmm.enabled) { + /* Don't re-enable carrier until we get the WMM_GET_STATUS event */ + enableData = FALSE; + } else { + /* Since WMM is not enabled, setup the queues with the defaults */ + wmm_setup_queues(priv); + } + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + + if (reassocAttempt + && (memcmp(¤tBSSID, + &Adapter->CurBssParams.BSSDescriptor.MacAddress, + sizeof(currentBSSID)) == 0)) { + + /* Reassociation attempt failed, still associated to old AP, + ** no need to wait for WMM notification to restart data + */ + enableData = TRUE; + } + if (enableData) { + PRINTM(INFO, "Post association, re-enabling data flow\n"); + wmm_start_queue(priv); + os_carrier_on(priv); + os_start_queue(priv); + } + } else { + PRINTM(INFO, "Post association, stopping data flow\n"); + os_carrier_off(priv); + os_stop_queue(priv); + } + + memcpy(wrqu.ap_addr.sa_data, + &Adapter->CurBssParams.BSSDescriptor.MacAddress, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWAP, &wrqu, NULL); + + pAssocRsp = (IEEEtypes_AssocRsp_t *) Adapter->assocRspBuffer; + + if (ret || pAssocRsp->StatusCode) { + ret = -ENETUNREACH; + } + + LEAVE(); + return ret; +} + +/** + * @brief Associated to a specific indexed entry in the ScanTable + * + * @param priv A pointer to wlan_private structure + * @param tableIdx Index into the ScanTable to associate to, index parameter + * base value is 1. No scanning is done before the + * association attempt. + * + * @return WLAN_STATUS_SUCCESS-success, otherwise fail + */ +int +wlan_associate_to_table_idx(wlan_private * priv, int tableIdx) +{ + wlan_adapter *Adapter = priv->adapter; + int ret; + + ENTER(); + +#ifdef REASSOCIATION + if (OS_ACQ_SEMAPHORE_BLOCK(&Adapter->ReassocSem)) { + PRINTM(ERROR, "Acquire semaphore error\n"); + return -EBUSY; + } +#endif + + PRINTM(INFO, "ASSOC: iwpriv: Index = %d, NumInScanTable = %d\n", + tableIdx, Adapter->NumInScanTable); + + /* Check index in table, subtract 1 if within range and call association + * sub-function. ScanTable[] is 0 based, parameter is 1 based to + * conform with IW_ENCODE_INDEX flag parameter passing in iwconfig/iwlist + */ + if (tableIdx && (tableIdx <= Adapter->NumInScanTable)) { + ret = wlan_associate(priv, &Adapter->ScanTable[tableIdx - 1]); + } else { + ret = -EINVAL; + } + +#ifdef REASSOCIATION + OS_REL_SEMAPHORE(&Adapter->ReassocSem); +#endif + LEAVE(); + + return ret; +} + +/** + * @brief Start an Adhoc Network + * + * @param priv A pointer to wlan_private structure + * @param AdhocSSID The ssid of the Adhoc Network + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +int +StartAdhocNetwork(wlan_private * priv, WLAN_802_11_SSID * AdhocSSID) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + Adapter->AdhocCreate = TRUE; + + PRINTM(INFO, "Adhoc Channel = %d\n", Adapter->AdhocChannel); + PRINTM(INFO, "CurBssParams.channel = %d\n", + Adapter->CurBssParams.BSSDescriptor.Channel); + PRINTM(INFO, "CurBssParams.band = %d\n", Adapter->CurBssParams.band); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_AD_HOC_START, + 0, HostCmd_OPTION_WAITFORRSP, 0, AdhocSSID); + + LEAVE(); + return ret; +} + +/** + * @brief Join an adhoc network found in a previous scan + * + * @param priv A pointer to wlan_private structure + * @param pBSSDesc Pointer to a BSS descriptor found in a previous scan + * to attempt to join + * + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +int +JoinAdhocNetwork(wlan_private * priv, BSSDescriptor_t * pBSSDesc) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + PRINTM(INFO, "JoinAdhocNetwork: CurBss.ssid =%s\n", + Adapter->CurBssParams.BSSDescriptor.Ssid.Ssid); + PRINTM(INFO, "JoinAdhocNetwork: CurBss.ssid_len =%u\n", + Adapter->CurBssParams.BSSDescriptor.Ssid.SsidLength); + PRINTM(INFO, "JoinAdhocNetwork: ssid =%s\n", pBSSDesc->Ssid.Ssid); + PRINTM(INFO, "JoinAdhocNetwork: ssid len =%u\n", + pBSSDesc->Ssid.SsidLength); + + /* check if the requested SSID is already joined */ + if (Adapter->CurBssParams.BSSDescriptor.Ssid.SsidLength + && !SSIDcmp(&pBSSDesc->Ssid, + &Adapter->CurBssParams.BSSDescriptor.Ssid) + && (Adapter->CurBssParams.BSSDescriptor.InfrastructureMode == + Wlan802_11IBSS)) { + + PRINTM(INFO, + "ADHOC_J_CMD: New ad-hoc SSID is the same as current, " + "not attempting to re-join"); + + return WLAN_STATUS_FAILURE; + } + + PRINTM(INFO, "CurBssParams.channel = %d\n", + Adapter->CurBssParams.BSSDescriptor.Channel); + PRINTM(INFO, "CurBssParams.band = %c\n", Adapter->CurBssParams.band); + + Adapter->AdhocCreate = FALSE; + + // store the SSID info temporarily + memset(&Adapter->AttemptedSSIDBeforeScan, 0, sizeof(WLAN_802_11_SSID)); + memcpy(&Adapter->AttemptedSSIDBeforeScan, + &pBSSDesc->Ssid, sizeof(WLAN_802_11_SSID)); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_AD_HOC_JOIN, + 0, HostCmd_OPTION_WAITFORRSP, 0, pBSSDesc); + + LEAVE(); + return ret; +} + +/** + * @brief Stop the Adhoc Network + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +int +StopAdhocNetwork(wlan_private * priv) +{ + return PrepareAndSendCommand(priv, HostCmd_CMD_802_11_AD_HOC_STOP, + 0, HostCmd_OPTION_WAITFORRSP, 0, NULL); +} + +/** + * @brief Send Deauthentication Request + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +int +SendDeauthentication(wlan_private * priv) +{ + return PrepareAndSendCommand(priv, HostCmd_CMD_802_11_DEAUTHENTICATE, + 0, HostCmd_OPTION_WAITFORRSP, 0, NULL); +} + +/** + * @brief Set Idle Off + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlanidle_off(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + const u8 zeroMac[] = { 0, 0, 0, 0, 0, 0 }; + int i; + + ENTER(); + + if (Adapter->MediaConnectStatus == WlanMediaStateDisconnected) { + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + if (memcmp(Adapter->PreviousBSSID, zeroMac, sizeof(zeroMac)) != 0) { + + PRINTM(INFO, "Previous SSID = %s\n", + Adapter->PreviousSSID.Ssid); + PRINTM(INFO, "Previous BSSID = " + "%02x:%02x:%02x:%02x:%02x:%02x:\n", + Adapter->PreviousBSSID[0], Adapter->PreviousBSSID[1], + Adapter->PreviousBSSID[2], Adapter->PreviousBSSID[3], + Adapter->PreviousBSSID[4], Adapter->PreviousBSSID[5]); + + i = FindSSIDInList(Adapter, + &Adapter->PreviousSSID, + Adapter->PreviousBSSID, + Adapter->InfrastructureMode); + + if (i < 0) { + SendSpecificBSSIDScan(priv, Adapter->PreviousBSSID); + i = FindSSIDInList(Adapter, + &Adapter->PreviousSSID, + Adapter->PreviousBSSID, + Adapter->InfrastructureMode); + } + + if (i < 0) { + /* If the BSSID could not be found, try just the SSID */ + i = FindSSIDInList(Adapter, + &Adapter->PreviousSSID, + NULL, Adapter->InfrastructureMode); + } + + if (i < 0) { + SendSpecificSSIDScan(priv, &Adapter->PreviousSSID); + i = FindSSIDInList(Adapter, + &Adapter->PreviousSSID, + NULL, Adapter->InfrastructureMode); + } + + if (i >= 0) { + ret = wlan_associate(priv, &Adapter->ScanTable[i]); + } + } + } else if (Adapter->InfrastructureMode == Wlan802_11IBSS) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_AD_HOC_START, + 0, HostCmd_OPTION_WAITFORRSP, + 0, &Adapter->PreviousSSID); + } + } + /* else it is connected */ + + PRINTM(INFO, "\nwlanidle is off"); + LEAVE(); + return ret; +} + +/** + * @brief Set Idle On + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlanidle_on(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + PRINTM(INFO, "Previous SSID = %s\n", Adapter->PreviousSSID.Ssid); + memcpy(&Adapter->PreviousSSID, + &Adapter->CurBssParams.BSSDescriptor.Ssid, + sizeof(WLAN_802_11_SSID)); + SendDeauthentication(priv); + + } else if (Adapter->InfrastructureMode == Wlan802_11IBSS) { + ret = StopAdhocNetwork(priv); + } + + } +#ifdef REASSOCIATION + if (Adapter->ReassocTimerIsSet == TRUE) { + CancelTimer(&Adapter->MrvDrvTimer); + Adapter->ReassocTimerIsSet = FALSE; + } +#endif /* REASSOCIATION */ + + PRINTM(INFO, "\nwlanidle is on"); + + LEAVE(); + return ret; +} + +/** + * @brief Append a generic IE as a passthrough TLV to a TLV buffer. + * + * This function is called from the network join command prep. routine. + * If the IE buffer has been setup by the application, this routine appends + * the buffer as a passthrough TLV type to the request. + * + * @param priv A pointer to wlan_private structure + * @param ppBuffer pointer to command buffer pointer + * + * @return bytes added to the buffer + */ +static int +wlan_cmd_append_generic_ie(wlan_private * priv, u8 ** ppBuffer) +{ + wlan_adapter *Adapter = priv->adapter; + int retLen = 0; + MrvlIEtypesHeader_t ieHeader; + + /* Null Checks */ + if (ppBuffer == 0) + return 0; + if (*ppBuffer == 0) + return 0; + + /* + * If there is a generic ie buffer setup, append it to the return + * parameter buffer pointer. + */ + if (Adapter->genIeBufferLen) { + PRINTM(INFO, "append generic %d to %p\n", Adapter->genIeBufferLen, + *ppBuffer); + + /* Wrap the generic IE buffer with a passthrough TLV type */ + ieHeader.Type = wlan_cpu_to_le16(TLV_TYPE_PASSTHROUGH); + ieHeader.Len = wlan_cpu_to_le16(Adapter->genIeBufferLen); + memcpy(*ppBuffer, &ieHeader, sizeof(ieHeader)); + + /* Increment the return size and the return buffer pointer param */ + *ppBuffer += sizeof(ieHeader); + retLen += sizeof(ieHeader); + + /* Copy the generic IE buffer to the output buffer, advance pointer */ + memcpy(*ppBuffer, Adapter->genIeBuffer, Adapter->genIeBufferLen); + + /* Increment the return size and the return buffer pointer param */ + *ppBuffer += Adapter->genIeBufferLen; + retLen += Adapter->genIeBufferLen; + + /* Reset the generic IE buffer */ + Adapter->genIeBufferLen = 0; + } + + /* return the length appended to the buffer */ + return retLen; +} + +/** + * @brief Append any application provided Marvell TLVs to a TLV buffer. + * + * This function is called from the network join command prep. routine. + * If the Marvell TLV buffer has been setup by the application, this routine + * appends the buffer to the request. + * + * @param priv A pointer to wlan_private structure + * @param ppBuffer pointer to command buffer pointer + * + * @return bytes added to the buffer + */ +static int +wlan_cmd_append_marvell_tlv(wlan_private * priv, u8 ** ppBuffer) +{ + wlan_adapter *Adapter = priv->adapter; + int retLen = 0; + + /* Null Checks */ + if (ppBuffer == 0) + return 0; + if (*ppBuffer == 0) + return 0; + + /* + * If there is a Marvell TLV buffer setup, append it to the return + * parameter buffer pointer. + */ + if (Adapter->mrvlAssocTlvBufferLen) { + PRINTM(INFO, "append tlv %d to %p\n", + Adapter->mrvlAssocTlvBufferLen, *ppBuffer); + + /* Copy the TLV buffer to the output buffer, advance pointer */ + memcpy(*ppBuffer, + Adapter->mrvlAssocTlvBuffer, Adapter->mrvlAssocTlvBufferLen); + + /* Increment the return size and the return buffer pointer param */ + *ppBuffer += Adapter->mrvlAssocTlvBufferLen; + retLen += Adapter->mrvlAssocTlvBufferLen; + + /* Reset the Marvell TLV buffer */ + Adapter->mrvlAssocTlvBufferLen = 0; + } + + /* return the length appended to the buffer */ + return retLen; +} + +/** + * @brief Append TSF tracking info from the scan table for the target AP + * + * This function is called from the network join command prep. routine. + * The TSF table TSF sent to the firmware contians two TSF values: + * - the TSF of the target AP from its previous beacon/probe response + * - the TSF timestamp of our local MAC at the time we observed the + * beacon/probe response. + * + * The firmware uses the timestamp values to set an initial TSF value + * in the MAC for the new association after a reassociation attempt. + * + * @param priv A pointer to wlan_private structure + * @param ppBuffer A pointer to command buffer pointer + * @param pBSSDesc A pointer to the BSS Descriptor from the scan table of + * the AP we are trying to join + * + * @return bytes added to the buffer + */ +static int +wlan_cmd_append_tsf_tlv(wlan_private * priv, u8 ** ppBuffer, + BSSDescriptor_t * pBSSDesc) +{ + MrvlIEtypes_TsfTimestamp_t tsfTlv; + u64 tsfVal; + + /* Null Checks */ + if (ppBuffer == 0) + return 0; + if (*ppBuffer == 0) + return 0; + + memset(&tsfTlv, 0x00, sizeof(MrvlIEtypes_TsfTimestamp_t)); + + tsfTlv.Header.Type = wlan_cpu_to_le16(TLV_TYPE_TSFTIMESTAMP); + tsfTlv.Header.Len = wlan_cpu_to_le16(2 * sizeof(tsfVal)); + + memcpy(*ppBuffer, &tsfTlv, sizeof(tsfTlv.Header)); + *ppBuffer += sizeof(tsfTlv.Header); + + /* TSF timestamp from the firmware TSF when the bcn/prb rsp was received */ + tsfVal = wlan_cpu_to_le64(pBSSDesc->networkTSF); + memcpy(*ppBuffer, &tsfVal, sizeof(tsfVal)); + *ppBuffer += sizeof(tsfVal); + + memcpy(&tsfVal, pBSSDesc->TimeStamp, sizeof(tsfVal)); + + PRINTM(INFO, "ASSOC: TSF offset calc: %016llx - %016llx\n", + tsfVal, pBSSDesc->networkTSF); + + tsfVal = wlan_cpu_to_le64(tsfVal); + memcpy(*ppBuffer, &tsfVal, sizeof(tsfVal)); + *ppBuffer += sizeof(tsfVal); + + return (sizeof(tsfTlv.Header) + (2 * sizeof(tsfVal))); +} + +/** + * @brief This function prepares command of deauthenticate. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_deauthenticate(wlan_private * priv, HostCmd_DS_COMMAND * cmd) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_DEAUTHENTICATE *dauth = &cmd->params.deauth; + + ENTER(); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_DEAUTHENTICATE); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_DEAUTHENTICATE) + S_DS_GEN); + + /* set AP MAC address */ + memcpy(dauth->MacAddr, + &Adapter->CurBssParams.BSSDescriptor.MacAddress, ETH_ALEN); + + /* Reason code 3 = Station is leaving */ +#define REASON_CODE_STA_LEAVING 3 + dauth->ReasonCode = wlan_cpu_to_le16(REASON_CODE_STA_LEAVING); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of association. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf Void cast of BSSDescriptor_t from the scan table to assoc + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_associate(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pdata_buf) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_ASSOCIATE *pAsso = &cmd->params.associate; + int ret = WLAN_STATUS_SUCCESS; + BSSDescriptor_t *pBSSDesc; + WLAN_802_11_RATES rates; + int ratesSize; + u8 *pos; + u16 TmpCap; + MrvlIEtypes_SsIdParamSet_t *pSsidTlv; + MrvlIEtypes_PhyParamSet_t *pPhyTlv; + MrvlIEtypes_SsParamSet_t *pSsTlv; + MrvlIEtypes_RatesParamSet_t *pRatesTlv; + MrvlIEtypes_AuthType_t *pAuthTlv; + MrvlIEtypes_RsnParamSet_t *pRsnTlv; + + ENTER(); + + pBSSDesc = (BSSDescriptor_t *) pdata_buf; + pos = (u8 *) pAsso; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_ASSOCIATE); + + /* Save so we know which BSS Desc to use in the response handler */ + Adapter->pAttemptedBSSDesc = pBSSDesc; + + memcpy(pAsso->PeerStaAddr, + pBSSDesc->MacAddress, sizeof(pAsso->PeerStaAddr)); + pos += sizeof(pAsso->PeerStaAddr); + + /* set the listen interval */ + pAsso->ListenInterval = wlan_cpu_to_le16(Adapter->ListenInterval); + + pos += sizeof(pAsso->CapInfo); + pos += sizeof(pAsso->ListenInterval); + pos += sizeof(pAsso->Reserved1); + + pSsidTlv = (MrvlIEtypes_SsIdParamSet_t *) pos; + pSsidTlv->Header.Type = wlan_cpu_to_le16(TLV_TYPE_SSID); + pSsidTlv->Header.Len = pBSSDesc->Ssid.SsidLength; + memcpy(pSsidTlv->SsId, pBSSDesc->Ssid.Ssid, pSsidTlv->Header.Len); + pos += sizeof(pSsidTlv->Header) + pSsidTlv->Header.Len; + pSsidTlv->Header.Len = wlan_cpu_to_le16(pSsidTlv->Header.Len); + + pPhyTlv = (MrvlIEtypes_PhyParamSet_t *) pos; + pPhyTlv->Header.Type = wlan_cpu_to_le16(TLV_TYPE_PHY_DS); + pPhyTlv->Header.Len = sizeof(pPhyTlv->fh_ds.DsParamSet); + memcpy(&pPhyTlv->fh_ds.DsParamSet, + &pBSSDesc->PhyParamSet.DsParamSet.CurrentChan, + sizeof(pPhyTlv->fh_ds.DsParamSet)); + pos += sizeof(pPhyTlv->Header) + pPhyTlv->Header.Len; + pPhyTlv->Header.Len = wlan_cpu_to_le16(pPhyTlv->Header.Len); + + pSsTlv = (MrvlIEtypes_SsParamSet_t *) pos; + pSsTlv->Header.Type = wlan_cpu_to_le16(TLV_TYPE_CF); + pSsTlv->Header.Len = sizeof(pSsTlv->cf_ibss.CfParamSet); + pos += sizeof(pSsTlv->Header) + pSsTlv->Header.Len; + pSsTlv->Header.Len = wlan_cpu_to_le16(pSsTlv->Header.Len); + + /* Get the common rates supported between the driver and the BSS Desc */ + if (setup_rates_from_bssdesc(Adapter, pBSSDesc, rates, &ratesSize)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Setup the Rates TLV in the association command */ + pRatesTlv = (MrvlIEtypes_RatesParamSet_t *) pos; + pRatesTlv->Header.Type = wlan_cpu_to_le16(TLV_TYPE_RATES); + pRatesTlv->Header.Len = wlan_cpu_to_le16(ratesSize); + memcpy(pRatesTlv->Rates, rates, ratesSize); + pos += sizeof(pRatesTlv->Header) + ratesSize; + PRINTM(INFO, "ASSOC_CMD: Rates size = %d\n", ratesSize); + + /* Add the Authentication type to be used for Auth frames if needed */ + pAuthTlv = (MrvlIEtypes_AuthType_t *) pos; + pAuthTlv->Header.Type = wlan_cpu_to_le16(TLV_TYPE_AUTH_TYPE); + pAuthTlv->Header.Len = sizeof(pAuthTlv->AuthType); + pAuthTlv->AuthType = Adapter->SecInfo.AuthenticationMode; + pos += sizeof(pAuthTlv->Header) + pAuthTlv->Header.Len; + pAuthTlv->Header.Len = wlan_cpu_to_le16(pAuthTlv->Header.Len); + + if (!Adapter->wps.SessionEnable) { + if (Adapter->SecInfo.WPAEnabled || Adapter->SecInfo.WPA2Enabled) { + pRsnTlv = (MrvlIEtypes_RsnParamSet_t *) pos; + pRsnTlv->Header.Type = (u16) Adapter->Wpa_ie[0]; /* WPA_IE or WPA2_IE */ + pRsnTlv->Header.Type = pRsnTlv->Header.Type & 0x00FF; + pRsnTlv->Header.Type = wlan_cpu_to_le16(pRsnTlv->Header.Type); + pRsnTlv->Header.Len = (u16) Adapter->Wpa_ie[1]; + pRsnTlv->Header.Len = pRsnTlv->Header.Len & 0x00FF; + if (pRsnTlv->Header.Len <= (sizeof(Adapter->Wpa_ie) - 2)) { + memcpy(pRsnTlv->RsnIE, &Adapter->Wpa_ie[2], + pRsnTlv->Header.Len); + } else { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + HEXDUMP("ASSOC_CMD: RSN IE", (u8 *) pRsnTlv, + sizeof(pRsnTlv->Header) + pRsnTlv->Header.Len); + pos += sizeof(pRsnTlv->Header) + pRsnTlv->Header.Len; + pRsnTlv->Header.Len = wlan_cpu_to_le16(pRsnTlv->Header.Len); + } + } + + wlan_wmm_process_association_req(priv, &pos, &pBSSDesc->wmmIE); + + wlan_cmd_append_generic_ie(priv, &pos); + + wlan_cmd_append_marvell_tlv(priv, &pos); + + wlan_cmd_append_tsf_tlv(priv, &pos, pBSSDesc); + + if (wlan_create_dnld_countryinfo_11d(priv, 0)) { + PRINTM(INFO, "Dnld_countryinfo_11d failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + if (wlan_parse_dnld_countryinfo_11d(priv)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + cmd->Size = wlan_cpu_to_le16((u16) (pos - (u8 *) pAsso) + S_DS_GEN); + + /* set the Capability info at last */ + memcpy(&TmpCap, &pBSSDesc->Cap, sizeof(pAsso->CapInfo)); + TmpCap &= CAPINFO_MASK; + PRINTM(INFO, "ASSOC_CMD: TmpCap=%4X CAPINFO_MASK=%4X\n", + TmpCap, CAPINFO_MASK); + TmpCap = wlan_cpu_to_le16(TmpCap); + memcpy(&pAsso->CapInfo, &TmpCap, sizeof(pAsso->CapInfo)); + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function prepares command of ad_hoc_start. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pssid A pointer to WLAN_802_11_SSID structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_ad_hoc_start(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pssid) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_AD_HOC_START *adhs = &cmd->params.ads; + int ret = WLAN_STATUS_SUCCESS; + int cmdAppendSize = 0; + int i; + u16 TmpCap; + BSSDescriptor_t *pBSSDesc; + + ENTER(); + + if (!Adapter) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_AD_HOC_START); + + pBSSDesc = &Adapter->CurBssParams.BSSDescriptor; + Adapter->pAttemptedBSSDesc = pBSSDesc; + + /* + * Fill in the parameters for 2 data structures: + * 1. HostCmd_DS_802_11_AD_HOC_START Command + * 2. pBSSDesc + * + * Driver will fill up SSID, BSSType,IBSS param, Physical Param, + * probe delay, and Cap info. + * + * Firmware will fill up beacon period, Basic rates + * and operational rates. + */ + + memset(adhs->SSID, 0, MRVDRV_MAX_SSID_LENGTH); + + memcpy(adhs->SSID, ((WLAN_802_11_SSID *) pssid)->Ssid, + ((WLAN_802_11_SSID *) pssid)->SsidLength); + + PRINTM(INFO, "ADHOC_S_CMD: SSID = %s\n", adhs->SSID); + + memset(pBSSDesc->Ssid.Ssid, 0, MRVDRV_MAX_SSID_LENGTH); + memcpy(pBSSDesc->Ssid.Ssid, + ((WLAN_802_11_SSID *) pssid)->Ssid, + ((WLAN_802_11_SSID *) pssid)->SsidLength); + + pBSSDesc->Ssid.SsidLength = ((WLAN_802_11_SSID *) pssid)->SsidLength; + + /* set the BSS type */ + adhs->BSSType = HostCmd_BSS_TYPE_IBSS; + pBSSDesc->InfrastructureMode = Wlan802_11IBSS; + adhs->BeaconPeriod = wlan_cpu_to_le16(Adapter->BeaconPeriod); + pBSSDesc->BeaconPeriod = Adapter->BeaconPeriod; + + /* set Physical param set */ +#define DS_PARA_IE_ID 3 +#define DS_PARA_IE_LEN 1 + + adhs->PhyParamSet.DsParamSet.ElementId = DS_PARA_IE_ID; + adhs->PhyParamSet.DsParamSet.Len = DS_PARA_IE_LEN; + + if (!get_cfp_by_band_and_channel + (0, (u16) Adapter->AdhocChannel, Adapter->region_channel)) { + CHANNEL_FREQ_POWER *cfp; + cfp = + get_cfp_by_band_and_channel(0, FIRST_VALID_CHANNEL, + Adapter->region_channel); + if (cfp) + Adapter->AdhocChannel = cfp->Channel; + } + + ASSERT(Adapter->AdhocChannel); + + PRINTM(INFO, "ADHOC_S_CMD: Creating ADHOC on Channel %d\n", + Adapter->AdhocChannel); + + Adapter->CurBssParams.BSSDescriptor.Channel = Adapter->AdhocChannel; + + pBSSDesc->Channel = Adapter->AdhocChannel; + adhs->PhyParamSet.DsParamSet.CurrentChan = Adapter->AdhocChannel; + + memcpy(&pBSSDesc->PhyParamSet, + &adhs->PhyParamSet, sizeof(IEEEtypes_PhyParamSet_t)); + + pBSSDesc->NetworkTypeInUse = Wlan802_11DS; + + /* set IBSS param set */ +#define IBSS_PARA_IE_ID 6 +#define IBSS_PARA_IE_LEN 2 + + adhs->SsParamSet.IbssParamSet.ElementId = IBSS_PARA_IE_ID; + adhs->SsParamSet.IbssParamSet.Len = IBSS_PARA_IE_LEN; + adhs->SsParamSet.IbssParamSet.AtimWindow + = wlan_cpu_to_le16(Adapter->AtimWindow); + pBSSDesc->ATIMWindow = Adapter->AtimWindow; + memcpy(&pBSSDesc->SsParamSet, + &adhs->SsParamSet, sizeof(IEEEtypes_SsParamSet_t)); + + /* set Capability info */ + adhs->Cap.Ess = 0; + adhs->Cap.Ibss = 1; + pBSSDesc->Cap.Ibss = 1; + + /* set up privacy in pBSSDesc */ + if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled + || Adapter->AdhocAESEnabled) { + +#define AD_HOC_CAP_PRIVACY_ON 1 + PRINTM(INFO, "ADHOC_S_CMD: WEPStatus set, Privacy to WEP\n"); + pBSSDesc->Privacy = Wlan802_11PrivFilter8021xWEP; + adhs->Cap.Privacy = AD_HOC_CAP_PRIVACY_ON; + } else { + PRINTM(INFO, "ADHOC_S_CMD: WEPStatus NOT set, Setting " + "Privacy to ACCEPT ALL\n"); + pBSSDesc->Privacy = Wlan802_11PrivFilterAcceptAll; + } + + memset(adhs->DataRate, 0, sizeof(adhs->DataRate)); + + if (Adapter->adhoc_grate_enabled == TRUE) { + memcpy(adhs->DataRate, AdhocRates_G, + MIN(sizeof(adhs->DataRate), sizeof(AdhocRates_G))); + if (Adapter-> + CurrentPacketFilter & HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON) { + ret = + PrepareAndSendCommand(priv, HostCmd_CMD_MAC_CONTROL, 0, + HostCmd_OPTION_WAITFORRSP, 0, + &Adapter->CurrentPacketFilter); + if (ret) { + PRINTM(INFO, "ADHOC_S_CMD: G Protection config failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + } + } else { + memcpy(adhs->DataRate, AdhocRates_B, + MIN(sizeof(adhs->DataRate), sizeof(AdhocRates_B))); + } + + /* Find the last non zero */ + for (i = 0; i < sizeof(adhs->DataRate) && adhs->DataRate[i]; i++); + + Adapter->CurBssParams.NumOfRates = i; + + /* Copy the ad-hoc creating rates into Current BSS state structure */ + memcpy(&Adapter->CurBssParams.DataRates, + &adhs->DataRate, Adapter->CurBssParams.NumOfRates); + + PRINTM(INFO, "ADHOC_S_CMD: Rates=%02x %02x %02x %02x \n", + adhs->DataRate[0], adhs->DataRate[1], + adhs->DataRate[2], adhs->DataRate[3]); + + PRINTM(INFO, "ADHOC_S_CMD: AD HOC Start command is ready\n"); + + if (wlan_create_dnld_countryinfo_11d(priv, Adapter->CurBssParams.band)) { + PRINTM(INFO, "ADHOC_S_CMD: dnld_countryinfo_11d failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_AD_HOC_START) + + S_DS_GEN + cmdAppendSize); + + memcpy(&TmpCap, &adhs->Cap, sizeof(u16)); + TmpCap = wlan_cpu_to_le16(TmpCap); + memcpy(&adhs->Cap, &TmpCap, sizeof(u16)); + + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + return ret; +} + +/** + * @brief This function prepares command of ad_hoc_stop. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_ad_hoc_stop(wlan_private * priv, HostCmd_DS_COMMAND * cmd) +{ + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_AD_HOC_STOP); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_AD_HOC_STOP) + + S_DS_GEN); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of ad_hoc_join. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param pdata_buf Void cast of BSSDescriptor_t from the scan table to join + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_ad_hoc_join(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pdata_buf) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_AD_HOC_JOIN *pAdHocJoin = &cmd->params.adj; + BSSDescriptor_t *pBSSDesc = (BSSDescriptor_t *) pdata_buf; + int cmdAppendSize = 0; + int ret = WLAN_STATUS_SUCCESS; + WLAN_802_11_RATES rates; + int ratesSize; + u16 TmpCap; + u16 CurrentPacketFilter; + + ENTER(); + +#define USE_G_PROTECTION 0x02 + if (pBSSDesc->ERPFlags & USE_G_PROTECTION) { + CurrentPacketFilter = + Adapter-> + CurrentPacketFilter | HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON; + ret = + PrepareAndSendCommand(priv, HostCmd_CMD_MAC_CONTROL, 0, + HostCmd_OPTION_WAITFORRSP, 0, + &CurrentPacketFilter); + if (ret) { + PRINTM(INFO, "ADHOC_S_CMD: G Protection config failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + } + Adapter->pAttemptedBSSDesc = pBSSDesc; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_AD_HOC_JOIN); + + pAdHocJoin->BssDescriptor.BSSType = HostCmd_BSS_TYPE_IBSS; + + pAdHocJoin->BssDescriptor.BeaconPeriod + = wlan_cpu_to_le16(pBSSDesc->BeaconPeriod); + + memcpy(&pAdHocJoin->BssDescriptor.BSSID, + &pBSSDesc->MacAddress, MRVDRV_ETH_ADDR_LEN); + + memcpy(&pAdHocJoin->BssDescriptor.SSID, + &pBSSDesc->Ssid.Ssid, pBSSDesc->Ssid.SsidLength); + + memcpy(&pAdHocJoin->BssDescriptor.PhyParamSet, + &pBSSDesc->PhyParamSet, sizeof(IEEEtypes_PhyParamSet_t)); + + memcpy(&pAdHocJoin->BssDescriptor.SsParamSet, + &pBSSDesc->SsParamSet, sizeof(IEEEtypes_SsParamSet_t)); + + memcpy(&TmpCap, &pBSSDesc->Cap, sizeof(IEEEtypes_CapInfo_t)); + + TmpCap &= CAPINFO_MASK; + + PRINTM(INFO, "ADHOC_J_CMD: TmpCap=%4X CAPINFO_MASK=%4X\n", + TmpCap, CAPINFO_MASK); + memcpy(&pAdHocJoin->BssDescriptor.Cap, &TmpCap, + sizeof(IEEEtypes_CapInfo_t)); + + /* information on BSSID descriptor passed to FW */ + PRINTM(INFO, + "ADHOC_J_CMD: BSSID = %02x-%02x-%02x-%02x-%02x-%02x, SSID = %s\n", + pAdHocJoin->BssDescriptor.BSSID[0], + pAdHocJoin->BssDescriptor.BSSID[1], + pAdHocJoin->BssDescriptor.BSSID[2], + pAdHocJoin->BssDescriptor.BSSID[3], + pAdHocJoin->BssDescriptor.BSSID[4], + pAdHocJoin->BssDescriptor.BSSID[5], + pAdHocJoin->BssDescriptor.SSID); + + /* Get the common rates supported between the driver and the BSS Desc */ + if (setup_rates_from_bssdesc(Adapter, pBSSDesc, rates, &ratesSize)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Copy Data Rates from the Rates recorded in scan response */ + memset(pAdHocJoin->BssDescriptor.DataRates, 0, + sizeof(pAdHocJoin->BssDescriptor.DataRates)); + memcpy(pAdHocJoin->BssDescriptor.DataRates, rates, ratesSize); + + /* Copy the adhoc join rates into Current BSS state structure */ + Adapter->CurBssParams.NumOfRates = ratesSize; + memcpy(&Adapter->CurBssParams.DataRates, rates, ratesSize); + + /* Copy the channel information */ + Adapter->CurBssParams.BSSDescriptor.Channel = pBSSDesc->Channel; + + if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled + || Adapter->AdhocAESEnabled) { + pAdHocJoin->BssDescriptor.Cap.Privacy = AD_HOC_CAP_PRIVACY_ON; + } + + if (Adapter->PSMode == Wlan802_11PowerModeMAX_PSP) { + /* wake up first */ + WLAN_802_11_POWER_MODE LocalPSMode; + + LocalPSMode = Wlan802_11PowerModeCAM; + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_PS_MODE, + HostCmd_ACT_GEN_SET, 0, 0, &LocalPSMode); + + if (ret) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + } + + if (wlan_create_dnld_countryinfo_11d(priv, 0)) { + PRINTM(INFO, "Dnld_countryinfo_11d failed\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if (wlan_parse_dnld_countryinfo_11d(priv)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_AD_HOC_JOIN) + + S_DS_GEN + cmdAppendSize); + + memcpy(&TmpCap, &pAdHocJoin->BssDescriptor.Cap, + sizeof(IEEEtypes_CapInfo_t)); + TmpCap = wlan_cpu_to_le16(TmpCap); + + memcpy(&pAdHocJoin->BssDescriptor.Cap, + &TmpCap, sizeof(IEEEtypes_CapInfo_t)); + + done: + LEAVE(); + return ret; +} + +/** + * @brief Association firmware command response handler + * + * The response buffer for the association command has the following + * memory layout. + * + * For cases where an association response was not received (indicated + * by the CapInfo and AId field): + * + * .------------------------------------------------------------. + * | Header(4 * sizeof(u16)): Standard command response hdr | + * .------------------------------------------------------------. + * | CapInfo/Error Return(u16): | + * | 0xFFFF(-1): Internal error | + * | 0xFFFE(-2): Authentication unhandled message | + * | 0xFFFD(-3): Authentication refused | + * | 0xFFFC(-4): Timeout waiting for AP response | + * .------------------------------------------------------------. + * | StatusCode(u16): | + * | If CapInfo is -1: | + * | An internal firmware failure prevented the | + * | command from being processed. The StatusCode | + * | will be set to 1. | + * | | + * | If CapInfo is -2: | + * | An authentication frame was received but was | + * | not handled by the firmware. IEEE Status | + * | code for the failure is returned. | + * | | + * | If CapInfo is -3: | + * | An authentication frame was received and the | + * | StatusCode is the IEEE Status reported in the | + * | response. | + * | | + * | If CapInfo is -4: | + * | (1) Association response timeout | + * | (2) Authentication response timeout | + * .------------------------------------------------------------. + * | AId(u16): 0xFFFF | + * .------------------------------------------------------------. + * + * + * For cases where an association response was received, the IEEE + * standard association response frame is returned: + * + * .------------------------------------------------------------. + * | Header(4 * sizeof(u16)): Standard command response hdr | + * .------------------------------------------------------------. + * | CapInfo(u16): IEEE Capability | + * .------------------------------------------------------------. + * | StatusCode(u16): IEEE Status Code | + * .------------------------------------------------------------. + * | AId(u16): IEEE Association ID | + * .------------------------------------------------------------. + * | IEEE IEs(variable): Any received IEs comprising the | + * | remaining portion of a received | + * | association response frame. | + * .------------------------------------------------------------. + * + * For simplistic handling, the StatusCode field can be used to determine + * an association success (0) or failure (non-zero). + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11_associate(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + IEEEtypes_AssocRsp_t *pAssocRsp; + BSSDescriptor_t *pBSSDesc; + WLAN_802_11_RATES rates; + int ratesSize; + + ENTER(); + + pAssocRsp = (IEEEtypes_AssocRsp_t *) & resp->params; + + HEXDUMP("ASSOC_RESP:", (void *) &resp->params, + wlan_le16_to_cpu(resp->Size) - S_DS_GEN); + + Adapter->assocRspSize = MIN(wlan_le16_to_cpu(resp->Size) - S_DS_GEN, + sizeof(Adapter->assocRspBuffer)); + + memcpy(Adapter->assocRspBuffer, &resp->params, Adapter->assocRspSize); + + if (pAssocRsp->StatusCode) { + priv->adapter->dbg.num_cmd_assoc_failure++; + + PRINTM(CMND, "ASSOC_RESP: Association Failed, " + "status code = %d, error = %d\n", + pAssocRsp->StatusCode, *(short *) &pAssocRsp->Capability); + ret = WLAN_STATUS_FAILURE; + + goto done; + } + + /* Send a Media Connected event, according to the Spec */ + Adapter->MediaConnectStatus = WlanMediaStateConnected; + + /* Set the attempted BSSID Index to current */ + pBSSDesc = Adapter->pAttemptedBSSDesc; + + PRINTM(INFO, "ASSOC_RESP: %s\n", pBSSDesc->Ssid.Ssid); + + /* Make a copy of current BSSID descriptor */ + memcpy(&Adapter->CurBssParams.BSSDescriptor, + pBSSDesc, sizeof(BSSDescriptor_t)); + + /* update CurBssParams */ + Adapter->CurBssParams.BSSDescriptor.Channel + = pBSSDesc->PhyParamSet.DsParamSet.CurrentChan; + + if (setup_rates_from_bssdesc(Adapter, pBSSDesc, rates, &ratesSize)) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + /* Copy the infra. association rates into Current BSS state structure */ + Adapter->CurBssParams.NumOfRates = ratesSize; + memcpy(&Adapter->CurBssParams.DataRates, rates, ratesSize); + + /* Adjust the timestamps in the scan table to be relative to the newly + * associated AP's TSF + */ + wlan_scan_update_tsf_timestamps(priv, pBSSDesc); + + if (pBSSDesc->wmmIE.VendHdr.ElementId == WMM_IE) { + Adapter->CurBssParams.wmm_enabled = TRUE; + } else { + Adapter->CurBssParams.wmm_enabled = FALSE; + } + + if (Adapter->wmm.required && Adapter->CurBssParams.wmm_enabled) { + Adapter->wmm.enabled = TRUE; + } else { + Adapter->wmm.enabled = FALSE; + } + + Adapter->CurBssParams.wmm_uapsd_enabled = FALSE; + + if (Adapter->wmm.enabled == TRUE) { + Adapter->CurBssParams.wmm_uapsd_enabled + = pBSSDesc->wmmIE.QoSInfo.QosUAPSD; + } + + PRINTM(INFO, "ASSOC_RESP: CurrentPacketFilter is %x\n", + Adapter->CurrentPacketFilter); + + if (Adapter->SecInfo.WPAEnabled || Adapter->SecInfo.WPA2Enabled) + Adapter->IsGTK_SET = FALSE; + + Adapter->SNR[TYPE_RXPD][TYPE_AVG] = 0; + Adapter->NF[TYPE_RXPD][TYPE_AVG] = 0; + + memset(Adapter->rawSNR, 0x00, sizeof(Adapter->rawSNR)); + memset(Adapter->rawNF, 0x00, sizeof(Adapter->rawNF)); + Adapter->nextSNRNF = 0; + Adapter->numSNRNF = 0; + + priv->adapter->dbg.num_cmd_assoc_success++; + + PRINTM(INFO, "ASSOC_RESP: Associated \n"); + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function handles the command response of disassociate + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11_disassociate(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + ENTER(); + + priv->adapter->dbg.num_cmd_deauth++; + MacEventDisconnected(priv); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the command response of ad_hoc_start and + * ad_hoc_join + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11_ad_hoc(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + u16 Command = resp->Command; + u16 Result = resp->Result; + HostCmd_DS_802_11_AD_HOC_RESULT *pAdHocResult; + union iwreq_data wrqu; + BSSDescriptor_t *pBSSDesc; + + ENTER(); + + pAdHocResult = &resp->params.result; + + pBSSDesc = Adapter->pAttemptedBSSDesc; + + /* + * Join result code 0 --> SUCCESS + */ + if (Result) { + PRINTM(INFO, "ADHOC_RESP Failed\n"); + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + MacEventDisconnected(priv); + } + + memset(&Adapter->CurBssParams.BSSDescriptor, + 0x00, sizeof(Adapter->CurBssParams.BSSDescriptor)); + + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + /* Send a Media Connected event, according to the Spec */ + Adapter->MediaConnectStatus = WlanMediaStateConnected; + + if (Command == HostCmd_RET_802_11_AD_HOC_START) { + PRINTM(INFO, "ADHOC_S_RESP %s\n", pBSSDesc->Ssid.Ssid); + + /* Update the created network descriptor with the new BSSID */ + memcpy(pBSSDesc->MacAddress, + pAdHocResult->BSSID, MRVDRV_ETH_ADDR_LEN); + } else { + /* + * Now the join cmd should be successful + * If BSSID has changed use SSID to compare instead of BSSID + */ + PRINTM(INFO, "ADHOC_J_RESP %s\n", pBSSDesc->Ssid.Ssid); + + /* Make a copy of current BSSID descriptor, only needed for join since + * the current descriptor is already being used for adhoc start + */ + memcpy(&Adapter->CurBssParams.BSSDescriptor, + pBSSDesc, sizeof(BSSDescriptor_t)); + } + + memset(&wrqu, 0, sizeof(wrqu)); + memcpy(wrqu.ap_addr.sa_data, + &Adapter->CurBssParams.BSSDescriptor.MacAddress, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWAP, &wrqu, NULL); + + PRINTM(INFO, "ADHOC_RESP: Channel = %d\n", Adapter->AdhocChannel); + PRINTM(INFO, "ADHOC_RESP: BSSID = %02x:%02x:%02x:%02x:%02x:%02x\n", + Adapter->CurBssParams.BSSDescriptor.MacAddress[0], + Adapter->CurBssParams.BSSDescriptor.MacAddress[1], + Adapter->CurBssParams.BSSDescriptor.MacAddress[2], + Adapter->CurBssParams.BSSDescriptor.MacAddress[3], + Adapter->CurBssParams.BSSDescriptor.MacAddress[4], + Adapter->CurBssParams.BSSDescriptor.MacAddress[5]); + LEAVE(); + return ret; +} + +/** + * @brief This function handles the command response of ad_hoc_stop + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11_ad_hoc_stop(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + ENTER(); + + MacEventDisconnected(priv); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +#ifdef REASSOCIATION +/** + * @brief This function handles re-association. it is triggered + * by re-assoc timer. + * + * @param data A pointer to wlan_thread structure + * @return WLAN_STATUS_SUCCESS + */ +int +wlan_reassociation_thread(void *data) +{ + wlan_thread *thread = data; + wlan_private *priv = thread->priv; + wlan_adapter *Adapter = priv->adapter; + wait_queue_t wait; + int i; + int ret = WLAN_STATUS_SUCCESS; + + OS_INTERRUPT_SAVE_AREA; + + ENTER(); + + wlan_activate_thread(thread); + init_waitqueue_entry(&wait, current); + + current->flags |= PF_NOFREEZE; + + for (;;) { + add_wait_queue(&thread->waitQ, &wait); + OS_SET_THREAD_STATE(TASK_INTERRUPTIBLE); + + PRINTM(INFO, "Reassoc: Thread sleeping...\n"); + + schedule(); + + OS_SET_THREAD_STATE(TASK_RUNNING); + remove_wait_queue(&thread->waitQ, &wait); + + if (Adapter->SurpriseRemoved) { + break; + } + + if (kthread_should_stop()) { + break; + } + + PRINTM(INFO, "Reassoc: Thread waking up...\n"); + + if (Adapter->InfrastructureMode != Wlan802_11Infrastructure || + Adapter->HardwareStatus != WlanHardwareStatusReady) { + PRINTM(MSG, "Reassoc: mode or hardware status is not correct\n"); + continue; + } + + /* The semaphore is used to avoid reassociation thread and + wlan_set_scan/wlan_set_essid interrupting each other. + Reassociation should be disabled completely by application if + wlan_set_user_scan_ioctl/wlan_set_wap is used. + */ + if (OS_ACQ_SEMAPHORE_BLOCK(&Adapter->ReassocSem)) { + PRINTM(ERROR, "Acquire semaphore error, reassociation thread\n"); + goto settimer; + } + + if (Adapter->MediaConnectStatus != WlanMediaStateDisconnected) { + OS_REL_SEMAPHORE(&Adapter->ReassocSem); + PRINTM(MSG, "Reassoc: Adapter->MediaConnectStatus is wrong\n"); + continue; + } + + PRINTM(INFO, "Reassoc: Required ESSID: %s\n", + Adapter->PreviousSSID.Ssid); + + PRINTM(INFO, "Reassoc: Performing Active Scan @ %lu\n", + os_time_get()); + SendSpecificSSIDScan(priv, &Adapter->PreviousSSID); + + i = FindSSIDInList(Adapter, + &Adapter->PreviousSSID, + Adapter->PreviousBSSID, + Adapter->InfrastructureMode); + + if (i < 0) { + /* If the SSID could not be found, try just the SSID */ + i = FindSSIDInList(Adapter, + &Adapter->PreviousSSID, + NULL, Adapter->InfrastructureMode); + } + + if (i >= 0) { + if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SET_WEP, + 0, HostCmd_OPTION_WAITFORRSP, + OID_802_11_ADD_WEP, NULL); + if (ret) + PRINTM(INFO, "Ressoc: Fail to set WEP KEY\n"); + } + wlan_associate(priv, &Adapter->ScanTable[i]); + } + + OS_REL_SEMAPHORE(&Adapter->ReassocSem); + + settimer: + if (Adapter->MediaConnectStatus == WlanMediaStateDisconnected) { + PRINTM(INFO, "Reassoc: No AP found or assoc failed." + "Restarting re-assoc Timer @ %lu\n", os_time_get()); + + Adapter->ReassocTimerIsSet = TRUE; + ModTimer(&Adapter->MrvDrvTimer, MRVDRV_TIMER_10S); + } + } + + wlan_deactivate_thread(thread); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function triggers re-association by waking up + * re-assoc thread. + * + * @param FunctionContext A pointer to FunctionContext + * @return n/a + */ +void +MrvDrvReassocTimerFunction(void *FunctionContext) +{ + wlan_private *priv = (wlan_private *) FunctionContext; + wlan_adapter *Adapter = priv->adapter; + OS_INTERRUPT_SAVE_AREA; + + ENTER(); + + PRINTM(INFO, "MrvDrvReassocTimer fired.\n"); + Adapter->ReassocTimerIsSet = FALSE; + if (Adapter->PSState != PS_STATE_FULL_POWER) { + /* wait until Exit_PS command returns */ + Adapter->ReassocTimerIsSet = TRUE; + ModTimer(&Adapter->MrvDrvTimer, MRVDRV_TIMER_1S); + PRINTM(INFO, "MrvDrvTimerFunction(PSState=%d) waiting" + "for Exit_PS done\n", Adapter->PSState); + LEAVE(); + return; + } + + PRINTM(INFO, "Waking Up the Reassoc Thread\n"); + + wake_up_interruptible(&priv->ReassocThread.waitQ); + + LEAVE(); + return; +} +#endif /* REASSOCIATION */ + +int +sendADHOCBSSIDQuery(wlan_private * priv) +{ + return PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_IBSS_COALESCING_STATUS, + HostCmd_ACT_GET, 0, 0, NULL); +} diff --git a/drivers/net/wireless/marvell8686/wlan_join.h b/drivers/net/wireless/marvell8686/wlan_join.h new file mode 100644 index 0000000..ffb944e --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_join.h @@ -0,0 +1,84 @@ +/** @file wlan_join.h + * + * @brief Interface for the wlan infrastructure and adhoc join routines + * + * Driver interface functions and type declarations for the join module + * implemented in wlan_join.c. Process all start/join requests for + * both adhoc and infrastructure networks + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + * + * @sa wlan_join.c + */ +/************************************************************* +Change Log: + 01/11/06: Initial revision. Match new scan code, relocate related functions + +************************************************************/ + +#ifndef _WLAN_JOIN_H +#define _WLAN_JOIN_H + +//! Size of buffer allocated to store the association response from firmware +#define MRVDRV_ASSOC_RSP_BUF_SIZE 500 + +//! Size of buffer allocated to store IEs passed to firmware in the assoc req +#define MRVDRV_GENIE_BUF_SIZE 256 + +//! Size of buffer allocated to store TLVs passed to firmware in the assoc req +#define MRVDRV_ASSOC_TLV_BUF_SIZE 256 + +extern int wlan_cmd_802_11_authenticate(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + void *pdata_buf); +extern int wlan_cmd_802_11_ad_hoc_join(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + void *pdata_buf); +extern int wlan_cmd_802_11_ad_hoc_stop(wlan_private * priv, + HostCmd_DS_COMMAND * cmd); +extern int wlan_cmd_802_11_ad_hoc_start(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + void *pssid); +extern int wlan_cmd_802_11_deauthenticate(wlan_private * priv, + HostCmd_DS_COMMAND * cmd); +extern int wlan_cmd_802_11_associate(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + void *pdata_buf); +extern int wlan_ret_802_11_authenticate(wlan_private * priv, + HostCmd_DS_COMMAND * resp); +extern int wlan_ret_802_11_ad_hoc(wlan_private * priv, + HostCmd_DS_COMMAND * resp); +extern int wlan_ret_802_11_ad_hoc_stop(wlan_private * priv, + HostCmd_DS_COMMAND * resp); +extern int wlan_ret_802_11_disassociate(wlan_private * priv, + HostCmd_DS_COMMAND * resp); +extern int wlan_ret_802_11_associate(wlan_private * priv, + HostCmd_DS_COMMAND * resp); + +extern int wlan_associate(wlan_private * priv, BSSDescriptor_t * pBSSDesc); +extern int wlan_associate_to_table_idx(wlan_private * priv, int tableIdx); + +extern int wlanidle_on(wlan_private * priv); +extern int wlanidle_off(wlan_private * priv); + +extern int wlan_do_adhocstop_ioctl(wlan_private * priv); +extern int wlan_reassociation_thread(void *data); + +extern int StartAdhocNetwork(wlan_private * priv, + WLAN_802_11_SSID * AdhocSSID); +extern int JoinAdhocNetwork(wlan_private * priv, BSSDescriptor_t * pBSSDesc); +extern int StopAdhocNetwork(wlan_private * priv); +extern int SendDeauthentication(wlan_private * priv); + +extern int wlan_do_adhocstop_ioctl(wlan_private * priv); +extern int wlan_get_assoc_rsp_ioctl(wlan_private * priv, struct iwreq *wrq); +extern int wlan_set_mrvl_tlv_ioctl(wlan_private * priv, struct iwreq *wrq); + +#ifdef __KERNEL__ +extern int wlan_set_wap(struct net_device *dev, struct iw_request_info *info, + struct sockaddr *awrq, char *extra); +#endif + +extern int sendADHOCBSSIDQuery(wlan_private * priv); + +#endif diff --git a/drivers/net/wireless/marvell8686/wlan_main.c b/drivers/net/wireless/marvell8686/wlan_main.c new file mode 100644 index 0000000..c938cee --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_main.c @@ -0,0 +1,1359 @@ +/** @file wlan_main.c + * + * @brief This file contains the major functions in WLAN + * driver. It includes init, exit, open, close and main + * thread etc.. + * + */ +/** + * @mainpage M-WLAN Linux Driver + * + * @section overview_sec Overview + * + * The M-WLAN is a Linux reference driver for Marvell + * 802.11 (a/b/g) WLAN chipset. + * + * @section copyright_sec Copyright + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + * + */ +/******************************************************** +Change log: + 09/30/05: Add Doxygen format comments + 12/09/05: Add TX_QUEUE support + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join functions. + 01/12/06: Add TxLockFlag for UAPSD power save mode + and Proprietary Periodic sleep support +********************************************************/ + +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ + +#ifdef ENABLE_PM +#define WLAN_PM_DRV_NAME "wlan_pm_drv" + +static int wlan_pm_suspend(struct device *pmdev, u32 state, u32 level); +static int wlan_pm_resume(struct device *pmdev, u32 level); +static void wlan_pm_release(struct device *pmdev); + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct device_driver wlan_pm_driver = { + .name = WLAN_PM_DRV_NAME, + .bus = &platform_bus_type, + .suspend = wlan_pm_suspend, + .resume = wlan_pm_resume, +}; + +/*! Device Definition for WLAN */ +static struct platform_device wlan_pm_platform_device = { + .name = WLAN_PM_DRV_NAME, + .id = 0, + .dev.release = wlan_pm_release, +}; +#endif + +spinlock_t driver_lock = SPIN_LOCK_UNLOCKED; +ulong driver_flags; + +#define WLAN_TX_PWR_DEFAULT 20 /*100mW */ +#define WLAN_TX_PWR_US_DEFAULT 20 /*100mW */ +#define WLAN_TX_PWR_JP_DEFAULT 16 /*50mW */ +#define WLAN_TX_PWR_FR_100MW 20 /*100mW */ +#define WLAN_TX_PWR_FR_10MW 10 /*10mW */ +#define WLAN_TX_PWR_EMEA_DEFAULT 20 /*100mW */ + +/* Format { Channel, Frequency (MHz), MaxTxPower } */ +/* Band: 'B/G', Region: USA FCC/Canada IC */ +static CHANNEL_FREQ_POWER channel_freq_power_US_BG[] = { + {1, 2412, WLAN_TX_PWR_US_DEFAULT}, + {2, 2417, WLAN_TX_PWR_US_DEFAULT}, + {3, 2422, WLAN_TX_PWR_US_DEFAULT}, + {4, 2427, WLAN_TX_PWR_US_DEFAULT}, + {5, 2432, WLAN_TX_PWR_US_DEFAULT}, + {6, 2437, WLAN_TX_PWR_US_DEFAULT}, + {7, 2442, WLAN_TX_PWR_US_DEFAULT}, + {8, 2447, WLAN_TX_PWR_US_DEFAULT}, + {9, 2452, WLAN_TX_PWR_US_DEFAULT}, + {10, 2457, WLAN_TX_PWR_US_DEFAULT}, + {11, 2462, WLAN_TX_PWR_US_DEFAULT} +}; + +/* Band: 'B/G', Region: Europe ETSI */ +static CHANNEL_FREQ_POWER channel_freq_power_EU_BG[] = { + {1, 2412, WLAN_TX_PWR_EMEA_DEFAULT}, + {2, 2417, WLAN_TX_PWR_EMEA_DEFAULT}, + {3, 2422, WLAN_TX_PWR_EMEA_DEFAULT}, + {4, 2427, WLAN_TX_PWR_EMEA_DEFAULT}, + {5, 2432, WLAN_TX_PWR_EMEA_DEFAULT}, + {6, 2437, WLAN_TX_PWR_EMEA_DEFAULT}, + {7, 2442, WLAN_TX_PWR_EMEA_DEFAULT}, + {8, 2447, WLAN_TX_PWR_EMEA_DEFAULT}, + {9, 2452, WLAN_TX_PWR_EMEA_DEFAULT}, + {10, 2457, WLAN_TX_PWR_EMEA_DEFAULT}, + {11, 2462, WLAN_TX_PWR_EMEA_DEFAULT}, + {12, 2467, WLAN_TX_PWR_EMEA_DEFAULT}, + {13, 2472, WLAN_TX_PWR_EMEA_DEFAULT} +}; + +/* Band: 'B/G', Region: France */ +static CHANNEL_FREQ_POWER channel_freq_power_FR_BG[] = { + {1, 2412, WLAN_TX_PWR_FR_100MW}, + {2, 2417, WLAN_TX_PWR_FR_100MW}, + {3, 2422, WLAN_TX_PWR_FR_100MW}, + {4, 2427, WLAN_TX_PWR_FR_100MW}, + {5, 2432, WLAN_TX_PWR_FR_100MW}, + {6, 2437, WLAN_TX_PWR_FR_100MW}, + {7, 2442, WLAN_TX_PWR_FR_100MW}, + {8, 2447, WLAN_TX_PWR_FR_100MW}, + {9, 2452, WLAN_TX_PWR_FR_100MW}, + {10, 2457, WLAN_TX_PWR_FR_10MW}, + {11, 2462, WLAN_TX_PWR_FR_10MW}, + {12, 2467, WLAN_TX_PWR_FR_10MW}, + {13, 2472, WLAN_TX_PWR_FR_10MW} +}; + +/* Band: 'B/G', Region: Japan */ +static CHANNEL_FREQ_POWER channel_freq_power_JPN41_BG[] = { + {1, 2412, WLAN_TX_PWR_JP_DEFAULT}, + {2, 2417, WLAN_TX_PWR_JP_DEFAULT}, + {3, 2422, WLAN_TX_PWR_JP_DEFAULT}, + {4, 2427, WLAN_TX_PWR_JP_DEFAULT}, + {5, 2432, WLAN_TX_PWR_JP_DEFAULT}, + {6, 2437, WLAN_TX_PWR_JP_DEFAULT}, + {7, 2442, WLAN_TX_PWR_JP_DEFAULT}, + {8, 2447, WLAN_TX_PWR_JP_DEFAULT}, + {9, 2452, WLAN_TX_PWR_JP_DEFAULT}, + {10, 2457, WLAN_TX_PWR_JP_DEFAULT}, + {11, 2462, WLAN_TX_PWR_JP_DEFAULT}, + {12, 2467, WLAN_TX_PWR_JP_DEFAULT}, + {13, 2472, WLAN_TX_PWR_JP_DEFAULT} +}; + +/* Band: 'B/G', Region: Japan */ +static CHANNEL_FREQ_POWER channel_freq_power_JPN40_BG[] = { + {14, 2484, WLAN_TX_PWR_JP_DEFAULT} +}; + +/******************************************************** + Global Variables +********************************************************/ + +/** + * the structure for channel, frequency and power + */ +typedef struct _region_cfp_table +{ + u8 region; + CHANNEL_FREQ_POWER *cfp_BG; + int cfp_no_BG; +} region_cfp_table_t; + +/** + * the structure for the mapping between region and CFP + */ +region_cfp_table_t region_cfp_table[] = { + {0x10, /*US FCC */ + channel_freq_power_US_BG, + sizeof(channel_freq_power_US_BG) / sizeof(CHANNEL_FREQ_POWER), + } + , + {0x20, /*CANADA IC */ + channel_freq_power_US_BG, + sizeof(channel_freq_power_US_BG) / sizeof(CHANNEL_FREQ_POWER), + } + , + {0x30, /*EU*/ channel_freq_power_EU_BG, + sizeof(channel_freq_power_EU_BG) / sizeof(CHANNEL_FREQ_POWER), + } + , + {0x32, /*FRANCE*/ channel_freq_power_FR_BG, + sizeof(channel_freq_power_FR_BG) / sizeof(CHANNEL_FREQ_POWER), + } + , + {0x40, /*JAPAN*/ channel_freq_power_JPN40_BG, + sizeof(channel_freq_power_JPN40_BG) / sizeof(CHANNEL_FREQ_POWER), + } + , + {0x41, /*JAPAN*/ channel_freq_power_JPN41_BG, + sizeof(channel_freq_power_JPN41_BG) / sizeof(CHANNEL_FREQ_POWER), + } + , +/*Add new region here */ +}; + +/** + * the rates supported by the card + */ +u8 WlanDataRates[WLAN_SUPPORTED_RATES] = + { 0x02, 0x04, 0x0B, 0x16, 0x00, 0x0C, 0x12, + 0x18, 0x24, 0x30, 0x48, 0x60, 0x6C, 0x00 +}; + +/** + * the rates supported + */ +u8 SupportedRates[G_SUPPORTED_RATES] = + { 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, + 0x60, 0x6c, 0 +}; + +/** + * the rates supported for ad-hoc G mode + */ +u8 AdhocRates_G[G_SUPPORTED_RATES] = + { 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c, +0 }; + +/** + * the rates supported for ad-hoc B mode + */ +u8 AdhocRates_B[4] = { 0x82, 0x84, 0x8b, 0x96 }; + +/** + * the global variable of a pointer to wlan_private + * structure variable + */ +wlan_private *wlanpriv = NULL; + +u32 DSFreqList[15] = { + 0, 2412000, 2417000, 2422000, 2427000, 2432000, 2437000, 2442000, + 2447000, 2452000, 2457000, 2462000, 2467000, 2472000, 2484000 +}; + +/** + * the table to keep region code + */ +u16 RegionCodeToIndex[MRVDRV_MAX_REGION_CODE] = + { 0x10, 0x20, 0x30, 0x32, 0x40, 0x41 }; + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function opens the network device + * + * @param dev A pointer to net_device structure + * @return WLAN_STATUS_SUCCESS + */ +static int +wlan_open(struct net_device *dev) +{ + wlan_private *priv = (wlan_private *) dev->priv; + wlan_adapter *adapter = priv->adapter; + + ENTER(); + + MODULE_GET; + + priv->open = TRUE; + + if ((adapter->MediaConnectStatus == WlanMediaStateConnected) + && (adapter->InfrastructureMode != Wlan802_11IBSS + || adapter->AdhocLinkSensed == TRUE)) + os_carrier_on(priv); + else + os_carrier_off(priv); + + os_start_queue(priv); + wmm_start_queue(priv); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function closes the network device + * + * @param dev A pointer to net_device structure + * @return WLAN_STATUS_SUCCESS + */ +static int +wlan_close(struct net_device *dev) +{ + wlan_private *priv = dev->priv; + + ENTER(); + + if (priv->adapter) + wlan_clean_txrx(priv); + + MODULE_PUT; + + priv->open = FALSE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +#ifdef ENABLE_PM +/** + * @brief + * This function is called to put the SDHC in a low power state. Refer to the + * document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param dev the device structure used to give information on which SDHC + * to suspend + * @param state the power state the device is entering + * @param level the stage in device suspension process that we want the + * device to be put in + * + * @return 0 : go to sleep mode + * -1 : do not accept to go to sleep mode. + */ +static int +wlan_pm_suspend(struct device *pmdev, u32 state, u32 level) +{ + wlan_private *priv = wlanpriv; + wlan_adapter *Adapter = priv->adapter; + struct net_device *dev = priv->wlan_dev.netdev; + + switch (level) { + + case SUSPEND_DISABLE: + PRINTM(INFO, "WIFI_PM_SUSPEND_CALLBACK: enter SUSPEND_DISABLE.\n"); + + /* in associated mode : check that chipset is in IEEE PS and well configured to wake up the host if needed */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + if ((Adapter->PSState != PS_STATE_SLEEP) + || (!Adapter->bWakeupDevRequired) + || (Adapter->WakeupTries != 0)) { + PRINTM(INFO, "WIFI_PM_SUSPEND_CALLBACK: can't enter sleep mode because \ + PSstate=%d, bWakeupDevRequired=%d, wakeupTries=%d\n", + Adapter->PSState, Adapter->bWakeupDevRequired, Adapter->WakeupTries); + return WLAN_STATUS_FAILURE; + } + + else { + + /* + * Detach the network interface + * if the network is running + */ + if (netif_running(dev)) { + netif_device_detach(dev); + PRINTM(INFO, "netif_device_detach().\n"); + } + /* Stop bus clock */ + sbi_set_bus_clock(priv, FALSE); + } + } + + /* in non associated mode : check that chipset is in Deepsleep mode */ + else { + if (Adapter->IsDeepSleep == FALSE) { + PRINTM(INFO, + "WIFI_PM_SUSPEND_CALLBACK: No allowed to enter sleep while in FW in full power.\n"); + return WLAN_STATUS_FAILURE; + } + /* + * Detach the network interface + * if the network is running + */ + if (netif_running(dev)) { + netif_device_detach(dev); + } + } + break; + + case SUSPEND_SAVE_STATE: + + PRINTM(INFO, "WIFI_PM_SUSPEND_CALLBACK: enter SUSPEND_SAVE_STATE.\n"); + /* Save bus state to restore it when waking up */ + sbi_suspend(priv); + + break; + + case SUSPEND_POWER_DOWN: + + PRINTM(INFO, "WIFI_PM_SUSPEND_CALLBACK: enter SUSPEND_POWER_DOWN.\n"); + /* nothing to do */ + break; + + default: + + break; + + } + + return WLAN_STATUS_SUCCESS; + +} + +/** + * @brief + * This function is called to bring the SDHC back from a low power state. Refer + * to the document driver-model/driver.txt in the kernel source tree for more + * information. + * + * @param dev the device structure used to give information on which SDHC + * to resume + * @param level the stage in device resumption process that we want the + * device to be put in + * + * @return The function always returns 0. + */ +static int +wlan_pm_resume(struct device *pmdev, u32 level) +{ + wlan_private *priv = wlanpriv; + wlan_adapter *Adapter = priv->adapter; + struct net_device *dev = priv->wlan_dev.netdev; + + switch (level) { + + case RESUME_POWER_ON: + + PRINTM(INFO, "WIFI_PM_RESUME_CALLBACK: enter RESUME_POWER_ON.\n"); + /* nothing to do */ + break; + + case RESUME_RESTORE_STATE: + + PRINTM(INFO, + "WIFI_PM_RESUME_CALLBACK: enter RESUME_RESTORE_STATE.\n"); + /* Restore bus state */ + sbi_resume(priv); + break; + + case RESUME_ENABLE: + + PRINTM(INFO, "WIFI_PM_RESUME_CALLBACK: enter RESUME_ENABLE.\n"); + + /* in associated mode */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + + if (Adapter->bWakeupDevRequired == FALSE) { + /* could never happen */ + PRINTM(MSG, "WIFI_PM_RESUME_CALLBACK: serious error.\n"); + } else { + /* + * Start bus clock + */ + sbi_set_bus_clock(priv, TRUE); + /* + * Attach the network interface + * if the network is running + */ + if (netif_running(dev)) { + netif_device_attach(dev); + PRINTM(INFO, "WIFI_PM : after netif_device_attach().\n"); + } + PRINTM(INFO, + "WIFI_PM : After netif attach, in associated mode.\n"); + } + } + + /* in non associated mode */ + else { + if (netif_running(dev)) { + netif_device_attach(dev); + } + + PRINTM(INFO, + "WIFI_PM : after netif attach, in NON associated mode.\n"); + } + break; + + default: + break; + + } + return WLAN_STATUS_SUCCESS; + +} + +/** + * @brief + * Dummy function to be compliant with Linux Power Management framework. + * + * @param pmdev the device structure used to give information on which SDHC + * to use + * + * @return None. + */ +static void +wlan_pm_release(struct device *pmdev) +{ + PRINTM(INFO, "WIFI_PM_DRIVER : Into pm_device release function\n"); + return; +} + +#endif /* ENABLE_PM */ + +/** + * @brief This function handles packet transmission + * + * @param skb A pointer to sk_buff structure + * @param dev A pointer to net_device structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + int ret; + wlan_private *priv = dev->priv; + + ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + PRINTM(DATA, "Data <= kernel\n"); + + if (wlan_tx_packet(priv, skb)) { + /* Transmit failed */ + ret = WLAN_STATUS_FAILURE; + } + + LEAVE(); + return ret; +} + +/** + * @brief This function handles the timeout of packet + * transmission + * + * @param dev A pointer to net_device structure + * @return n/a + */ +static void +wlan_tx_timeout(struct net_device *dev) +{ + wlan_private *priv = (wlan_private *) dev->priv; + + ENTER(); + + PRINTM(DATA, "Tx timeout\n"); + UpdateTransStart(dev); + + priv->adapter->dbg.num_tx_timeout++; + + priv->adapter->IntCounter++; + wake_up_interruptible(&priv->MainThread.waitQ); + + LEAVE(); +} + +/** + * @brief This function returns the network statistics + * + * @param dev A pointer to wlan_private structure + * @return A pointer to net_device_stats structure + */ +static struct net_device_stats * +wlan_get_stats(struct net_device *dev) +{ + wlan_private *priv = (wlan_private *) dev->priv; + + return &priv->stats; +} + +/** + * @brief This function sets the MAC address to firmware. + * + * @param priv A pointer to wlan_private structure + * @param pRxPD A pointer to RxPD structure of received packet + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +wlan_set_mac_address(struct net_device *dev, void *addr) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = (wlan_private *) dev->priv; + wlan_adapter *Adapter = priv->adapter; + struct sockaddr *pHwAddr = (struct sockaddr *) addr; + + ENTER(); + + memset(Adapter->CurrentAddr, 0, MRVDRV_ETH_ADDR_LEN); + + /* dev->dev_addr is 8 bytes */ + HEXDUMP("dev->dev_addr:", dev->dev_addr, ETH_ALEN); + + HEXDUMP("addr:", pHwAddr->sa_data, ETH_ALEN); + memcpy(Adapter->CurrentAddr, pHwAddr->sa_data, ETH_ALEN); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_MAC_ADDRESS, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + PRINTM(INFO, "set mac address failed.\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + HEXDUMP("Adapter->MacAddr:", Adapter->CurrentAddr, ETH_ALEN); + memcpy(dev->dev_addr, Adapter->CurrentAddr, ETH_ALEN); + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function sets multicast addresses to firmware + * + * @param dev A pointer to net_device structure + * @return n/a + */ +static void +wlan_set_multicast_list(struct net_device *dev) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int OldPacketFilter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + OldPacketFilter = Adapter->CurrentPacketFilter; + + if (dev->flags & IFF_PROMISC) { + PRINTM(INFO, "Enable Promiscuous mode\n"); + Adapter->CurrentPacketFilter |= HostCmd_ACT_MAC_PROMISCUOUS_ENABLE; + Adapter->CurrentPacketFilter &= ~HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + /* Multicast */ + Adapter->CurrentPacketFilter &= ~HostCmd_ACT_MAC_PROMISCUOUS_ENABLE; + + if (dev->flags & IFF_ALLMULTI || dev->mc_count > + MRVDRV_MAX_MULTICAST_LIST_SIZE) { + PRINTM(INFO, "Enabling All Multicast!\n"); + Adapter->CurrentPacketFilter |= + HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE; + } else { + Adapter->CurrentPacketFilter &= + ~HostCmd_ACT_MAC_ALL_MULTICAST_ENABLE; + + if (!dev->mc_count) { + PRINTM(INFO, "No multicast addresses - " + "disabling multicast!\n"); + + } else { + int i; + + Adapter->NumOfMulticastMACAddr = + CopyMulticastAddrs(Adapter, dev); + + PRINTM(INFO, "Multicast addresses: %d\n", dev->mc_count); + + for (i = 0; i < dev->mc_count; i++) { + PRINTM(INFO, "Multicast address %d:" + "%x %x %x %x %x %x\n", i, + Adapter->MulticastList[i][0], + Adapter->MulticastList[i][1], + Adapter->MulticastList[i][2], + Adapter->MulticastList[i][3], + Adapter->MulticastList[i][4], + Adapter->MulticastList[i][5]); + } + /* set multicast addresses to firmware */ + ret = + PrepareAndSendCommand(priv, HostCmd_CMD_MAC_MULTICAST_ADR, + HostCmd_ACT_GEN_SET, 0, 0, NULL); + } + } + } + + if (Adapter->CurrentPacketFilter != OldPacketFilter) { + if (ret == WLAN_STATUS_SUCCESS) + PrepareAndSendCommand(priv, + HostCmd_CMD_MAC_CONTROL, + 0, 0, 0, &Adapter->CurrentPacketFilter); + } + + LEAVE(); +} + +/** + * @brief This function pops rx_skb from the rx queue. + * + * @param RxSkbQ A pointer to rx_skb queue + * @return A pointer to skb + */ +static struct sk_buff * +wlan_pop_rx_skb(struct sk_buff *RxSkbQ) +{ + struct sk_buff *skb_data = NULL; + + if (!list_empty((struct list_head *) RxSkbQ)) { + skb_data = RxSkbQ->next; + list_del((struct list_head *) RxSkbQ->next); + } + + return skb_data; +} + +/** + * @brief This function hanldes the major job in WLAN driver. + * it handles the event generated by firmware, rx data received + * from firmware and tx data sent from kernel. + * + * @param data A pointer to wlan_thread structure + * @return WLAN_STATUS_SUCCESS + */ +static int +wlan_service_main_thread(void *data) +{ + wlan_thread *thread = data; + wlan_private *priv = thread->priv; + wlan_adapter *Adapter = priv->adapter; + wait_queue_t wait; + u8 ireg = 0; + u8 runCmd = 1; + + OS_INTERRUPT_SAVE_AREA; + + ENTER(); + + wlan_activate_thread(thread); + + init_waitqueue_entry(&wait, current); + + current->flags |= PF_NOFREEZE; + + wmm_init(priv); + + for (;;) { + add_wait_queue(&thread->waitQ, &wait); + OS_SET_THREAD_STATE(TASK_INTERRUPTIBLE); + + runCmd = 1; + TX_DISABLE; + + if ((Adapter->WakeupTries) || + (Adapter->PSState == PS_STATE_SLEEP + && !Adapter->bWakeupDevRequired) || + (!Adapter->IntCounter && + Adapter->PSState == PS_STATE_PRE_SLEEP) || + (!Adapter->IntCounter + && (priv->wlan_dev.dnld_sent || Adapter->TxLockFlag + || wmm_lists_empty(priv) || wmm_is_queue_stopped(priv)) + && (priv->wlan_dev.dnld_sent || !Adapter->CurrentTxSkb) + && (priv->wlan_dev.dnld_sent || Adapter->CurCmd || + list_empty(&Adapter->CmdPendingQ)) + ) + ) { + PRINTM(INFO, "main-thread sleeping... " + "HS_Act=%s WakeupReq=%s Conn=%s PS_Mode=%d PS_State=%d\n", + (Adapter->HS_Activated) ? "Y" : "N", + (Adapter->bWakeupDevRequired) ? "Y" : "N", + (Adapter->MediaConnectStatus) ? "Y" : "N", + Adapter->PSMode, Adapter->PSState); + +#ifdef _MAINSTONE + MST_LEDDAT1 = get_utimeofday(); +#endif + TX_RESTORE; + schedule(); + PRINTM(INFO, "main-thread waking up: IntCnt=%d " + "CurCmd=%s CmdPending=%s\n" + " Connect=%s " + "CurTxSkb=%s dnld_sent=%d\n", + Adapter->IntCounter, + (Adapter->CurCmd) ? "Y" : "N", + list_empty(&Adapter->CmdPendingQ) ? "N" : "Y", + (Adapter->MediaConnectStatus) ? "Y" : "N", + (Adapter->CurrentTxSkb) ? "Y" : "N", + priv->wlan_dev.dnld_sent); + } else { + TX_RESTORE; + } + + OS_SET_THREAD_STATE(TASK_RUNNING); + remove_wait_queue(&thread->waitQ, &wait); + + if (kthread_should_stop() || Adapter->SurpriseRemoved) { + PRINTM(INFO, "main-thread: break from main thread: " + "SurpriseRemoved=0x%x\n", Adapter->SurpriseRemoved); + break; + } + + if (Adapter->IntCounter) { + OS_INT_DISABLE; + Adapter->IntCounter = 0; + OS_INT_RESTORE; + + if (sbi_get_int_status(priv, &ireg)) { + PRINTM(ERROR, + "main-thread: reading HOST_INT_STATUS_REG failed\n"); + continue; + } + OS_INT_DISABLE; + Adapter->HisRegCpy |= ireg; + OS_INT_RESTORE; + PRINTM(INTR, "INT: status = 0x%x\n", Adapter->HisRegCpy); + } else if (Adapter->bWakeupDevRequired + && (Adapter->HS_Activated || (Adapter->IsDeepSleep) + ) + ) { + Adapter->WakeupTries++; + PRINTM(CMND, + "Wakeup device... WakeupReq=%s Conn=%s PS_Mode=%d PS_State=%d\n", + (Adapter->bWakeupDevRequired) ? "Y" : "N", + (priv->adapter->MediaConnectStatus) ? "Y" : "N", + priv->adapter->PSMode, priv->adapter->PSState); + + /* Wake up device */ + if (sbi_exit_deep_sleep(priv)) + PRINTM(MSG, "main-thread: wakeup dev failed\n"); + continue; + } + + /* Command response? */ + if (Adapter->HisRegCpy & HIS_CmdUpLdRdy) { + OS_INT_DISABLE; + Adapter->HisRegCpy &= ~HIS_CmdUpLdRdy; + OS_INT_RESTORE; + + wlan_process_rx_command(priv); + } + + /* Any received data? */ + if (Adapter->HisRegCpy & HIS_RxUpLdRdy) { + OS_INT_DISABLE; + Adapter->HisRegCpy &= ~HIS_RxUpLdRdy; + OS_INT_RESTORE; + + wlan_send_rxskbQ(priv); + runCmd = 0; + } + + /* Any Card Event */ + if (Adapter->HisRegCpy & HIS_CardEvent) { + OS_INT_DISABLE; + Adapter->HisRegCpy &= ~HIS_CardEvent; + OS_INT_RESTORE; + + if (sbi_read_event_cause(priv)) { + PRINTM(ERROR, "main-thread: sbi_read_event_cause failed.\n"); + continue; + } + wlan_process_event(priv); + } + + /* Check if we need to confirm Sleep Request received previously */ + if (Adapter->PSState == PS_STATE_PRE_SLEEP) { + if (!priv->wlan_dev.dnld_sent && !Adapter->CurCmd) { + ASSERT(Adapter->MediaConnectStatus == + WlanMediaStateConnected); + PSConfirmSleep(priv, (u16) Adapter->PSMode); + } + } + + /* The PS state is changed during processing of + * Sleep Request event above + */ + if ((Adapter->PSState == PS_STATE_SLEEP) + || (Adapter->PSState == PS_STATE_PRE_SLEEP)) { + continue; + } + + if (Adapter->IsDeepSleep) + continue; + + /* The HS_Activated flag is changed during processing of + HS_Activate command resp */ + /* We cannot send command or data if HS_Activated and + WakeupDevRequired are TRUE */ + if (Adapter->HS_Activated && Adapter->bWakeupDevRequired) { + PRINTM(INFO, "main-thread: cannot send command or data, " + "HS_Activated=%d\n", Adapter->HS_Activated); + continue; + } + + /* Execute the next command */ + if (!priv->wlan_dev.dnld_sent && !Adapter->CurCmd && runCmd == 1) { + ExecuteNextCommand(priv); + } + + if (!priv->wlan_dev.dnld_sent + && !wmm_lists_empty(priv) && !wmm_is_queue_stopped(priv)) { + if ((Adapter->PSState == PS_STATE_FULL_POWER) + || (Adapter->sleep_period.period == 0) + || (Adapter->TxLockFlag == FALSE)) { + wmm_process_tx(priv); + } + } + + } + + wlan_deactivate_thread(thread); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function adds the card. it will probe the + * card, allocate the wlan_priv and initialize the device. + * + * @param card A pointer to card + * @return A pointer to wlan_private structure + */ +wlan_private * +wlan_add_card(void *card) +{ + struct net_device *dev = NULL; + wlan_private *priv = NULL; + + ENTER(); + + /* probe the card */ + if (sbi_probe_card(card) < 0) { + PRINTM(MSG, "NO card found!\n"); + return NULL; + } + + /* Allocate an Ethernet device and register it */ + if (!(dev = alloc_etherdev(sizeof(wlan_private)))) { + PRINTM(MSG, "Init ethernet device failed!\n"); + return NULL; + } + + priv = dev->priv; + + /* allocate buffer for wlan_adapter */ + if (!(priv->adapter = kmalloc(sizeof(wlan_adapter), GFP_KERNEL))) { + PRINTM(MSG, "Allocate buffer for wlan_adapter failed!\n"); + goto err_kmalloc; + } + + /* init wlan_adapter */ + memset(priv->adapter, 0, sizeof(wlan_adapter)); + + priv->wlan_dev.netdev = dev; + priv->wlan_dev.card = card; + wlanpriv = priv; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + SET_MODULE_OWNER(dev); +#endif + + /* Setup the OS Interface to our functions */ + dev->open = wlan_open; + dev->hard_start_xmit = wlan_hard_start_xmit; + dev->stop = wlan_close; + dev->do_ioctl = wlan_do_ioctl; + dev->set_mac_address = wlan_set_mac_address; + + dev->tx_timeout = wlan_tx_timeout; + dev->get_stats = wlan_get_stats; + dev->watchdog_timeo = MRVDRV_DEFAULT_WATCHDOG_TIMEOUT; + +#ifdef WIRELESS_EXT +#if WIRELESS_EXT > 17 + wlan_handler_def.get_wireless_stats = wlan_get_wireless_stats; +#else + dev->get_wireless_stats = wlan_get_wireless_stats; +#endif + dev->wireless_handlers = (struct iw_handler_def *) &wlan_handler_def; +#endif +#define NETIF_F_DYNALLOC 16 + dev->features |= NETIF_F_DYNALLOC; + dev->flags |= IFF_BROADCAST | IFF_MULTICAST; + dev->set_multicast_list = wlan_set_multicast_list; + + init_waitqueue_head(&priv->adapter->ds_awake_q); + +#ifdef ENABLE_PM + /* register Driver to Linux Power Management system. */ + if (!driver_register(&wlan_pm_driver)) { + /* Register one device to Linux Power Management system. */ + if (platform_device_register(&wlan_pm_platform_device)) { + PRINTM(MSG, + "WiFi driver, wlan_main : error when registering device to Linux Power Managment.\n"); + driver_unregister(&wlan_pm_driver); + } else { + PRINTM(INFO, + "WiFi device and driver registered to Linux Power Managment.\n"); + } + } else { + PRINTM(MSG, + "WiFi driver, wlan_main : error when registering driver to Linux Power Managment.\n"); + } + +#endif + + INIT_LIST_HEAD(&priv->adapter->CmdFreeQ); + INIT_LIST_HEAD(&priv->adapter->CmdPendingQ); + + PRINTM(INFO, "Starting kthread...\n"); + priv->MainThread.priv = priv; + wlan_create_thread(wlan_service_main_thread, + &priv->MainThread, "wlan_main_service"); + + ConfigureThreadPriority(); + +#ifdef REASSOCIATION + priv->ReassocThread.priv = priv; + wlan_create_thread(wlan_reassociation_thread, + &priv->ReassocThread, "wlan_reassoc_service"); +#endif /* REASSOCIATION */ + + /* + * Register the device. Fillup the private data structure with + * relevant information from the card and request for the required + * IRQ. + */ + + if (sbi_register_dev(priv) < 0) { + PRINTM(FATAL, "Failed to register wlan device!\n"); + goto err_registerdev; + } + + PRINTM(WARN, "%s: Marvell Wlan 802.11 Adapter " + "revision 0x%02X at IRQ %i\n", dev->name, + priv->adapter->chip_rev, dev->irq); + +#ifdef CONFIG_MARVELL_8686_PROC_FS + wlan_proc_entry(priv, dev); +#ifdef CONFIG_MARVELL_8686_DEBUG + wlan_debug_entry(priv, dev); +#endif +#endif + + /* Get the CIS Table */ + sbi_get_cis_info(priv); + + /* init FW and HW */ + if (wlan_init_fw(priv)) { + PRINTM(FATAL, "Firmware Init Failed\n"); + goto err_init_fw; + } + + if (register_netdev(dev)) { + printk(KERN_ERR "Cannot register network device!\n"); + goto err_netdev; + } + + LEAVE(); + return priv; + +err_netdev: + unregister_netdev(dev); + +err_init_fw: + sbi_unregister_dev(priv); + +#ifdef CONFIG_MARVELL_8686_PROC_FS +#ifdef CONFIG_MARVELL_8686_DEBUG + wlan_debug_remove(priv); +#endif + wlan_proc_remove(priv); +#endif + +err_registerdev: + /* Stop the thread servicing the interrupts */ + wake_up_interruptible(&priv->MainThread.waitQ); + wlan_terminate_thread(&priv->MainThread); + +#ifdef REASSOCIATION + wake_up_interruptible(&priv->ReassocThread.waitQ); + wlan_terminate_thread(&priv->ReassocThread); +#endif /* REASSOCIATION */ + +err_kmalloc: + free_netdev(dev); + wlan_free_adapter(priv); + wlanpriv = NULL; + + LEAVE(); + return NULL; +} + +/** + * @brief This function removes the card. + * + * @param priv A pointer to card + * @return WLAN_STATUS_SUCCESS + */ +int +wlan_remove_card(void *card) +{ + wlan_private *priv = wlanpriv; + wlan_adapter *Adapter; + struct net_device *dev; + union iwreq_data wrqu; + + ENTER(); + + if (!priv) { + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + Adapter = priv->adapter; + + if (!Adapter) { + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + dev = priv->wlan_dev.netdev; + + wake_up_interruptible(&Adapter->ds_awake_q); + + if (Adapter->CurCmd) { + PRINTM(INFO, "Wake up current cmdwait_q\n"); + wake_up_interruptible(&Adapter->CurCmd->cmdwait_q); + } + + Adapter->CurCmd = NULL; + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + wlan_clean_txrx(priv); + Adapter->MediaConnectStatus = WlanMediaStateDisconnected; + } + Adapter->IsAutoDeepSleepEnabled = FALSE; + if (Adapter->IsDeepSleep == TRUE) { + sbi_exit_deep_sleep(priv); + if (Adapter->IsDeepSleep == TRUE) { + if (os_wait_interruptible_timeout(Adapter->ds_awake_q, + !Adapter->IsDeepSleep, + MRVDRV_DEEP_SLEEP_EXIT_TIMEOUT) + == 0) { + PRINTM(MSG, "ds_awake_q: timer expired\n"); + } + } + } + + if (Adapter->PSMode == Wlan802_11PowerModeMAX_PSP) { + Adapter->PSMode = Wlan802_11PowerModeCAM; + PSWakeup(priv, HostCmd_OPTION_WAITFORRSP); + } + + memset(wrqu.ap_addr.sa_data, 0xaa, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWAP, &wrqu, NULL); + + /* Disable interrupts on the card as we cannot handle them after RESET */ + sbi_disable_host_int(priv); + + PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RESET, 0, 0, 0, NULL); + + os_sched_timeout(500); + +#ifdef ENABLE_PM + /* unregister driver and device from Linux Power Management system. */ + platform_device_unregister(&wlan_pm_platform_device); + driver_unregister(&wlan_pm_driver); +#endif + + Adapter->SurpriseRemoved = TRUE; + + /* Stop the thread servicing the interrupts */ + wake_up_interruptible(&priv->MainThread.waitQ); + +#ifdef REASSOCIATION + wake_up_interruptible(&priv->ReassocThread.waitQ); +#endif /* REASSOCIATION */ + +#ifdef CONFIG_MARVELL_8686_PROC_FS +#ifdef CONFIG_MARVELL_8686_DEBUG + wlan_debug_remove(priv); +#endif + wlan_proc_remove(priv); +#endif + + PRINTM(INFO, "unregester dev\n"); + sbi_unregister_dev(priv); + + /* Last reference is our one */ + PRINTM(INFO, "refcnt = %d\n", atomic_read(&dev->refcnt)); + + PRINTM(INFO, "netdev_finish_unregister: %s%s.\n", dev->name, + (dev->features & NETIF_F_DYNALLOC) ? "" : ", old style"); + + unregister_netdev(dev); + + PRINTM(INFO, "Unregister finish\n"); + + priv->wlan_dev.netdev = NULL; + PRINTM(INFO, "Free Adapter\n"); + wlan_free_adapter(priv); + free_netdev(dev); + wlanpriv = NULL; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief This function sends the rx packets to the os from the skb queue + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +void +wlan_send_rxskbQ(wlan_private * priv) +{ + struct sk_buff *skb; + + ENTER(); + if (priv->adapter) { + while ((skb = wlan_pop_rx_skb(&priv->adapter->RxSkbQ))) { + if (ProcessRxedPacket(priv, skb) == -ENOMEM) + break; + } + } + LEAVE(); +} + +/** + * @brief This function finds the CFP in + * region_cfp_table based on region and band parameter. + * + * @param region The region code + * @param band The band + * @param cfp_no A pointer to CFP number + * @return A pointer to CFP + */ +CHANNEL_FREQ_POWER * +wlan_get_region_cfp_table(u8 region, u8 band, int *cfp_no) +{ + int i; + + ENTER(); + + for (i = 0; i < sizeof(region_cfp_table) / sizeof(region_cfp_table_t); + i++) { + PRINTM(INFO, "region_cfp_table[i].region=%d\n", + region_cfp_table[i].region); + if (region_cfp_table[i].region == region) { + { + *cfp_no = region_cfp_table[i].cfp_no_BG; + LEAVE(); + return region_cfp_table[i].cfp_BG; + } + } + } + + LEAVE(); + return NULL; +} + +/** + * @brief This function sets region table. + * + * @param priv A pointer to wlan_private structure + * @param region The region code + * @param band The band + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_set_regiontable(wlan_private * priv, u8 region, u8 band) +{ + wlan_adapter *Adapter = priv->adapter; + int i = 0; + + CHANNEL_FREQ_POWER *cfp; + int cfp_no; + + ENTER(); + + memset(Adapter->region_channel, 0, sizeof(Adapter->region_channel)); + + { + cfp = wlan_get_region_cfp_table(region, band, &cfp_no); + if (cfp != NULL) { + Adapter->region_channel[i].NrCFP = cfp_no; + Adapter->region_channel[i].CFP = cfp; + } else { + PRINTM(INFO, "wrong region code %#x in Band B-G\n", region); + return WLAN_STATUS_FAILURE; + } + Adapter->region_channel[i].Valid = TRUE; + Adapter->region_channel[i].Region = region; + Adapter->region_channel[i].Band = band; + i++; + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function handles the interrupt. it will change PS + * state if applicable. it will wake up main_thread to handle + * the interrupt event as well. + * + * @param dev A pointer to net_device structure + * @return n/a + */ +void +wlan_interrupt(struct net_device *dev) +{ + wlan_private *priv = dev->priv; + + priv->adapter->IntCounter++; + + PRINTM(INTR, "*\n"); + + priv->adapter->WakeupTries = 0; + + if (priv->adapter->PSState == PS_STATE_SLEEP) { + priv->adapter->PSState = PS_STATE_AWAKE; + } + + wake_up_interruptible(&priv->MainThread.waitQ); +} + +#if 0 +/** + * @brief This function initializes module. + * + * @param n/a A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_init_module(void) +{ + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (sbi_register(wlan_add_card, wlan_remove_card, NULL) == NULL) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function cleans module + * + * @param priv n/a + * @return n/a + */ +void +wlan_cleanup_module(void) +{ + ENTER(); + + sbi_unregister(); + + LEAVE(); +} + +module_init(wlan_init_module); +module_exit(wlan_cleanup_module); +#endif + +MODULE_DESCRIPTION("M-WLAN Driver"); +MODULE_AUTHOR("Marvell International Ltd."); diff --git a/drivers/net/wireless/marvell8686/wlan_proc.c b/drivers/net/wireless/marvell8686/wlan_proc.c new file mode 100644 index 0000000..99604dd --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_proc.c @@ -0,0 +1,213 @@ +/** @file wlan_proc.c + * @brief This file contains functions for proc fin proc file. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 10/04/05: Add Doxygen format comments + 01/05/06: Add kernel 2.6.x support + +********************************************************/ +#include "include.h" + +#ifdef CONFIG_MARVELL_8686_PROC_FS +/******************************************************** + Local Variables +********************************************************/ + +static char *szModes[] = { + "Ad-hoc", + "Managed", + "Auto", + "Unknown" +}; + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief proc read function + * + * @param page pointer to buffer + * @param start read data starting position + * @param offset offset + * @param count counter + * @param eof end of file flag + * @param data data to output + * @return number of output data + */ +static int +wlan_proc_read(char *page, char **start, off_t offset, + int count, int *eof, void *data) +{ + int i; + char *p = page; + struct net_device *netdev = data; + struct dev_mc_list *mcptr = netdev->mc_list; + char fmt[64]; + wlan_private *priv = netdev->priv; + wlan_adapter *Adapter = priv->adapter; + ulong flags; + + if (offset != 0) { + *eof = 1; + goto exit; + } + + get_version(Adapter, fmt, sizeof(fmt) - 1); + + p += sprintf(p, "driver_name = " "\"wlan\"\n"); + p += sprintf(p, "driver_version = %s", fmt); + p += sprintf(p, "\nInterfaceName=\"%s\"\n", netdev->name); + p += sprintf(p, "Mode=\"%s\"\n", szModes[Adapter->InfrastructureMode]); + p += sprintf(p, "State=\"%s\"\n", + ((Adapter->MediaConnectStatus == + WlanMediaStateDisconnected) ? "Disconnected" : + "Connected")); + p += sprintf(p, "MACAddress=\"%02x:%02x:%02x:%02x:%02x:%02x\"\n", + netdev->dev_addr[0], netdev->dev_addr[1], + netdev->dev_addr[2], netdev->dev_addr[3], + netdev->dev_addr[4], netdev->dev_addr[5]); + + p += sprintf(p, "MCCount=\"%d\"\n", netdev->mc_count); + p += sprintf(p, "ESSID=\"%s\"\n", + (u8 *) Adapter->CurBssParams.BSSDescriptor.Ssid.Ssid); + p += sprintf(p, "Channel=\"%d\"\n", + Adapter->CurBssParams.BSSDescriptor.Channel); + p += sprintf(p, "region_code = \"%02x\"\n", (u32) Adapter->RegionCode); + + /* + * Put out the multicast list + */ + for (i = 0; i < netdev->mc_count; i++) { + p += sprintf(p, + "MCAddr[%d]=\"%02x:%02x:%02x:%02x:%02x:%02x\"\n", + i, + mcptr->dmi_addr[0], mcptr->dmi_addr[1], + mcptr->dmi_addr[2], mcptr->dmi_addr[3], + mcptr->dmi_addr[4], mcptr->dmi_addr[5]); + + mcptr = mcptr->next; + } + p += sprintf(p, "num_tx_bytes = %lu\n", priv->stats.tx_bytes); + p += sprintf(p, "num_rx_bytes = %lu\n", priv->stats.rx_bytes); + p += sprintf(p, "num_tx_pkts = %lu\n", priv->stats.tx_packets); + p += sprintf(p, "num_rx_pkts = %lu\n", priv->stats.rx_packets); + p += sprintf(p, "num_tx_pkts_dropped = %lu\n", priv->stats.tx_dropped); + p += sprintf(p, "num_rx_pkts_dropped = %lu\n", priv->stats.rx_dropped); + p += sprintf(p, "num_tx_pkts_err = %lu\n", priv->stats.tx_errors); + p += sprintf(p, "num_rx_pkts_err = %lu\n", priv->stats.rx_errors); + p += sprintf(p, "carrier %s\n", + ((netif_carrier_ok(priv->wlan_dev.netdev)) ? "on" : "off")); + p += sprintf(p, "tx queue %s\n", + ((netif_queue_stopped(priv->wlan_dev.netdev)) ? "stopped" : + "started")); + + spin_lock_irqsave(&Adapter->QueueSpinLock, flags); + if (Adapter->CurCmd) { + HostCmd_DS_COMMAND *CmdPtr = + (HostCmd_DS_COMMAND *) Adapter->CurCmd->BufVirtualAddr; + p += sprintf(p, "CurCmd ID = 0x%x, 0x%x\n", + wlan_cpu_to_le16(CmdPtr->Command), + wlan_cpu_to_le16(*(u16 *) ((u8 *) CmdPtr + S_DS_GEN))); + } else { + p += sprintf(p, "CurCmd NULL\n"); + } + spin_unlock_irqrestore(&Adapter->QueueSpinLock, flags); + + exit: + return (p - page); +} + +/******************************************************** + Global Functions +********************************************************/ + +/** + * @brief create wlan proc file + * + * @param priv pointer wlan_private + * @param dev pointer net_device + * @return N/A + */ +void +wlan_proc_entry(wlan_private * priv, struct net_device *dev) +{ + + PRINTM(INFO, "Creating Proc Interface\n"); + + if (!priv->proc_entry) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + priv->proc_entry = proc_mkdir("wlan", init_net.proc_net); +#else + priv->proc_entry = proc_mkdir("wlan", proc_net); +#endif + + + if (priv->proc_entry) { + priv->proc_dev = create_proc_read_entry + ("info", 0, priv->proc_entry, wlan_proc_read, dev); + } + } +} + +/** + * @brief remove proc file + * + * @param priv pointer wlan_private + * @return N/A + */ +void +wlan_proc_remove(wlan_private * priv) +{ + + if (priv->proc_entry) { + remove_proc_entry("info", priv->proc_entry); + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + remove_proc_entry("wlan", init_net.proc_net); +#else + remove_proc_entry("wlan", proc_net); +#endif + +} + +/** + * @brief convert string to number + * + * @param s pointer to numbered string + * @return converted number from string s + */ +int +string_to_number(char *s) +{ + int r = 0; + int base = 0; + + if ((strncmp(s, "0x", 2) == 0) || (strncmp(s, "0X", 2) == 0)) + base = 16; + else + base = 10; + if (base == 16) + s += 2; + for (s = s; *s != 0; s++) { + if ((*s >= 48) && (*s <= 57)) + r = (r * base) + (*s - 48); + else if ((*s >= 65) && (*s <= 70)) + r = (r * base) + (*s - 55); + else if ((*s >= 97) && (*s <= 102)) + r = (r * base) + (*s - 87); + else + break; + } + + return r; +} + +#endif diff --git a/drivers/net/wireless/marvell8686/wlan_rx.c b/drivers/net/wireless/marvell8686/wlan_rx.c new file mode 100644 index 0000000..9a55ca1 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_rx.c @@ -0,0 +1,367 @@ +/** @file wlan_rx.c + * @brief This file contains the handling of RX in wlan + * driver. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 09/28/05: Add Doxygen format comments + 12/09/05: ADD Sliding window SNR/NF Average Calculation support + +********************************************************/ + +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ + +typedef struct +{ + u8 dest_addr[6]; + u8 src_addr[6]; + u16 h803_len; + +} __ATTRIB_PACK__ Eth803Hdr_t; + +typedef struct +{ + u8 llc_dsap; + u8 llc_ssap; + u8 llc_ctrl; + u8 snap_oui[3]; + u16 snap_type; + +} __ATTRIB_PACK__ Rfc1042Hdr_t; + +typedef struct +{ + Eth803Hdr_t eth803_hdr; + Rfc1042Hdr_t rfc1042_hdr; + +} __ATTRIB_PACK__ RxPacketHdr_t; + +typedef struct +{ + u8 dest_addr[6]; + u8 src_addr[6]; + u16 ethertype; + +} __ATTRIB_PACK__ EthII_Hdr_t; + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function computes the AvgSNR . + * + * @param priv A pointer to wlan_private structure + * @return AvgSNR + */ +static u8 +wlan_getAvgSNR(wlan_private * priv) +{ + u8 i; + u16 temp = 0; + wlan_adapter *Adapter = priv->adapter; + if (Adapter->numSNRNF == 0) + return 0; + for (i = 0; i < Adapter->numSNRNF; i++) + temp += Adapter->rawSNR[i]; + return (u8) (temp / Adapter->numSNRNF); + +} + +/** + * @brief This function computes the AvgNF + * + * @param priv A pointer to wlan_private structure + * @return AvgNF + */ +static u8 +wlan_getAvgNF(wlan_private * priv) +{ + u8 i; + u16 temp = 0; + wlan_adapter *Adapter = priv->adapter; + if (Adapter->numSNRNF == 0) + return 0; + for (i = 0; i < Adapter->numSNRNF; i++) + temp += Adapter->rawNF[i]; + return (u8) (temp / Adapter->numSNRNF); + +} + +/** + * @brief This function save the raw SNR/NF to our internel buffer + * + * @param priv A pointer to wlan_private structure + * @param pRxPD A pointer to RxPD structure of received packet + * @return n/a + */ +static void +wlan_save_rawSNRNF(wlan_private * priv, RxPD * pRxPD) +{ + wlan_adapter *Adapter = priv->adapter; + if (Adapter->numSNRNF < Adapter->data_avg_factor) + Adapter->numSNRNF++; + Adapter->rawSNR[Adapter->nextSNRNF] = pRxPD->SNR; + Adapter->rawNF[Adapter->nextSNRNF] = pRxPD->NF; + Adapter->nextSNRNF++; + if (Adapter->nextSNRNF >= Adapter->data_avg_factor) + Adapter->nextSNRNF = 0; + return; +} + +#define DATA_RSSI_LOW_BIT 0x01 +#define DATA_SNR_LOW_BIT 0x02 +#define DATA_RSSI_HIGH_BIT 0x04 +#define DATA_SNR_HIGH_BIT 0x08 +/** + * @brief This function computes the RSSI in received packet. + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +static void +wlan_check_subscribe_event(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int temp; + if (Adapter->subevent.EventsBitmap == 0) + return; + if ((Adapter->subevent.EventsBitmap & DATA_RSSI_LOW_BIT) || + (Adapter->subevent.EventsBitmap & DATA_RSSI_HIGH_BIT)) { + temp = + -CAL_RSSI(Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + Adapter->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + if (Adapter->subevent.EventsBitmap & DATA_RSSI_LOW_BIT) { + if (temp > Adapter->subevent.Rssi_low.value) { + if (!Adapter->subevent.Rssi_low.Freq) { + Adapter->subevent.EventsBitmap &= ~DATA_RSSI_LOW_BIT; + send_iwevcustom_event(priv, CUS_EVT_DATA_RSSI_LOW); + } else { + Adapter->subevent.Rssi_low_count++; + if (Adapter->subevent.Rssi_low_count >= + Adapter->subevent.Rssi_low.Freq) { + Adapter->subevent.Rssi_low_count = 0; + send_iwevcustom_event(priv, CUS_EVT_DATA_RSSI_LOW); + } + } + } else + Adapter->subevent.Rssi_low_count = 0; + } + if (Adapter->subevent.EventsBitmap & DATA_RSSI_HIGH_BIT) { + if (temp < Adapter->subevent.Rssi_high.value) { + if (!Adapter->subevent.Rssi_high.Freq) { + Adapter->subevent.EventsBitmap &= ~DATA_RSSI_HIGH_BIT; + send_iwevcustom_event(priv, CUS_EVT_DATA_RSSI_HIGH); + } else { + Adapter->subevent.Rssi_high_count++; + if (Adapter->subevent.Rssi_high_count >= + Adapter->subevent.Rssi_high.Freq) { + Adapter->subevent.Rssi_high_count = 0; + send_iwevcustom_event(priv, CUS_EVT_DATA_RSSI_HIGH); + } + } + } else + Adapter->subevent.Rssi_high_count = 0; + } + } + if ((Adapter->subevent.EventsBitmap & DATA_SNR_LOW_BIT) || + (Adapter->subevent.EventsBitmap & DATA_SNR_HIGH_BIT)) { + temp = Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE; + if (Adapter->subevent.EventsBitmap & DATA_SNR_LOW_BIT) { + if (temp < Adapter->subevent.Snr_low.value) { + if (!Adapter->subevent.Snr_low.Freq) { + send_iwevcustom_event(priv, CUS_EVT_DATA_SNR_LOW); + Adapter->subevent.EventsBitmap &= ~DATA_SNR_LOW_BIT; + } else { + Adapter->subevent.Snr_low_count++; + if (Adapter->subevent.Snr_low_count >= + Adapter->subevent.Snr_low.Freq) { + Adapter->subevent.Snr_low_count = 0; + send_iwevcustom_event(priv, CUS_EVT_DATA_SNR_LOW); + } + } + } else + Adapter->subevent.Snr_low_count = 0; + } + if (Adapter->subevent.EventsBitmap & DATA_SNR_HIGH_BIT) { + if (temp > Adapter->subevent.Snr_high.value) { + if (!Adapter->subevent.Snr_high.Freq) { + Adapter->subevent.EventsBitmap &= ~DATA_SNR_HIGH_BIT; + send_iwevcustom_event(priv, CUS_EVT_DATA_SNR_HIGH); + } else { + Adapter->subevent.Snr_high_count++; + if (Adapter->subevent.Snr_high_count >= + Adapter->subevent.Snr_high.Freq) { + Adapter->subevent.Snr_high_count = 0; + send_iwevcustom_event(priv, CUS_EVT_DATA_SNR_HIGH); + } + } + + } else + Adapter->subevent.Snr_high_count = 0; + } + } + return; +} + +/** + * @brief This function computes the RSSI in received packet. + * + * @param priv A pointer to wlan_private structure + * @param pRxPD A pointer to RxPD structure of received packet + * @return n/a + */ +static void +wlan_compute_rssi(wlan_private * priv, RxPD * pRxPD) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + PRINTM(INFO, "RxPD: SNR = %d, NF = %d\n", pRxPD->SNR, pRxPD->NF); + + Adapter->SNR[TYPE_RXPD][TYPE_NOAVG] = pRxPD->SNR; + Adapter->NF[TYPE_RXPD][TYPE_NOAVG] = pRxPD->NF; + wlan_save_rawSNRNF(priv, pRxPD); + + Adapter->RxPDAge = os_time_get(); + Adapter->RxPDRate = pRxPD->RxRate; + + Adapter->SNR[TYPE_RXPD][TYPE_AVG] = wlan_getAvgSNR(priv) * AVG_SCALE; + Adapter->NF[TYPE_RXPD][TYPE_AVG] = wlan_getAvgNF(priv) * AVG_SCALE; + PRINTM(INFO, "SNR-avg = %d, NF-avg = %d\n", + Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + Adapter->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + + Adapter->RSSI[TYPE_RXPD][TYPE_NOAVG] = + CAL_RSSI(Adapter->SNR[TYPE_RXPD][TYPE_NOAVG], + Adapter->NF[TYPE_RXPD][TYPE_NOAVG]); + + Adapter->RSSI[TYPE_RXPD][TYPE_AVG] = + CAL_RSSI(Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + Adapter->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + wlan_check_subscribe_event(priv); + LEAVE(); +} + +/******************************************************** + Global functions +********************************************************/ + +/** + * @brief This function processes received packet and forwards it + * to kernel/upper layer + * + * @param priv A pointer to wlan_private + * @param skb A pointer to skb which includes the received packet + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +ProcessRxedPacket(wlan_private * priv, struct sk_buff *skb) +{ + int ret = WLAN_STATUS_SUCCESS; + + RxPacketHdr_t *pRxPkt; + RxPD *pRxPD; + + int hdrChop; + EthII_Hdr_t *pEthHdr; + u32 u32SkbLen = skb->len; + + const u8 rfc1042_eth_hdr[] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; + + ENTER(); + + pRxPD = (RxPD *) skb->data; + pRxPkt = (RxPacketHdr_t *) ((u8 *) pRxPD + pRxPD->PktOffset); + + DBG_HEXDUMP(DAT_D, "Rx", skb->data, MIN(skb->len, MAX_DATA_DUMP_LEN)); + + endian_convert_RxPD(pRxPD); + + if (skb->len < (ETH_HLEN + 8 + pRxPD->PktOffset)) { + PRINTM(ERROR, "RX Error: FRAME RECEIVED WITH BAD LENGTH\n"); + priv->stats.rx_length_errors++; + ret = WLAN_STATUS_SUCCESS; + kfree_skb(skb); + goto done; + } + + PRINTM(INFO, "RX Data: skb->len - pRxPD->PktOffset = %d - %d = %d\n", + skb->len, pRxPD->PktOffset, skb->len - pRxPD->PktOffset); + + HEXDUMP("RX Data: Dest", pRxPkt->eth803_hdr.dest_addr, + sizeof(pRxPkt->eth803_hdr.dest_addr)); + HEXDUMP("RX Data: Src", pRxPkt->eth803_hdr.src_addr, + sizeof(pRxPkt->eth803_hdr.src_addr)); + + if (memcmp(&pRxPkt->rfc1042_hdr, + rfc1042_eth_hdr, sizeof(rfc1042_eth_hdr)) == 0) { + /* + * Replace the 803 header and rfc1042 header (llc/snap) with an + * EthernetII header, keep the src/dst and snap_type (ethertype) + * + * The firmware only passes up SNAP frames converting + * all RX Data from 802.11 to 802.2/LLC/SNAP frames. + * + * To create the Ethernet II, just move the src, dst address right + * before the snap_type. + */ + pEthHdr = (EthII_Hdr_t *) + ((u8 *) & pRxPkt->eth803_hdr + + sizeof(pRxPkt->eth803_hdr) + sizeof(pRxPkt->rfc1042_hdr) + - sizeof(pRxPkt->eth803_hdr.dest_addr) + - sizeof(pRxPkt->eth803_hdr.src_addr) + - sizeof(pRxPkt->rfc1042_hdr.snap_type)); + + memcpy(pEthHdr->src_addr, pRxPkt->eth803_hdr.src_addr, + sizeof(pEthHdr->src_addr)); + memcpy(pEthHdr->dest_addr, pRxPkt->eth803_hdr.dest_addr, + sizeof(pEthHdr->dest_addr)); + + /* Chop off the RxPD + the excess memory from the 802.2/llc/snap header + * that was removed + */ + hdrChop = (u8 *) pEthHdr - (u8 *) pRxPD; + } else { + HEXDUMP("RX Data: LLC/SNAP", + (u8 *) & pRxPkt->rfc1042_hdr, sizeof(pRxPkt->rfc1042_hdr)); + + /* Chop off the RxPD */ + hdrChop = (u8 *) & pRxPkt->eth803_hdr - (u8 *) pRxPD; + } + + /* Chop off the leading header bytes so the skb points to the start of + * either the reconstructed EthII frame or the 802.2/llc/snap frame + */ + skb_pull(skb, hdrChop); + + u32SkbLen = skb->len; + wlan_compute_rssi(priv, pRxPD); + + if (os_upload_rx_packet(priv, skb)) { + PRINTM(ERROR, "RX Error: os_upload_rx_packet" " returns failure\n"); + ret = WLAN_STATUS_FAILURE; + goto done; + } + priv->stats.rx_bytes += u32SkbLen; + priv->stats.rx_packets++; + + PRINTM(DATA, "Data => kernel\n"); + + ret = WLAN_STATUS_SUCCESS; + done: + LEAVE(); + + return (ret); +} diff --git a/drivers/net/wireless/marvell8686/wlan_scan.c b/drivers/net/wireless/marvell8686/wlan_scan.c new file mode 100644 index 0000000..5ce08d3 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_scan.c @@ -0,0 +1,3226 @@ +/** @file wlan_scan.c + * + * @brief Functions implementing wlan scan IOCTL and firmware command APIs + * + * IOCTL handlers as well as command preperation and response routines + * for sending scan commands to the firmware. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + * + * @sa wlan_scan.h + */ +/******************************************************** +Change Log: + 01/11/06: Initial revision. New scan code, relocate related functions + 01/19/06: Update specific scan routines to discard old results for adhoc + 01/31/06: Add support for selectively enabling the FW Scan channel filter + +************************************************************/ + +#include "include.h" + +/******************************************************** + Local Constants +********************************************************/ + +//! Approximate amount of data needed to pass a scan result back to iwlist +#define MAX_SCAN_CELL_SIZE (IW_EV_ADDR_LEN \ + + MRVDRV_MAX_SSID_LENGTH \ + + IW_EV_UINT_LEN \ + + IW_EV_FREQ_LEN \ + + IW_EV_QUAL_LEN \ + + MRVDRV_MAX_SSID_LENGTH \ + + IW_EV_PARAM_LEN \ + + 40) /* 40 for WPAIE */ + +//! Memory needed to store a max sized Channel List TLV for a firmware scan +#define CHAN_TLV_MAX_SIZE (sizeof(MrvlIEtypesHeader_t) \ + + (MRVDRV_MAX_CHANNELS_PER_SCAN \ + * sizeof(ChanScanParamSet_t))) + +//! Memory needed to store a max number/size SSID TLV for a firmware scan +#define SSID_TLV_MAX_SIZE (1 * sizeof(MrvlIEtypes_SsIdParamSet_t)) + +//! WPS TLV MAX size is MAX IE size plus 2 bytes for u16 MRVL TLV extension +#define WPS_TLV_MAX_SIZE (sizeof(IEEEtypes_VendorSpecific_t) + 2) + +//! Maximum memory needed for a wlan_scan_cmd_config with all TLVs at max +#define MAX_SCAN_CFG_ALLOC (sizeof(wlan_scan_cmd_config) \ + + sizeof(MrvlIEtypes_NumProbes_t) \ + + CHAN_TLV_MAX_SIZE \ + + SSID_TLV_MAX_SIZE \ + + WPS_TLV_MAX_SIZE) + +//! The maximum number of channels the firmware can scan per command +#define MRVDRV_MAX_CHANNELS_PER_SCAN 14 + +/** + * @brief Number of channels to scan per firmware scan command issuance. + * + * Number restricted to prevent hitting the limit on the amount of scan data + * returned in a single firmware scan command. + */ +#define MRVDRV_CHANNELS_PER_SCAN_CMD 4 + +//! Macro to enable/disable SSID checking before storing a scan table +#ifdef DISCARD_BAD_SSID +#define CHECK_SSID_IS_VALID(x) ssid_valid(&bssidEntry.Ssid) +#else +#define CHECK_SSID_IS_VALID(x) TRUE +#endif + +/******************************************************** + Local Variables and Types +********************************************************/ + +/** + * @brief Interally used to send a configured scan cmd between driver routines + */ +typedef union +{ + wlan_scan_cmd_config config; //!< Scan configuration (variable length) + u8 configAllocBuf[MAX_SCAN_CFG_ALLOC]; //!< Max allocated block +} wlan_scan_cmd_config_tlv; + +/** + * @brief Check if a scanned network compatible with the driver settings + * + * WEP WPA WPA2 ad-hoc encrypt Network + * enabled enabled enabled AES mode Privacy WPA WPA2 Compatible + * 0 0 0 0 NONE 0 0 0 yes No security + * 0 1 0 0 x 1x 1 x yes WPA + * 0 0 1 0 x 1x x 1 yes WPA2 + * 0 0 0 1 NONE 1 0 0 yes Ad-hoc AES + * + * 1 0 0 0 NONE 1 0 0 yes Static WEP + * 0 0 0 0 !=NONE 1 0 0 yes Dynamic WEP + * + * + * @param Adapter A pointer to wlan_adapter + * @param index Index in ScanTable to check against current driver settings + * @param mode Network mode: Infrastructure or IBSS + * + * @return Index in ScanTable, or error code if negative + */ +static int +IsNetworkCompatible(wlan_adapter * Adapter, int index, int mode) +{ + BSSDescriptor_t *pBSSDesc; + + ENTER(); + + pBSSDesc = &Adapter->ScanTable[index]; + + /* Don't check for compatibility if roaming */ + if ((Adapter->MediaConnectStatus == WlanMediaStateConnected) + && (Adapter->InfrastructureMode == Wlan802_11Infrastructure) + && (pBSSDesc->InfrastructureMode == Wlan802_11Infrastructure)) { + LEAVE(); + return index; + } + + if (Adapter->wps.SessionEnable == TRUE) { + PRINTM(INFO, "Return success directly in WPS period\n"); + LEAVE(); + return index; + } + + if (pBSSDesc->InfrastructureMode == mode) { + if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPDisabled + && !Adapter->SecInfo.WPAEnabled + && !Adapter->SecInfo.WPA2Enabled + && pBSSDesc->wpaIE.VendHdr.ElementId != WPA_IE + && pBSSDesc->rsnIE.IeeeHdr.ElementId != RSN_IE + && !Adapter->AdhocAESEnabled + && Adapter->SecInfo.EncryptionMode == CIPHER_NONE + && !pBSSDesc->Privacy) { + /* no security */ + LEAVE(); + return index; + } else if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled + && !Adapter->SecInfo.WPAEnabled + && !Adapter->SecInfo.WPA2Enabled + && !Adapter->AdhocAESEnabled && pBSSDesc->Privacy) { + /* static WEP enabled */ + LEAVE(); + return index; + } else if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPDisabled + && Adapter->SecInfo.WPAEnabled + && !Adapter->SecInfo.WPA2Enabled + && (pBSSDesc->wpaIE.VendHdr.ElementId == WPA_IE) + && !Adapter->AdhocAESEnabled + /* Privacy bit may NOT be set in some APs like LinkSys WRT54G + && pBSSDesc->Privacy */ + ) { + /* WPA enabled */ + PRINTM(INFO, "IsNetworkCompatible() WPA: index=%d wpa_ie=%#x " + "wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s EncMode=%#x " + "privacy=%#x\n", + index, + pBSSDesc->wpaIE.VendHdr.ElementId, + pBSSDesc->rsnIE.IeeeHdr.ElementId, + (Adapter->SecInfo.WEPStatus == + Wlan802_11WEPEnabled) ? "e" : "d", + (Adapter->SecInfo.WPAEnabled) ? "e" : "d", + (Adapter->SecInfo.WPA2Enabled) ? "e" : "d", + Adapter->SecInfo.EncryptionMode, pBSSDesc->Privacy); + LEAVE(); + return index; + } else if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPDisabled + && !Adapter->SecInfo.WPAEnabled + && Adapter->SecInfo.WPA2Enabled + && (pBSSDesc->rsnIE.IeeeHdr.ElementId == RSN_IE) + && !Adapter->AdhocAESEnabled + /* Privacy bit may NOT be set in some APs like LinkSys WRT54G + && pBSSDesc->Privacy */ + ) { + /* WPA2 enabled */ + PRINTM(INFO, "IsNetworkCompatible() WPA2: index=%d wpa_ie=%#x " + "wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s EncMode=%#x " + "privacy=%#x\n", + index, + pBSSDesc->wpaIE.VendHdr.ElementId, + pBSSDesc->rsnIE.IeeeHdr.ElementId, + (Adapter->SecInfo.WEPStatus == + Wlan802_11WEPEnabled) ? "e" : "d", + (Adapter->SecInfo.WPAEnabled) ? "e" : "d", + (Adapter->SecInfo.WPA2Enabled) ? "e" : "d", + Adapter->SecInfo.EncryptionMode, pBSSDesc->Privacy); + LEAVE(); + return index; + } else if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPDisabled + && !Adapter->SecInfo.WPAEnabled + && !Adapter->SecInfo.WPA2Enabled + && (pBSSDesc->wpaIE.VendHdr.ElementId != WPA_IE) + && (pBSSDesc->rsnIE.IeeeHdr.ElementId != RSN_IE) + && Adapter->AdhocAESEnabled + && Adapter->SecInfo.EncryptionMode == CIPHER_NONE + && pBSSDesc->Privacy) { + /* Ad-hoc AES enabled */ + LEAVE(); + return index; + } else if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPDisabled + && !Adapter->SecInfo.WPAEnabled + && !Adapter->SecInfo.WPA2Enabled + && (pBSSDesc->wpaIE.VendHdr.ElementId != WPA_IE) + && (pBSSDesc->rsnIE.IeeeHdr.ElementId != RSN_IE) + && !Adapter->AdhocAESEnabled + && Adapter->SecInfo.EncryptionMode != CIPHER_NONE + && pBSSDesc->Privacy) { + /* dynamic WEP enabled */ + PRINTM(INFO, "IsNetworkCompatible() dynamic WEP: index=%d " + "wpa_ie=%#x wpa2_ie=%#x EncMode=%#x privacy=%#x\n", + index, + pBSSDesc->wpaIE.VendHdr.ElementId, + pBSSDesc->rsnIE.IeeeHdr.ElementId, + Adapter->SecInfo.EncryptionMode, pBSSDesc->Privacy); + LEAVE(); + return index; + } + + /* security doesn't match */ + PRINTM(INFO, "IsNetworkCompatible() FAILED: index=%d wpa_ie=%#x " + "wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s EncMode=%#x privacy=%#x\n", + index, + pBSSDesc->wpaIE.VendHdr.ElementId, + pBSSDesc->rsnIE.IeeeHdr.ElementId, + (Adapter->SecInfo.WEPStatus == + Wlan802_11WEPEnabled) ? "e" : "d", + (Adapter->SecInfo.WPAEnabled) ? "e" : "d", + (Adapter->SecInfo.WPA2Enabled) ? "e" : "d", + Adapter->SecInfo.EncryptionMode, pBSSDesc->Privacy); + LEAVE(); + return -ECONNREFUSED; + } + + /* mode doesn't match */ + LEAVE(); + return -ENETUNREACH; +} + +/** + * @brief This function validates a SSID as being able to be printed + * + * @param pSsid SSID structure to validate + * + * @return TRUE or FALSE + */ +static BOOLEAN +ssid_valid(WLAN_802_11_SSID * pSsid) +{ + int ssidIdx; + + for (ssidIdx = 0; ssidIdx < pSsid->SsidLength; ssidIdx++) { + if (!isprint(pSsid->Ssid[ssidIdx])) { + return FALSE; + } + } + + return TRUE; +} + +/** + * @brief Post process the scan table after a new scan command has completed + * + * Inspect each entry of the scan table and try to find an entry that + * matches our current associated/joined network from the scan. If + * one is found, update the stored copy of the BSSDescriptor for our + * current network. + * + * Debug dump the current scan table contents if compiled accordingly. + * + * @param priv A pointer to wlan_private structure + * + * @return void + */ +static void +wlan_scan_process_results(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int i; + int foundCurrent; + + foundCurrent = FALSE; + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + Adapter->CurBssParams.BSSDescriptor.pBeaconBuf = NULL; + Adapter->CurBssParams.BSSDescriptor.beaconBufSize = 0; + Adapter->CurBssParams.BSSDescriptor.beaconBufSizeMax = 0; + i = FindSSIDInList(Adapter, + &Adapter->CurBssParams.BSSDescriptor.Ssid, + Adapter->CurBssParams.BSSDescriptor.MacAddress, + Adapter->InfrastructureMode); + + if (i >= 0) { + PRINTM(INFO, "Found current ssid/bssid in list @ index #%d\n", i); + /* Make a copy of current BSSID descriptor */ + memcpy(&Adapter->CurBssParams.BSSDescriptor, + &Adapter->ScanTable[i], + sizeof(Adapter->CurBssParams.BSSDescriptor)); + } + } + + for (i = 0; i < Adapter->NumInScanTable; i++) { + PRINTM(INFO, "Scan:(%02d) %02x:%02x:%02x:%02x:%02x:%02x, " + "RSSI[%03d], SSID[%s]\n", + i, + Adapter->ScanTable[i].MacAddress[0], + Adapter->ScanTable[i].MacAddress[1], + Adapter->ScanTable[i].MacAddress[2], + Adapter->ScanTable[i].MacAddress[3], + Adapter->ScanTable[i].MacAddress[4], + Adapter->ScanTable[i].MacAddress[5], + (s32) Adapter->ScanTable[i].Rssi, + Adapter->ScanTable[i].Ssid.Ssid); + + } +} + +/** + * @brief Create a channel list for the driver to scan based on region info + * + * Use the driver region/band information to construct a comprehensive list + * of channels to scan. This routine is used for any scan that is not + * provided a specific channel list to scan. + * + * @param priv A pointer to wlan_private structure + * @param scanChanList Output parameter: Resulting channel list to scan + * @param filteredScan Flag indicating whether or not a BSSID or SSID filter + * is being sent in the command to firmware. Used to + * increase the number of channels sent in a scan + * command and to disable the firmware channel scan + * filter. + * + * @return void + */ +static void +wlan_scan_create_channel_list(wlan_private * priv, + ChanScanParamSet_t * scanChanList, + BOOLEAN filteredScan) +{ + + wlan_adapter *Adapter = priv->adapter; + REGION_CHANNEL *scanRegion; + CHANNEL_FREQ_POWER *cfp; + int rgnIdx; + int chanIdx; + int nextChan; + u8 scanType; + + chanIdx = 0; + + /* Set the default scan type to the user specified type, will later + * be changed to passive on a per channel basis if restricted by + * regulatory requirements (11d or 11h) + */ + scanType = Adapter->ScanType; + + for (rgnIdx = 0; rgnIdx < NELEMENTS(Adapter->region_channel); rgnIdx++) { + if (wlan_get_state_11d(priv) == ENABLE_11D && + Adapter->MediaConnectStatus != WlanMediaStateConnected) { + /* Scan all the supported chan for the first scan */ + if (!Adapter->universal_channel[rgnIdx].Valid) + continue; + scanRegion = &Adapter->universal_channel[rgnIdx]; + + /* clear the parsed_region_chan for the first scan */ + memset(&Adapter->parsed_region_chan, 0x00, + sizeof(Adapter->parsed_region_chan)); + } else { + if (!Adapter->region_channel[rgnIdx].Valid) + continue; + scanRegion = &Adapter->region_channel[rgnIdx]; + } + + for (nextChan = 0; + nextChan < scanRegion->NrCFP; nextChan++, chanIdx++) { + + cfp = scanRegion->CFP + nextChan; + + if (wlan_get_state_11d(priv) == ENABLE_11D) { + scanType = + wlan_get_scan_type_11d(cfp->Channel, + &Adapter->parsed_region_chan); + } + + switch (scanRegion->Band) { + case BAND_B: + case BAND_G: + default: + scanChanList[chanIdx].RadioType = HostCmd_SCAN_RADIO_TYPE_BG; + break; + } + + if (scanType == HostCmd_SCAN_TYPE_PASSIVE) { + scanChanList[chanIdx].MaxScanTime = + wlan_cpu_to_le16(Adapter->PassiveScanTime); + scanChanList[chanIdx].ChanScanMode.PassiveScan = TRUE; + } else { + scanChanList[chanIdx].MaxScanTime = + wlan_cpu_to_le16(Adapter->ActiveScanTime); + scanChanList[chanIdx].ChanScanMode.PassiveScan = FALSE; + } + + scanChanList[chanIdx].ChanNumber = cfp->Channel; + + if (filteredScan) { + scanChanList[chanIdx].MaxScanTime = + wlan_cpu_to_le16(Adapter->SpecificScanTime); + scanChanList[chanIdx].ChanScanMode.DisableChanFilt = TRUE; + } + } + } +} + +static void +wlan_add_wps_probe_request_ie(wlan_private * priv, u8 ** ppTlvOut) +{ + wlan_adapter *Adapter = priv->adapter; + MrvlIEtypesHeader_t *tlv; + + if (Adapter->wps.wpsIe.VendHdr.Len) { + tlv = (MrvlIEtypesHeader_t *) * ppTlvOut; + tlv->Type = wlan_cpu_to_le16(TLV_TYPE_WPS_ENROLLEE_PROBE_REQ_TLV); + tlv->Len = wlan_cpu_to_le16(Adapter->wps.wpsIe.VendHdr.Len); + *ppTlvOut += sizeof(MrvlIEtypesHeader_t); + memcpy(*ppTlvOut, + Adapter->wps.wpsIe.VendHdr.Oui, + Adapter->wps.wpsIe.VendHdr.Len); + *ppTlvOut += (Adapter->wps.wpsIe.VendHdr.Len + + sizeof(MrvlIEtypesHeader_t)); + } +} + +/** + * @brief Construct a wlan_scan_cmd_config structure to use in issue scan cmds + * + * Application layer or other functions can invoke wlan_scan_networks + * with a scan configuration supplied in a wlan_ioctl_user_scan_cfg struct. + * This structure is used as the basis of one or many wlan_scan_cmd_config + * commands that are sent to the command processing module and sent to + * firmware. + * + * Create a wlan_scan_cmd_config based on the following user supplied + * parameters (if present): + * - SSID filter + * - BSSID filter + * - Number of Probes to be sent + * - Channel list + * + * If the SSID or BSSID filter is not present, disable/clear the filter. + * If the number of probes is not set, use the adapter default setting + * Qualify the channel + * + * @param priv A pointer to wlan_private structure + * @param pUserScanIn NULL or pointer to scan configuration parameters + * @param pScanCfgOut Output parameter: Resulting scan configuration + * @param ppChanTlvOut Output parameter: Pointer to the start of the + * channel TLV portion of the output scan config + * @param pScanChanList Output parameter: Pointer to the resulting channel + * list to scan + * @param pMaxChanPerScan Output parameter: Number of channels to scan for + * each issuance of the firmware scan command + * @param pFilteredScan Output parameter: Flag indicating whether or not + * a BSSID or SSID filter is being sent in the + * command to firmware. Used to increase the number + * of channels sent in a scan command and to + * disable the firmware channel scan filter. + * @param pScanCurrentOnly Output parameter: Flag indicating whether or not + * we are only scanning our current active channel + * + * @return void + */ +static void +wlan_scan_setup_scan_config(wlan_private * priv, + const wlan_ioctl_user_scan_cfg * pUserScanIn, + wlan_scan_cmd_config * pScanCfgOut, + MrvlIEtypes_ChanListParamSet_t ** ppChanTlvOut, + ChanScanParamSet_t * pScanChanList, + int *pMaxChanPerScan, + BOOLEAN * pFilteredScan, + BOOLEAN * pScanCurrentOnly) +{ + wlan_adapter *Adapter = priv->adapter; + const u8 zeroMac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + MrvlIEtypes_NumProbes_t *pNumProbesTlv; + u8 *pTlvPos; + u16 numProbes; + u16 ssidLen; + int chanIdx; + int scanType; + int scanDur; + int channel; + int radioType; + int ssidIdx; + BOOLEAN ssidFilter; + + MrvlIEtypes_WildCardSsIdParamSet_t *pWildCardSsidTlv; + + /* The tlvBufferLen is calculated for each scan command. The TLVs added + * in this routine will be preserved since the routine that sends + * the command will append channelTLVs at *ppChanTlvOut. The difference + * between the *ppChanTlvOut and the tlvBuffer start will be used + * to calculate the size of anything we add in this routine. + */ + pScanCfgOut->tlvBufferLen = 0; + + /* Running tlv pointer. Assigned to ppChanTlvOut at end of function + * so later routines know where channels can be added to the command buf + */ + pTlvPos = pScanCfgOut->tlvBuffer; + + /* + * Set the initial scan paramters for progressive scanning. If a specific + * BSSID or SSID is used, the number of channels in the scan command + * will be increased to the absolute maximum + */ + *pMaxChanPerScan = MRVDRV_MAX_CHANNELS_PER_SCAN; + + /* Initialize the scan as un-filtered; the flag is later set to + * TRUE below if a SSID or BSSID filter is sent in the command + */ + *pFilteredScan = FALSE; + + /* Initialize the scan as not being only on the current channel. If + * the channel list is customized, only contains one channel, and + * is the active channel, this is set true and data flow is not halted. + */ + *pScanCurrentOnly = FALSE; + + if (pUserScanIn) { + + /* Default the ssidFilter flag to TRUE, set false under certain + * wildcard conditions and qualified by the existence of an SSID + * list before marking the scan as filtered + */ + ssidFilter = TRUE; + + /* Set the bss type scan filter, use Adapter setting if unset */ + pScanCfgOut->bssType = (pUserScanIn->bssType ? pUserScanIn->bssType : + Adapter->ScanMode); + + /* Set the number of probes to send, use Adapter setting if unset */ + numProbes = (pUserScanIn->numProbes ? pUserScanIn->numProbes : + Adapter->ScanProbes); + + /* + * Set the BSSID filter to the incoming configuration, + * if non-zero. If not set, it will remain disabled (all zeros). + */ + memcpy(pScanCfgOut->specificBSSID, + pUserScanIn->specificBSSID, + sizeof(pScanCfgOut->specificBSSID)); + + for (ssidIdx = 0; ((ssidIdx < NELEMENTS(pUserScanIn->ssidList)) + && (*pUserScanIn->ssidList[ssidIdx].ssid + || pUserScanIn->ssidList[ssidIdx].maxLen)); + ssidIdx++) { + + ssidLen = strlen(pUserScanIn->ssidList[ssidIdx].ssid) + 1; + + pWildCardSsidTlv = (MrvlIEtypes_WildCardSsIdParamSet_t *) pTlvPos; + pWildCardSsidTlv->Header.Type + = wlan_cpu_to_le16(TLV_TYPE_WILDCARDSSID); + pWildCardSsidTlv->Header.Len + = ssidLen + sizeof(pWildCardSsidTlv->MaxSsidLength); + pWildCardSsidTlv->MaxSsidLength + = pUserScanIn->ssidList[ssidIdx].maxLen; + + memcpy(pWildCardSsidTlv->SsId, + pUserScanIn->ssidList[ssidIdx].ssid, ssidLen); + + pTlvPos += (sizeof(pWildCardSsidTlv->Header) + + pWildCardSsidTlv->Header.Len); + + pWildCardSsidTlv->Header.Len + = wlan_cpu_to_le16(pWildCardSsidTlv->Header.Len); + + PRINTM(INFO, "Scan: ssidList[%d]: %s, %d\n", + ssidIdx, + pWildCardSsidTlv->SsId, pWildCardSsidTlv->MaxSsidLength); + + /* Empty wildcard ssid with a maxlen will match many or potentially + * all SSIDs (maxlen == 32), therefore do not treat the scan + * as filtered. + */ + if ((ssidLen == 0) && pWildCardSsidTlv->MaxSsidLength) { + ssidFilter = FALSE; + } + } + + /* + * The default number of channels sent in the command is low to + * ensure the response buffer from the firmware does not truncate + * scan results. That is not an issue with an SSID or BSSID + * filter applied to the scan results in the firmware. + */ + if ((ssidIdx && ssidFilter) + || memcmp(pScanCfgOut->specificBSSID, &zeroMac, sizeof(zeroMac))) { + *pFilteredScan = TRUE; + } + + } else { + pScanCfgOut->bssType = Adapter->ScanMode; + numProbes = Adapter->ScanProbes; + } + + /* If the input config or adapter has the number of Probes set, add tlv */ + if (numProbes) { + + PRINTM(INFO, "Scan: numProbes = %d\n", numProbes); + + pNumProbesTlv = (MrvlIEtypes_NumProbes_t *) pTlvPos; + pNumProbesTlv->Header.Type = wlan_cpu_to_le16(TLV_TYPE_NUMPROBES); + pNumProbesTlv->Header.Len = sizeof(pNumProbesTlv->NumProbes); + pNumProbesTlv->NumProbes = wlan_cpu_to_le16(numProbes); + + pTlvPos += sizeof(pNumProbesTlv->Header) + pNumProbesTlv->Header.Len; + + pNumProbesTlv->Header.Len = + wlan_cpu_to_le16(pNumProbesTlv->Header.Len); + } + + wlan_add_wps_probe_request_ie(priv, &pTlvPos); + + /* + * Set the output for the channel TLV to the address in the tlv buffer + * past any TLVs that were added in this fuction (SSID, numProbes). + * Channel TLVs will be added past this for each scan command, preserving + * the TLVs that were previously added. + */ + *ppChanTlvOut = (MrvlIEtypes_ChanListParamSet_t *) pTlvPos; + + if (pUserScanIn && pUserScanIn->chanList[0].chanNumber) { + + PRINTM(INFO, "Scan: Using supplied channel list\n"); + + for (chanIdx = 0; + chanIdx < WLAN_IOCTL_USER_SCAN_CHAN_MAX + && pUserScanIn->chanList[chanIdx].chanNumber; chanIdx++) { + + channel = pUserScanIn->chanList[chanIdx].chanNumber; + (pScanChanList + chanIdx)->ChanNumber = channel; + + radioType = pUserScanIn->chanList[chanIdx].radioType; + (pScanChanList + chanIdx)->RadioType = radioType; + + scanType = pUserScanIn->chanList[chanIdx].scanType; + + if (scanType == HostCmd_SCAN_TYPE_PASSIVE) { + (pScanChanList + chanIdx)->ChanScanMode.PassiveScan = TRUE; + } else { + (pScanChanList + chanIdx)->ChanScanMode.PassiveScan = FALSE; + } + + if (pUserScanIn->chanList[chanIdx].scanTime) { + scanDur = pUserScanIn->chanList[chanIdx].scanTime; + } else { + if (scanType == HostCmd_SCAN_TYPE_PASSIVE) { + scanDur = Adapter->PassiveScanTime; + } else if (*pFilteredScan) { + scanDur = Adapter->SpecificScanTime; + } else { + scanDur = Adapter->ActiveScanTime; + } + } + + (pScanChanList + chanIdx)->MinScanTime = + wlan_cpu_to_le16(scanDur); + (pScanChanList + chanIdx)->MaxScanTime = + wlan_cpu_to_le16(scanDur); + } + + /* Check if we are only scanning the current channel */ + if ((chanIdx == 1) + && (pUserScanIn->chanList[0].chanNumber + == priv->adapter->CurBssParams.BSSDescriptor.Channel)) { + *pScanCurrentOnly = TRUE; + PRINTM(INFO, "Scan: Scanning current channel only"); + } + + } else { + PRINTM(INFO, "Scan: Creating full region channel list\n"); + wlan_scan_create_channel_list(priv, pScanChanList, *pFilteredScan); + } +} + +/** + * @brief Construct and send multiple scan config commands to the firmware + * + * Previous routines have created a wlan_scan_cmd_config with any requested + * TLVs. This function splits the channel TLV into maxChanPerScan lists + * and sends the portion of the channel TLV along with the other TLVs + * to the wlan_cmd routines for execution in the firmware. + * + * @param priv A pointer to wlan_private structure + * @param maxChanPerScan Maximum number channels to be included in each + * scan command sent to firmware + * @param filteredScan Flag indicating whether or not a BSSID or SSID + * filter is being used for the firmware command + * scan command sent to firmware + * @param pScanCfgOut Scan configuration used for this scan. + * @param pChanTlvOut Pointer in the pScanCfgOut where the channel TLV + * should start. This is past any other TLVs that + * must be sent down in each firmware command. + * @param pScanChanList List of channels to scan in maxChanPerScan segments + * + * @return WLAN_STATUS_SUCCESS or error return otherwise + */ +static int +wlan_scan_channel_list(wlan_private * priv, + int maxChanPerScan, + BOOLEAN filteredScan, + wlan_scan_cmd_config * pScanCfgOut, + MrvlIEtypes_ChanListParamSet_t * pChanTlvOut, + ChanScanParamSet_t * pScanChanList) +{ + ChanScanParamSet_t *pTmpChan; + ChanScanParamSet_t *pStartChan; + u8 scanBand; + int doneEarly; + int tlvIdx; + int totalscantime; + int ret; + + ENTER(); + + if (pScanCfgOut == 0 || pChanTlvOut == 0 || pScanChanList == 0) { + PRINTM(INFO, "Scan: Null detect: %p, %p, %p\n", + pScanCfgOut, pChanTlvOut, pScanChanList); + return WLAN_STATUS_FAILURE; + } + + ret = WLAN_STATUS_SUCCESS; + + pChanTlvOut->Header.Type = wlan_cpu_to_le16(TLV_TYPE_CHANLIST); + + /* Set the temp channel struct pointer to the start of the desired list */ + pTmpChan = pScanChanList; + + /* Loop through the desired channel list, sending a new firmware scan + * commands for each maxChanPerScan channels (or for 1,6,11 individually + * if configured accordingly) + */ + while (pTmpChan->ChanNumber) { + + tlvIdx = 0; + totalscantime = 0; + pChanTlvOut->Header.Len = 0; + scanBand = pTmpChan->RadioType; + pStartChan = pTmpChan; + doneEarly = FALSE; + + /* Construct the Channel TLV for the scan command. Continue to + * insert channel TLVs until: + * - the tlvIdx hits the maximum configured per scan command + * - the next channel to insert is 0 (end of desired channel list) + * - doneEarly is set (controlling individual scanning of 1,6,11) + */ + while (tlvIdx < maxChanPerScan && pTmpChan->ChanNumber && !doneEarly) { + + PRINTM(INFO, "Scan: Chan(%3d), Radio(%d), Mode(%d,%d), Dur(%d)\n", + pTmpChan->ChanNumber, + pTmpChan->RadioType, + pTmpChan->ChanScanMode.PassiveScan, + pTmpChan->ChanScanMode.DisableChanFilt, + pTmpChan->MaxScanTime); + + /* Copy the current channel TLV to the command being prepared */ + memcpy(pChanTlvOut->ChanScanParam + tlvIdx, + pTmpChan, sizeof(pChanTlvOut->ChanScanParam)); + + /* Increment the TLV header length by the size appended */ + pChanTlvOut->Header.Len += sizeof(pChanTlvOut->ChanScanParam); + + /* + * The tlv buffer length is set to the number of bytes of the + * between the channel tlv pointer and the start of the + * tlv buffer. This compensates for any TLVs that were appended + * before the channel list. + */ + pScanCfgOut->tlvBufferLen = ((u8 *) pChanTlvOut + - pScanCfgOut->tlvBuffer); + + /* Add the size of the channel tlv header and the data length */ + pScanCfgOut->tlvBufferLen += (sizeof(pChanTlvOut->Header) + + pChanTlvOut->Header.Len); + + /* Increment the index to the channel tlv we are constructing */ + tlvIdx++; + + /* Count the total scan time per command */ + totalscantime += pTmpChan->MaxScanTime; + + doneEarly = FALSE; + + /* Stop the loop if the *current* channel is in the 1,6,11 set + * and we are not filtering on a BSSID or SSID. + */ + if (!filteredScan && (pTmpChan->ChanNumber == 1 + || pTmpChan->ChanNumber == 6 + || pTmpChan->ChanNumber == 11)) { + doneEarly = TRUE; + } + + /* Increment the tmp pointer to the next channel to be scanned */ + pTmpChan++; + + /* Stop the loop if the *next* channel is in the 1,6,11 set. + * This will cause it to be the only channel scanned on the next + * interation + */ + if (!filteredScan && (pTmpChan->ChanNumber == 1 + || pTmpChan->ChanNumber == 6 + || pTmpChan->ChanNumber == 11)) { + doneEarly = TRUE; + } + } + + /* The total scan time should be less than scan command timeout value */ + if (totalscantime > MRVDRV_MAX_TOTAL_SCAN_TIME) { + PRINTM(MSG, + "Total scan time %d ms is over limit (%d ms), scan skipped\n", + totalscantime, MRVDRV_MAX_TOTAL_SCAN_TIME); + ret = WLAN_STATUS_FAILURE; + break; + } + + pChanTlvOut->Header.Len = wlan_cpu_to_le16(pChanTlvOut->Header.Len); + + /* Send the scan command to the firmware with the specified cfg */ + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SCAN, 0, + HostCmd_OPTION_WAITFORRSP, 0, + pScanCfgOut); + } + + LEAVE(); + + if (ret) { + return WLAN_STATUS_FAILURE; + } + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Internal function used to start a scan based on an input config + * + * Use the input user scan configuration information when provided in + * order to send the appropriate scan commands to firmware to populate or + * update the internal driver scan table + * + * @param priv A pointer to wlan_private structure + * @param pUserScanIn Pointer to the input configuration for the requested + * scan. + * + * @return WLAN_STATUS_SUCCESS or < 0 if error + */ +static int +wlan_scan_networks(wlan_private * priv, + const wlan_ioctl_user_scan_cfg * pUserScanIn) +{ + wlan_adapter *Adapter = priv->adapter; + MrvlIEtypes_ChanListParamSet_t *pChanTlvOut; + + ChanScanParamSet_t scanChanList[WLAN_IOCTL_USER_SCAN_CHAN_MAX]; + wlan_scan_cmd_config_tlv scanCfgOut; + BOOLEAN keepPreviousScan; + BOOLEAN filteredScan; + BOOLEAN scanCurrentChanOnly; + int maxChanPerScan; + int ret; + BOOLEAN bBgScan; + + ENTER(); + + memset(scanChanList, 0x00, sizeof(scanChanList)); + memset(&scanCfgOut, 0x00, sizeof(scanCfgOut)); + + keepPreviousScan = FALSE; + + wlan_scan_setup_scan_config(priv, + pUserScanIn, + &scanCfgOut.config, + &pChanTlvOut, + scanChanList, + &maxChanPerScan, + &filteredScan, &scanCurrentChanOnly); + + if (pUserScanIn) { + keepPreviousScan = pUserScanIn->keepPreviousScan; + } + + if (keepPreviousScan == FALSE) { + memset(Adapter->ScanTable, 0x00, + sizeof(BSSDescriptor_t) * MRVDRV_MAX_BSSID_LIST); + Adapter->NumInScanTable = 0; + Adapter->pBeaconBufEnd = Adapter->beaconBuffer; + } + + /* Keep the data path active if we are only scanning our current channel */ + if (!scanCurrentChanOnly) { + PRINTM(INFO, "Scan: WMM Queue stop\n"); + priv->wlan_dev.netdev->watchdog_timeo = MRVDRV_SCAN_WATCHDOG_TIMEOUT; + /* If WMM queues are in use, only stop the internal data queues */ + wmm_stop_queue(priv); + } + + bBgScan = priv->adapter->bgScanConfig->Enable; + if (priv->adapter->bgScanConfig->Enable == TRUE) { + wlan_bg_scan_enable(priv, FALSE); + } + + ret = wlan_scan_channel_list(priv, + maxChanPerScan, + filteredScan, + &scanCfgOut.config, + pChanTlvOut, scanChanList); + + /* Process the resulting scan table: + * - Remove any bad ssids + * - Update our current BSS information from scan data + */ + wlan_scan_process_results(priv); + + if (bBgScan == TRUE) { + wlan_bg_scan_enable(priv, TRUE); + } + + PRINTM(INFO, "Scan: WMM Queue start\n"); + + priv->wlan_dev.netdev->watchdog_timeo = MRVDRV_DEFAULT_WATCHDOG_TIMEOUT; + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + wmm_start_queue(priv); + } + os_carrier_on(priv); + os_start_queue(priv); + + LEAVE(); + return ret; +} + +/** + * @brief Create a brief scan resp to relay basic BSS info to the app layer + * + * When the beacon/probe response has not been buffered, use the saved BSS + * information available to provide a minimum response for the application + * ioctl retrieval routines. Include: + * - Timestamp + * - Beacon Period + * - Capabilities (including WMM Element if available) + * - SSID + * + * @param ppBuffer Output parameter: Buffer used to create basic scan rsp + * @param pBSSDesc Pointer to a BSS entry in the scan table to create + * scan response from for delivery to the application layer + * + * @return void + */ +static void +wlan_scan_create_brief_table_entry(u8 ** ppBuffer, BSSDescriptor_t * pBSSDesc) +{ + u8 *pTmpBuf = *ppBuffer; + u8 tmpSSIDHdr[2]; + u8 ieLen; + + if (copy_to_user(pTmpBuf, pBSSDesc->TimeStamp, + sizeof(pBSSDesc->TimeStamp))) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + pTmpBuf += sizeof(pBSSDesc->TimeStamp); + + if (copy_to_user(pTmpBuf, &pBSSDesc->BeaconPeriod, + sizeof(pBSSDesc->BeaconPeriod))) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + pTmpBuf += sizeof(pBSSDesc->BeaconPeriod); + + if (copy_to_user(pTmpBuf, &pBSSDesc->Cap, sizeof(pBSSDesc->Cap))) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + pTmpBuf += sizeof(pBSSDesc->Cap); + + tmpSSIDHdr[0] = 0; /* Element ID for SSID is zero */ + tmpSSIDHdr[1] = pBSSDesc->Ssid.SsidLength; + if (copy_to_user(pTmpBuf, tmpSSIDHdr, sizeof(tmpSSIDHdr))) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + pTmpBuf += sizeof(tmpSSIDHdr); + + if (copy_to_user(pTmpBuf, pBSSDesc->Ssid.Ssid, pBSSDesc->Ssid.SsidLength)) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + pTmpBuf += pBSSDesc->Ssid.SsidLength; + + if (pBSSDesc->wmmIE.VendHdr.ElementId == WMM_IE) { + ieLen = sizeof(IEEEtypes_Header_t) + pBSSDesc->wmmIE.VendHdr.Len; + if (copy_to_user(pTmpBuf, &pBSSDesc->wmmIE, ieLen)) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + + pTmpBuf += ieLen; + } + + if (pBSSDesc->wpaIE.VendHdr.ElementId == WPA_IE) { + ieLen = sizeof(IEEEtypes_Header_t) + pBSSDesc->wpaIE.VendHdr.Len; + if (copy_to_user(pTmpBuf, &pBSSDesc->wpaIE, ieLen)) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + + pTmpBuf += ieLen; + } + + if (pBSSDesc->rsnIE.IeeeHdr.ElementId == RSN_IE) { + ieLen = sizeof(IEEEtypes_Header_t) + pBSSDesc->rsnIE.IeeeHdr.Len; + if (copy_to_user(pTmpBuf, &pBSSDesc->rsnIE, ieLen)) { + PRINTM(INFO, "Copy to user failed\n"); + return; + } + + pTmpBuf += ieLen; + } + + *ppBuffer = pTmpBuf; +} + +/** + * @brief Inspect the scan response buffer for pointers to expected TLVs + * + * TLVs can be included at the end of the scan response BSS information. + * Parse the data in the buffer for pointers to TLVs that can potentially + * be passed back in the response + * + * @param pTlv Pointer to the start of the TLV buffer to parse + * @param tlvBufSize Size of the TLV buffer + * @param ppTsfTlv Output parameter: Pointer to the TSF TLV if found + * + * @return void + */ +static void +wlan_ret_802_11_scan_get_tlv_ptrs(MrvlIEtypes_Data_t * pTlv, + int tlvBufSize, + MrvlIEtypes_TsfTimestamp_t ** ppTsfTlv) +{ + MrvlIEtypes_Data_t *pCurrentTlv; + int tlvBufLeft; + u16 tlvType; + u16 tlvLen; + + pCurrentTlv = pTlv; + tlvBufLeft = tlvBufSize; + *ppTsfTlv = NULL; + + PRINTM(INFO, "SCAN_RESP: tlvBufSize = %d\n", tlvBufSize); + HEXDUMP("SCAN_RESP: TLV Buf", (u8 *) pTlv, tlvBufSize); + + while (tlvBufLeft >= sizeof(MrvlIEtypesHeader_t)) { + tlvType = wlan_le16_to_cpu(pCurrentTlv->Header.Type); + tlvLen = wlan_le16_to_cpu(pCurrentTlv->Header.Len); + + switch (tlvType) { + case TLV_TYPE_TSFTIMESTAMP: + PRINTM(INFO, "SCAN_RESP: TSF Timestamp TLV, len = %d\n", tlvLen); + *ppTsfTlv = (MrvlIEtypes_TsfTimestamp_t *) pCurrentTlv; + break; + + default: + PRINTM(INFO, "SCAN_RESP: Unhandled TLV = %d\n", tlvType); + /* Give up, this seems corrupted */ + return; + } /* switch */ + + tlvBufLeft -= (sizeof(pTlv->Header) + tlvLen); + pCurrentTlv = (MrvlIEtypes_Data_t *) (pCurrentTlv->Data + tlvLen); + } /* while */ +} + +/** + * @brief Interpret a BSS scan response returned from the firmware + * + * Parse the various fixed fields and IEs passed back for a a BSS probe + * response or beacon from the scan command. Record information as needed + * in the scan table BSSDescriptor_t for that entry. + * + * @param pBSSIDEntry Output parameter: Pointer to the BSS Entry + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +InterpretBSSDescriptionWithIE(BSSDescriptor_t * pBSSEntry, + u8 ** pBeaconInfo, int *bytesLeft) +{ + IEEEtypes_ElementId_e elemID; + IEEEtypes_FhParamSet_t *pFH; + IEEEtypes_DsParamSet_t *pDS; + IEEEtypes_CfParamSet_t *pCF; + IEEEtypes_IbssParamSet_t *pIbss; + IEEEtypes_CapInfo_t *pCap; + WLAN_802_11_FIXED_IEs fixedIE; + u8 *pCurrentPtr; + u8 *pRate; + u8 elemLen; + u16 totalIeLen; + u8 bytesToCopy; + u8 rateSize; + u16 beaconSize; + BOOLEAN foundDataRateIE; + int bytesLeftForCurrentBeacon; + IEEEtypes_ERPInfo_t *pERPInfo; + + IEEEtypes_VendorSpecific_t *pVendorIe; + const u8 wpa_oui[4] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wmm_oui[4] = { 0x00, 0x50, 0xf2, 0x02 }; + const u8 wps_oui[4] = { 0x00, 0x50, 0xf2, 0x04 }; + + IEEEtypes_CountryInfoSet_t *pcountryinfo; + + ENTER(); + + foundDataRateIE = FALSE; + rateSize = 0; + beaconSize = 0; + + if (*bytesLeft >= sizeof(beaconSize)) { + /* Extract & convert beacon size from the command buffer */ + memcpy(&beaconSize, *pBeaconInfo, sizeof(beaconSize)); + beaconSize = wlan_le16_to_cpu(beaconSize); + *bytesLeft -= sizeof(beaconSize); + *pBeaconInfo += sizeof(beaconSize); + } + + if (beaconSize == 0 || beaconSize > *bytesLeft) { + + *pBeaconInfo += *bytesLeft; + *bytesLeft = 0; + + return WLAN_STATUS_FAILURE; + } + + /* Initialize the current working beacon pointer for this BSS iteration */ + pCurrentPtr = *pBeaconInfo; + + /* Advance the return beacon pointer past the current beacon */ + *pBeaconInfo += beaconSize; + *bytesLeft -= beaconSize; + + bytesLeftForCurrentBeacon = beaconSize; + + memcpy(pBSSEntry->MacAddress, pCurrentPtr, MRVDRV_ETH_ADDR_LEN); + PRINTM(INFO, "InterpretIE: AP MAC Addr-%02x:%02x:%02x:%02x:%02x:%02x\n", + pBSSEntry->MacAddress[0], pBSSEntry->MacAddress[1], + pBSSEntry->MacAddress[2], pBSSEntry->MacAddress[3], + pBSSEntry->MacAddress[4], pBSSEntry->MacAddress[5]); + + pCurrentPtr += MRVDRV_ETH_ADDR_LEN; + bytesLeftForCurrentBeacon -= MRVDRV_ETH_ADDR_LEN; + + if (bytesLeftForCurrentBeacon < 12) { + PRINTM(INFO, "InterpretIE: Not enough bytes left\n"); + return WLAN_STATUS_FAILURE; + } + + /* + * next 4 fields are RSSI, time stamp, beacon interval, + * and capability information + */ + + /* RSSI is 1 byte long */ + pBSSEntry->Rssi = wlan_le32_to_cpu((LONG) (*pCurrentPtr)); + PRINTM(INFO, "InterpretIE: RSSI=%02X\n", *pCurrentPtr); + pCurrentPtr += 1; + bytesLeftForCurrentBeacon -= 1; + + /* + * The RSSI is not part of the beacon/probe response. After we have + * advanced pCurrentPtr past the RSSI field, save the remaining + * data for use at the application layer + */ + pBSSEntry->pBeaconBuf = pCurrentPtr; + pBSSEntry->beaconBufSize = bytesLeftForCurrentBeacon; + + /* time stamp is 8 bytes long */ + memcpy(fixedIE.Timestamp, pCurrentPtr, 8); + memcpy(pBSSEntry->TimeStamp, pCurrentPtr, 8); + pCurrentPtr += 8; + bytesLeftForCurrentBeacon -= 8; + + /* beacon interval is 2 bytes long */ + memcpy(&fixedIE.BeaconInterval, pCurrentPtr, 2); + pBSSEntry->BeaconPeriod = wlan_le16_to_cpu(fixedIE.BeaconInterval); + pCurrentPtr += 2; + bytesLeftForCurrentBeacon -= 2; + + /* capability information is 2 bytes long */ + memcpy(&fixedIE.Capabilities, pCurrentPtr, 2); + PRINTM(INFO, "InterpretIE: fixedIE.Capabilities=0x%X\n", + fixedIE.Capabilities); + fixedIE.Capabilities = wlan_le16_to_cpu(fixedIE.Capabilities); + pCap = (IEEEtypes_CapInfo_t *) & fixedIE.Capabilities; + memcpy(&pBSSEntry->Cap, pCap, sizeof(IEEEtypes_CapInfo_t)); + pCurrentPtr += 2; + bytesLeftForCurrentBeacon -= 2; + + /* rest of the current buffer are IE's */ + PRINTM(INFO, "InterpretIE: IELength for this AP = %d\n", + bytesLeftForCurrentBeacon); + + HEXDUMP("InterpretIE: IE info", (u8 *) pCurrentPtr, + bytesLeftForCurrentBeacon); + + if (pCap->Privacy) { + PRINTM(INFO, "InterpretIE: AP WEP enabled\n"); + pBSSEntry->Privacy = Wlan802_11PrivFilter8021xWEP; + } else { + pBSSEntry->Privacy = Wlan802_11PrivFilterAcceptAll; + } + + if (pCap->Ibss == 1) { + pBSSEntry->InfrastructureMode = Wlan802_11IBSS; + } else { + pBSSEntry->InfrastructureMode = Wlan802_11Infrastructure; + } + + /* process variable IE */ + while (bytesLeftForCurrentBeacon >= 2) { + elemID = (IEEEtypes_ElementId_e) (*((u8 *) pCurrentPtr)); + elemLen = *((u8 *) pCurrentPtr + 1); + totalIeLen = elemLen + sizeof(IEEEtypes_Header_t); + + if (bytesLeftForCurrentBeacon < elemLen) { + PRINTM(INFO, "InterpretIE: Error in processing IE, " + "bytes left < IE length\n"); + bytesLeftForCurrentBeacon = 0; + continue; + } + + switch (elemID) { + + case SSID: + pBSSEntry->Ssid.SsidLength = elemLen; + memcpy(pBSSEntry->Ssid.Ssid, (pCurrentPtr + 2), elemLen); + PRINTM(INFO, "InterpretIE: Ssid: %-32s\n", pBSSEntry->Ssid.Ssid); + break; + + case SUPPORTED_RATES: + memcpy(pBSSEntry->DataRates, pCurrentPtr + 2, elemLen); + memcpy(pBSSEntry->SupportedRates, pCurrentPtr + 2, elemLen); + HEXDUMP("InterpretIE: SupportedRates:", + pBSSEntry->SupportedRates, elemLen); + rateSize = elemLen; + foundDataRateIE = TRUE; + break; + + case EXTRA_IE: + PRINTM(INFO, "InterpretIE: EXTRA_IE Found!\n"); + pBSSEntry->extra_ie = 1; + break; + + case FH_PARAM_SET: + pFH = (IEEEtypes_FhParamSet_t *) pCurrentPtr; + pBSSEntry->NetworkTypeInUse = Wlan802_11FH; + memcpy(&pBSSEntry->PhyParamSet.FhParamSet, pFH, + sizeof(IEEEtypes_FhParamSet_t)); + pBSSEntry->PhyParamSet.FhParamSet.DwellTime + = + wlan_le16_to_cpu(pBSSEntry->PhyParamSet.FhParamSet.DwellTime); + break; + + case DS_PARAM_SET: + pDS = (IEEEtypes_DsParamSet_t *) pCurrentPtr; + + pBSSEntry->NetworkTypeInUse = Wlan802_11DS; + pBSSEntry->Channel = pDS->CurrentChan; + + memcpy(&pBSSEntry->PhyParamSet.DsParamSet, pDS, + sizeof(IEEEtypes_DsParamSet_t)); + break; + + case CF_PARAM_SET: + pCF = (IEEEtypes_CfParamSet_t *) pCurrentPtr; + memcpy(&pBSSEntry->SsParamSet.CfParamSet, pCF, + sizeof(IEEEtypes_CfParamSet_t)); + break; + + case IBSS_PARAM_SET: + pIbss = (IEEEtypes_IbssParamSet_t *) pCurrentPtr; + pBSSEntry->ATIMWindow = wlan_le32_to_cpu(pIbss->AtimWindow); + memcpy(&pBSSEntry->SsParamSet.IbssParamSet, pIbss, + sizeof(IEEEtypes_IbssParamSet_t)); + break; + + /* Handle Country Info IE */ + case COUNTRY_INFO: + pcountryinfo = (IEEEtypes_CountryInfoSet_t *) pCurrentPtr; + + if (pcountryinfo->Len < sizeof(pcountryinfo->CountryCode) || + pcountryinfo->Len + 2 > + sizeof(IEEEtypes_CountryInfoFullSet_t)) { + PRINTM(INFO, + "InterpretIE: 11D- Err " + "CountryInfo len =%d min=%d max=%d\n", + pcountryinfo->Len, sizeof(pcountryinfo->CountryCode), + sizeof(IEEEtypes_CountryInfoFullSet_t)); + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + memcpy(&pBSSEntry->CountryInfo, + pcountryinfo, pcountryinfo->Len + 2); + HEXDUMP("InterpretIE: 11D- CountryInfo:", + (u8 *) pcountryinfo, (u32) (pcountryinfo->Len + 2)); + break; + case ERP_INFO: + pERPInfo = (IEEEtypes_ERPInfo_t *) pCurrentPtr; + pBSSEntry->ERPFlags = pERPInfo->ERPFlags; + break; + case EXTENDED_SUPPORTED_RATES: + /* + * only process extended supported rate + * if data rate is already found. + * data rate IE should come before + * extended supported rate IE + */ + if (foundDataRateIE) { + if ((elemLen + rateSize) > WLAN_SUPPORTED_RATES) { + bytesToCopy = (WLAN_SUPPORTED_RATES - rateSize); + } else { + bytesToCopy = elemLen; + } + + pRate = (u8 *) pBSSEntry->DataRates; + pRate += rateSize; + memcpy(pRate, pCurrentPtr + 2, bytesToCopy); + + pRate = (u8 *) pBSSEntry->SupportedRates; + pRate += rateSize; + memcpy(pRate, pCurrentPtr + 2, bytesToCopy); + } + HEXDUMP("InterpretIE: ExtSupportedRates:", + pBSSEntry->SupportedRates, elemLen + rateSize); + break; + + case VENDOR_SPECIFIC_221: + pVendorIe = (IEEEtypes_VendorSpecific_t *) pCurrentPtr; + + if ((!memcmp + (pVendorIe->VendHdr.Oui, wpa_oui, + sizeof(pVendorIe->VendHdr.Oui))) + && (pVendorIe->VendHdr.OuiType == wpa_oui[3])) { + pBSSEntry->wpaIE.VendHdr.Len + = (MIN(totalIeLen, sizeof(pBSSEntry->wpaIE)) + - sizeof(IEEEtypes_Header_t)); + + memcpy(&pBSSEntry->wpaIE, + pCurrentPtr, + (pBSSEntry->wpaIE.VendHdr.Len + + sizeof(IEEEtypes_Header_t))); + + HEXDUMP("InterpretIE: Resp WPA_IE", + (u8 *) & pBSSEntry->wpaIE, + (pBSSEntry->wpaIE.VendHdr.Len + + sizeof(IEEEtypes_Header_t))); + } else + if ((!memcmp + (pVendorIe->VendHdr.Oui, wmm_oui, + sizeof(pVendorIe->VendHdr.Oui))) + && (pVendorIe->VendHdr.OuiType == wmm_oui[3])) { + if (totalIeLen == sizeof(IEEEtypes_WmmParameter_t) + || totalIeLen == sizeof(IEEEtypes_WmmInfo_t)) { + + /* Only accept and copy the WMM IE if it matches + * the size expected for the WMM Info IE or the + * WMM Parameter IE. + */ + memcpy((u8 *) & pBSSEntry->wmmIE, pCurrentPtr, + totalIeLen); + HEXDUMP("InterpretIE: Resp WMM_IE", + (u8 *) & pBSSEntry->wmmIE, totalIeLen); + } + } else + if ((!memcmp + (pVendorIe->VendHdr.Oui, wps_oui, + sizeof(pVendorIe->VendHdr.Oui))) + && (pVendorIe->VendHdr.OuiType == wps_oui[3])) { + memcpy((u8 *) & pBSSEntry->wpsIE, pCurrentPtr, totalIeLen); + HEXDUMP("InterpretIE: Resp WPS_IE", + (u8 *) & pBSSEntry->wpsIE, totalIeLen); + } + break; + case RSN_IE: + pBSSEntry->rsnIE.IeeeHdr.Len + = (MIN(totalIeLen, sizeof(pBSSEntry->rsnIE)) + - sizeof(IEEEtypes_Header_t)); + + memcpy(&pBSSEntry->rsnIE, + pCurrentPtr, + pBSSEntry->rsnIE.IeeeHdr.Len + sizeof(IEEEtypes_Header_t)); + + HEXDUMP("InterpretIE: Resp RSN_IE", + (u8 *) & pBSSEntry->rsnIE, + pBSSEntry->rsnIE.IeeeHdr.Len + + sizeof(IEEEtypes_Header_t)); + break; + } + + pCurrentPtr += elemLen + 2; + + /* need to account for IE ID and IE Len */ + bytesLeftForCurrentBeacon -= (elemLen + 2); + + } /* while (bytesLeftForCurrentBeacon > 2) */ + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Compare two SSIDs + * + * @param ssid1 A pointer to ssid to compare + * @param ssid2 A pointer to ssid to compare + * + * @return 0--ssid is same, otherwise is different + */ +int +SSIDcmp(WLAN_802_11_SSID * ssid1, WLAN_802_11_SSID * ssid2) +{ + if (!ssid1 || !ssid2) + return -1; + + if (ssid1->SsidLength != ssid2->SsidLength) + return -1; + + return memcmp(ssid1->Ssid, ssid2->Ssid, ssid1->SsidLength); +} + +/** + * @brief This function finds a specific compatible BSSID in the scan list + * + * @param Adapter A pointer to wlan_adapter + * @param bssid BSSID to find in the scan list + * @param mode Network mode: Infrastructure or IBSS + * + * @return index in BSSID list, or error return code (< 0) + */ +int +FindBSSIDInList(wlan_adapter * Adapter, u8 * bssid, int mode) +{ + int ret = -ENETUNREACH; + int i; + + if (!bssid) + return -EFAULT; + + PRINTM(INFO, "FindBSSID: Num of BSSIDs = %d\n", Adapter->NumInScanTable); + + /* Look through the scan table for a compatible match. The ret return + * variable will be equal to the index in the scan table (greater + * than zero) if the network is compatible. The loop will continue + * past a matched bssid that is not compatible in case there is an + * AP with multiple SSIDs assigned to the same BSSID + */ + for (i = 0; ret < 0 && i < Adapter->NumInScanTable; i++) { + if (!memcmp(Adapter->ScanTable[i].MacAddress, bssid, ETH_ALEN)) { + switch (mode) { + case Wlan802_11Infrastructure: + case Wlan802_11IBSS: + ret = IsNetworkCompatible(Adapter, i, mode); + break; + default: + ret = i; + break; + } + } + } + + if (ret >= 0) { + if (find_cfp_by_band_and_channel + (Adapter, 0, Adapter->ScanTable[ret].Channel) == NULL) { + ret = -ENETUNREACH; + } + } + return ret; +} + +/** + * @brief This function finds ssid in ssid list. + * + * @param Adapter A pointer to wlan_adapter + * @param ssid SSID to find in the list + * @param bssid BSSID to qualify the SSID selection (if provided) + * @param mode Network mode: Infrastructure or IBSS + * + * @return index in BSSID list + */ +int +FindSSIDInList(wlan_adapter * Adapter, WLAN_802_11_SSID * ssid, + u8 * bssid, int mode) +{ + int net = -ENETUNREACH; + u8 bestrssi = 0; + int i, j; + + PRINTM(INFO, "Num of Entries in Table = %d\n", Adapter->NumInScanTable); + + /* Loop through the table until the maximum is reached or until a match + * is found based on the bssid field comparison + */ + for (i = 0; + i < Adapter->NumInScanTable && (bssid == NULL || (bssid && net < 0)); + i++) { + + if (!SSIDcmp(&Adapter->ScanTable[i].Ssid, ssid) && ((bssid == NULL) + || + !memcmp(Adapter-> + ScanTable + [i]. + MacAddress, + bssid, + ETH_ALEN))) + { + switch (mode) { + case Wlan802_11Infrastructure: + case Wlan802_11IBSS: + j = IsNetworkCompatible(Adapter, i, mode); + + if (j >= 0) { + if (SCAN_RSSI(Adapter->ScanTable[i].Rssi) > bestrssi) { + bestrssi = SCAN_RSSI(Adapter->ScanTable[i].Rssi); + net = i; + } + } else { + if (net == -ENETUNREACH) { + net = j; + } + } + break; + case Wlan802_11AutoUnknown: + default: + /* Do not check compatibility if the mode requested is + * AutoUnknown. Allows generic find to work without + * verifying against the Adapter security settings + */ + if (SCAN_RSSI(Adapter->ScanTable[i].Rssi) > bestrssi) { + bestrssi = SCAN_RSSI(Adapter->ScanTable[i].Rssi); + net = i; + } + break; + } + } + } + if (net >= 0) { + if (find_cfp_by_band_and_channel + (Adapter, 0, Adapter->ScanTable[net].Channel) == NULL) { + net = -ENETUNREACH; + } + } + return net; +} + +/** + * @brief This function finds the best SSID in the Scan List + * + * Search the scan table for the best SSID that also matches the current + * adapter network preference (infrastructure or adhoc) + * + * @param Adapter A pointer to wlan_adapter + * + * @return index in BSSID list + */ +int +FindBestSSIDInList(wlan_adapter * Adapter) +{ + int mode = Adapter->InfrastructureMode; + int bestnet = -ENETUNREACH; + u8 bestrssi = 0; + int i; + + ENTER(); + + PRINTM(INFO, "Num of BSSIDs = %d\n", Adapter->NumInScanTable); + + for (i = 0; i < Adapter->NumInScanTable; i++) { + switch (mode) { + case Wlan802_11Infrastructure: + case Wlan802_11IBSS: + if (IsNetworkCompatible(Adapter, i, mode) >= 0) { + if (SCAN_RSSI(Adapter->ScanTable[i].Rssi) > bestrssi) { + bestrssi = SCAN_RSSI(Adapter->ScanTable[i].Rssi); + bestnet = i; + } + } + break; + case Wlan802_11AutoUnknown: + default: + if (SCAN_RSSI(Adapter->ScanTable[i].Rssi) > bestrssi) { + bestrssi = SCAN_RSSI(Adapter->ScanTable[i].Rssi); + bestnet = i; + } + break; + } + } + + LEAVE(); + return bestnet; +} + +/** + * @brief Find the AP with specific ssid in the scan list + * + * @param priv A pointer to wlan_private structure + * @param pSSID A pointer to AP's ssid + * + * @return WLAN_STATUS_SUCCESS--success, otherwise--fail + */ +int +FindBestNetworkSsid(wlan_private * priv, WLAN_802_11_SSID * pSSID) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + BSSDescriptor_t *pReqBSSID; + int i; + + ENTER(); + + memset(pSSID, 0, sizeof(WLAN_802_11_SSID)); + + wlan_scan_networks(priv, NULL); + + i = FindBestSSIDInList(Adapter); + + if (i >= 0) { + + pReqBSSID = &Adapter->ScanTable[i]; + memcpy(pSSID, &pReqBSSID->Ssid, sizeof(WLAN_802_11_SSID)); + + /* Make sure we are in the right mode */ + if (Adapter->InfrastructureMode == Wlan802_11AutoUnknown) { + Adapter->InfrastructureMode = pReqBSSID->InfrastructureMode; + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, + OID_802_11_INFRASTRUCTURE_MODE, NULL); + + if (ret) { + LEAVE(); + return ret; + } + } + } + + if (!pSSID->SsidLength) { + ret = WLAN_STATUS_FAILURE; + } + + LEAVE(); + return ret; +} + +/** + * @brief Delete a specific indexed entry from the scan table. + * + * Delete the scan table entry indexed by tableIdx. Compact the remaining + * entries and adjust any buffering of beacon/probe response data + * if needed. + * + * @param priv A pointer to wlan_private structure + * @param tableIdx Scan table entry index to delete from the table + * + * @return void + * + * @pre tableIdx must be an index to a valid entry + */ +static void +wlan_scan_delete_table_entry(wlan_private * priv, int tableIdx) +{ + wlan_adapter *Adapter = priv->adapter; + int delIdx; + uint beaconBufAdj; + u8 *pBeaconBuf; + + /* Shift the saved beacon buffer data for the scan table back over the + * entry being removed. Update the end of buffer pointer. Save the + * deleted buffer allocation size for pointer adjustments for entries + * compacted after the deleted index. + */ + beaconBufAdj = Adapter->ScanTable[tableIdx].beaconBufSizeMax; + + PRINTM(INFO, "Scan: Delete Entry %d, beacon buffer removal = %d bytes\n", + tableIdx, beaconBufAdj); + + /* Check if the table entry had storage allocated for its beacon */ + if (beaconBufAdj) { + pBeaconBuf = Adapter->ScanTable[tableIdx].pBeaconBuf; + + /* Remove the entry's buffer space, decrement the table end pointer + * by the amount we are removing + */ + Adapter->pBeaconBufEnd -= beaconBufAdj; + + PRINTM(INFO, + "Scan: Delete Entry %d, compact data: %p <- %p (sz = %d)\n", + tableIdx, + pBeaconBuf, + pBeaconBuf + beaconBufAdj, + Adapter->pBeaconBufEnd - pBeaconBuf); + + /* Compact data storage. Copy all data after the deleted entry's + * end address (pBeaconBuf + beaconBufAdj) back to the original + * start address (pBeaconBuf). + * + * Scan table entries affected by the move will have their entry + * pointer adjusted below. + * + * Use memmove since the dest/src memory regions overlap. + */ + memmove(pBeaconBuf, + pBeaconBuf + beaconBufAdj, + Adapter->pBeaconBufEnd - pBeaconBuf); + } + + PRINTM(INFO, "Scan: Delete Entry %d, NumInScanTable = %d\n", + tableIdx, Adapter->NumInScanTable); + + /* Shift all of the entries after the tableIdx back by one, compacting + * the table and removing the requested entry + */ + for (delIdx = tableIdx; (delIdx + 1) < Adapter->NumInScanTable; delIdx++) { + /* Copy the next entry over this one */ + memcpy(Adapter->ScanTable + delIdx, + Adapter->ScanTable + delIdx + 1, sizeof(BSSDescriptor_t)); + + /* Adjust this entry's pointer to its beacon buffer based on the + * removed/compacted entry from the deleted index. Don't decrement + * if the buffer pointer is NULL (no data stored for this entry). + */ + if (Adapter->ScanTable[delIdx].pBeaconBuf) { + Adapter->ScanTable[delIdx].pBeaconBuf -= beaconBufAdj; + } + } + + /* The last entry is invalid now that it has been deleted or moved back */ + memset(Adapter->ScanTable + Adapter->NumInScanTable - 1, + 0x00, sizeof(BSSDescriptor_t)); + + Adapter->NumInScanTable--; +} + +/** + * @brief Delete all occurrences of a given SSID from the scan table + * + * Iterate through the scan table and delete all entries that match a given + * SSID. Compact the remaining scan table entries. + * + * @param priv A pointer to wlan_private structure + * @param pDelSSID Pointer to an SSID struct to use in deleting all + * matching SSIDs from the scan table + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + * + */ +static int +wlan_scan_delete_ssid_table_entry(wlan_private * priv, + WLAN_802_11_SSID * pDelSSID) +{ + int tableIdx; + int retval = WLAN_STATUS_FAILURE; + + ENTER(); + + PRINTM(INFO, "Scan: Delete Ssid Entry: %-32s\n", pDelSSID->Ssid); + + /* If the requested SSID is found in the table, delete it. Then keep + * searching the table for multiple entires for the SSID until no + * more are found + */ + while ((tableIdx = FindSSIDInList(priv->adapter, + pDelSSID, + NULL, Wlan802_11AutoUnknown)) >= 0) { + PRINTM(INFO, "Scan: Delete Ssid Entry: Found Idx = %d\n", tableIdx); + retval = WLAN_STATUS_SUCCESS; + wlan_scan_delete_table_entry(priv, tableIdx); + } + + LEAVE(); + + return retval; +} + +/** + * @brief Scan Network + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_set_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + union iwreq_data wrqu; +#if WIRELESS_EXT >= 18 + struct iw_scan_req *req; + struct iw_point *dwrq = (struct iw_point *) vwrq; + wlan_ioctl_user_scan_cfg scanCfg; +#endif + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } +#ifdef REASSOCIATION + if (OS_ACQ_SEMAPHORE_BLOCK(&Adapter->ReassocSem)) { + PRINTM(ERROR, "Acquire semaphore error, wlan_set_scan\n"); + return -EBUSY; + } +#endif +#if WIRELESS_EXT >= 18 + if ((dwrq->flags & IW_SCAN_THIS_ESSID) && + (dwrq->length == sizeof(struct iw_scan_req))) { + req = (struct iw_scan_req *) extra; + if (req->essid_len <= WLAN_MAX_SSID_LENGTH) { + memset(&scanCfg, 0x00, sizeof(scanCfg)); + memcpy(scanCfg.ssidList[0].ssid, (u8 *) req->essid, + req->essid_len); + if (!wlan_scan_networks(priv, &scanCfg)) { + memset(&wrqu, 0, sizeof(union iwreq_data)); + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWSCAN, &wrqu, + NULL); + + } + } + } else { +#endif + + if (!wlan_scan_networks(priv, NULL)) { + memset(&wrqu, 0, sizeof(union iwreq_data)); + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWSCAN, &wrqu, + NULL); + + } +#if WIRELESS_EXT >= 18 + } +#endif + +#ifdef REASSOCIATION + OS_REL_SEMAPHORE(&Adapter->ReassocSem); +#endif + + if (Adapter->SurpriseRemoved) + return WLAN_STATUS_FAILURE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Send a scan command for all available channels filtered on a spec + * + * @param priv A pointer to wlan_private structure + * @param pRequestedSSID A pointer to AP's ssid + * + * @return WLAN_STATUS_SUCCESS-success, otherwise fail + */ +int +SendSpecificSSIDScan(wlan_private * priv, WLAN_802_11_SSID * pRequestedSSID) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_ioctl_user_scan_cfg scanCfg; + + ENTER(); + + if (pRequestedSSID == NULL) { + return WLAN_STATUS_FAILURE; + } + + wlan_scan_delete_ssid_table_entry(priv, pRequestedSSID); + + memset(&scanCfg, 0x00, sizeof(scanCfg)); + + memcpy(scanCfg.ssidList[0].ssid, + pRequestedSSID->Ssid, pRequestedSSID->SsidLength); + scanCfg.keepPreviousScan = TRUE; + + wlan_scan_networks(priv, &scanCfg); + + if (Adapter->SurpriseRemoved) + return WLAN_STATUS_FAILURE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief scan an AP with specific BSSID + * + * @param priv A pointer to wlan_private structure + * @param bssid A pointer to AP's bssid + * + * @return WLAN_STATUS_SUCCESS-success, otherwise fail + */ +int +SendSpecificBSSIDScan(wlan_private * priv, u8 * bssid) +{ + wlan_ioctl_user_scan_cfg scanCfg; + + ENTER(); + + if (bssid == NULL) { + return WLAN_STATUS_FAILURE; + } + + memset(&scanCfg, 0x00, sizeof(scanCfg)); + memcpy(scanCfg.specificBSSID, bssid, sizeof(scanCfg.specificBSSID)); + scanCfg.keepPreviousScan = TRUE; + + wlan_scan_networks(priv, &scanCfg); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Retrieve the scan table entries via wireless tools IOCTL call + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_get_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + char *current_ev = extra; + char *end_buf = extra + IW_SCAN_MAX_DATA; + CHANNEL_FREQ_POWER *cfp; + BSSDescriptor_t *pScanTable; + char *current_val; /* For rates */ + struct iw_event iwe; /* Temporary buffer */ + int i; + int j; + int rate; + + u8 buf[16 + 256 * 2]; + u8 *ptr; + u8 *pRawData; +#define PERFECT_RSSI ((u8)50) +#define WORST_RSSI ((u8)0) +#define RSSI_DIFF ((u8)(PERFECT_RSSI - WORST_RSSI)) + u8 rssi; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + PRINTM(INFO, "Current Ssid: %-32s\n", + Adapter->CurBssParams.BSSDescriptor.Ssid.Ssid); + } + + PRINTM(INFO, "Scan: Get: NumInScanTable = %d\n", Adapter->NumInScanTable); + +#if WIRELESS_EXT > 13 + /* The old API using SIOCGIWAPLIST had a hard limit of IW_MAX_AP. + * The new API using SIOCGIWSCAN is only limited by buffer size + * WE-14 -> WE-16 the buffer is limited to IW_SCAN_MAX_DATA bytes + * which is 4096. + */ + for (i = 0; i < Adapter->NumInScanTable; i++) { + if ((current_ev + MAX_SCAN_CELL_SIZE) >= end_buf) { + PRINTM(INFO, "i=%d break out: current_ev=%p end_buf=%p " + "MAX_SCAN_CELL_SIZE=%d\n", + i, current_ev, end_buf, MAX_SCAN_CELL_SIZE); + break; + } + + pScanTable = &Adapter->ScanTable[i]; + + PRINTM(INFO, "i=%d Ssid: %-32s\n", i, pScanTable->Ssid.Ssid); + + cfp = + find_cfp_by_band_and_channel(Adapter, 0, + (u16) pScanTable->Channel); + if (!cfp) { + PRINTM(INFO, "Invalid channel number %d\n", pScanTable->Channel); + continue; + } + + if (ssid_valid(&Adapter->ScanTable[i].Ssid) == FALSE) { + continue; + } + + /* First entry *MUST* be the AP MAC address */ + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, + &Adapter->ScanTable[i].MacAddress, ETH_ALEN); + + iwe.len = IW_EV_ADDR_LEN; + current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, iwe.len); + + //Add the ESSID + iwe.u.data.length = Adapter->ScanTable[i].Ssid.SsidLength; + + if (iwe.u.data.length > 32) { + iwe.u.data.length = 32; + } + + iwe.cmd = SIOCGIWESSID; + iwe.u.essid.flags = (i + 1) & IW_ENCODE_INDEX; + iwe.len = IW_EV_POINT_LEN + iwe.u.data.length; + current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, + (s8 *) Adapter->ScanTable[i].Ssid. + Ssid); + + //Add mode + iwe.cmd = SIOCGIWMODE; + iwe.u.mode = Adapter->ScanTable[i].InfrastructureMode + 1; + iwe.len = IW_EV_UINT_LEN; + current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, iwe.len); + + //frequency + iwe.cmd = SIOCGIWFREQ; + iwe.u.freq.m = (long) cfp->Freq * 100000; + iwe.u.freq.e = 1; + iwe.len = IW_EV_FREQ_LEN; + current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, iwe.len); + + /* Add quality statistics */ + iwe.cmd = IWEVQUAL; + iwe.u.qual.updated = IW_QUAL_ALL_UPDATED; + iwe.u.qual.level = SCAN_RSSI(Adapter->ScanTable[i].Rssi); + + rssi = iwe.u.qual.level - MRVDRV_NF_DEFAULT_SCAN_VALUE; + iwe.u.qual.qual = + (100 * RSSI_DIFF * RSSI_DIFF - (PERFECT_RSSI - rssi) * + (15 * (RSSI_DIFF) + 62 * (PERFECT_RSSI - rssi))) / + (RSSI_DIFF * RSSI_DIFF); + if (iwe.u.qual.qual > 100) + iwe.u.qual.qual = 100; + + if (Adapter->NF[TYPE_BEACON][TYPE_NOAVG] == 0) { + iwe.u.qual.noise = MRVDRV_NF_DEFAULT_SCAN_VALUE; + } else { + iwe.u.qual.noise = CAL_NF(Adapter->NF[TYPE_BEACON][TYPE_NOAVG]); + } + if ((Adapter->InfrastructureMode == Wlan802_11IBSS) && + !SSIDcmp(&Adapter->CurBssParams.BSSDescriptor.Ssid, + &Adapter->ScanTable[i].Ssid) + && Adapter->AdhocCreate) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RSSI, + 0, HostCmd_OPTION_WAITFORRSP, 0, + NULL); + + if (ret) { + LEAVE(); + return ret; + } + iwe.u.qual.level = + CAL_RSSI(Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + Adapter->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + } + + iwe.len = IW_EV_QUAL_LEN; + current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, iwe.len); + + /* Add encryption capability */ + iwe.cmd = SIOCGIWENCODE; + if (Adapter->ScanTable[i].Privacy) { + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + } else { + iwe.u.data.flags = IW_ENCODE_DISABLED; + } + iwe.u.data.length = 0; + iwe.len = IW_EV_POINT_LEN + iwe.u.data.length; + current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, NULL); + + current_val = current_ev + IW_EV_LCP_LEN; + + iwe.cmd = SIOCGIWRATE; + + iwe.u.bitrate.fixed = 0; + iwe.u.bitrate.disabled = 0; + iwe.u.bitrate.value = 0; + + /* Bit rate given in 500 kb/s units (+ 0x80) */ + for (j = 0; j < sizeof(Adapter->ScanTable[i].SupportedRates); j++) { + if (Adapter->ScanTable[i].SupportedRates[j] == 0) { + break; + } + rate = (Adapter->ScanTable[i].SupportedRates[j] & 0x7F) * 500000; + if (rate > iwe.u.bitrate.value) { + iwe.u.bitrate.value = rate; + } + + iwe.u.bitrate.value = (Adapter->ScanTable[i].SupportedRates[j] + & 0x7f) * 500000; + iwe.len = IW_EV_PARAM_LEN; + current_ev = + iwe_stream_add_value(current_ev, current_val, end_buf, &iwe, + iwe.len); + + } + if ((Adapter->ScanTable[i].InfrastructureMode == Wlan802_11IBSS) && + !SSIDcmp(&Adapter->CurBssParams.BSSDescriptor.Ssid, + &Adapter->ScanTable[i].Ssid) + && Adapter->AdhocCreate) { + iwe.u.bitrate.value = 22 * 500000; + } + iwe.len = IW_EV_PARAM_LEN; + current_ev = iwe_stream_add_value(current_ev, current_val, end_buf, + &iwe, iwe.len); + + /* Add new value to event */ + current_val = current_ev + IW_EV_LCP_LEN; + + if (Adapter->ScanTable[i].rsnIE.IeeeHdr.ElementId == RSN_IE) { + pRawData = (u8 *) & Adapter->ScanTable[i].rsnIE; + memset(&iwe, 0, sizeof(iwe)); + memset(buf, 0, sizeof(buf)); + ptr = buf; +#if WIRELESS_EXT >= 18 + memcpy(buf, pRawData, + Adapter->ScanTable[i].rsnIE.IeeeHdr.Len + 2); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = Adapter->ScanTable[i].rsnIE.IeeeHdr.Len + 2; +#else + ptr += sprintf(ptr, "rsn_ie="); + + for (j = 0; + j < (Adapter->ScanTable[i].rsnIE.IeeeHdr.Len + + sizeof(IEEEtypes_Header_t)); j++) { + ptr += sprintf(ptr, "%02x", *(pRawData + j)); + } + iwe.u.data.length = strlen(buf); + + PRINTM(INFO, "iwe.u.data.length %d\n", iwe.u.data.length); + PRINTM(INFO, "WPA2 BUF: %s \n", buf); + + iwe.cmd = IWEVCUSTOM; +#endif + iwe.len = IW_EV_POINT_LEN + iwe.u.data.length; + current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, buf); + } + if (Adapter->ScanTable[i].wpaIE.VendHdr.ElementId == WPA_IE) { + pRawData = (u8 *) & Adapter->ScanTable[i].wpaIE; + memset(&iwe, 0, sizeof(iwe)); + memset(buf, 0, sizeof(buf)); + ptr = buf; +#if WIRELESS_EXT >= 18 + memcpy(buf, pRawData, + Adapter->ScanTable[i].wpaIE.VendHdr.Len + 2); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = Adapter->ScanTable[i].wpaIE.VendHdr.Len + 2; +#else + ptr += sprintf(ptr, "wpa_ie="); + + for (j = 0; + j < (Adapter->ScanTable[i].wpaIE.VendHdr.Len + + sizeof(IEEEtypes_Header_t)); j++) { + ptr += sprintf(ptr, "%02x", *(pRawData + j)); + } + iwe.u.data.length = strlen(buf); + + PRINTM(INFO, "iwe.u.data.length %d\n", iwe.u.data.length); + PRINTM(INFO, "WPA BUF: %s \n", buf); + + iwe.cmd = IWEVCUSTOM; +#endif + iwe.len = IW_EV_POINT_LEN + iwe.u.data.length; + current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, buf); + } + if (Adapter->ScanTable[i].wpsIE.VendHdr.ElementId == WPS_IE) { + pRawData = (u8 *) & Adapter->ScanTable[i].wpsIE; + memset(&iwe, 0, sizeof(iwe)); + memset(buf, 0, sizeof(buf)); + ptr = buf; + ptr += sprintf(ptr, "wps_ie="); + + for (j = 0; + j < (Adapter->ScanTable[i].wpsIE.VendHdr.Len + + sizeof(IEEEtypes_Header_t)); j++) { + ptr += sprintf(ptr, "%02x", *(pRawData + j)); + } + iwe.u.data.length = strlen(buf); + + PRINTM(INFO, "iwe.u.data.length %d\n", iwe.u.data.length); + PRINTM(INFO, "WPS BUF: %s \n", buf); + + iwe.cmd = IWEVCUSTOM; + iwe.len = IW_EV_POINT_LEN + iwe.u.data.length; + current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, buf); + } + +#if WIRELESS_EXT > 14 + + if (Adapter->ScanTable[i].extra_ie != 0) { + memset(&iwe, 0, sizeof(iwe)); + memset(buf, 0, sizeof(buf)); + ptr = buf; + ptr += sprintf(ptr, "extra_ie"); + iwe.u.data.length = strlen(buf); + + PRINTM(INFO, "iwe.u.data.length %d\n", iwe.u.data.length); + PRINTM(INFO, "BUF: %s \n", buf); + + iwe.cmd = IWEVCUSTOM; + iwe.len = IW_EV_POINT_LEN + iwe.u.data.length; + current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, buf); + } +#endif + + current_val = current_ev + IW_EV_LCP_LEN; + + /* + * Check if we added any event + */ + if ((current_val - current_ev) > IW_EV_LCP_LEN) + current_ev = current_val; + } + + dwrq->length = (current_ev - extra); + dwrq->flags = 0; + + LEAVE(); +#endif + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Create a wlan_ioctl_get_scan_table_entry for a given BSS + * Descriptor for inclusion in the ioctl response to the user space + * application. + * + * + * @param priv A pointer to wlan_private structure + * @param pBSSDesc Pointer to a BSS entry in the scan table to form + * scan response from for delivery to the application layer + * @param ppBuffer Output parameter: Buffer used to output scan return struct + * @param pSpaceLeft Output parameter: Number of bytes available in the + * response buffer. + * + * @return WLAN_STATUS_SUCCESS, or < 0 with IOCTL error code + */ +static int +wlan_get_scan_table_ret_entry(wlan_private * priv, + BSSDescriptor_t * pBSSDesc, + u8 ** ppBuffer, int *pSpaceLeft) +{ + wlan_adapter *Adapter; + wlan_ioctl_get_scan_table_entry *pRspEntry; + wlan_ioctl_get_scan_table_entry tmpRspEntry; + int spaceNeeded; + u8 *pCurrent; + int variableSize; + + const int fixedSize = (sizeof(tmpRspEntry.fixedFieldLength) + + sizeof(tmpRspEntry.fixedFields) + + sizeof(tmpRspEntry.bssInfoLength)); + + ENTER(); + + Adapter = priv->adapter; + pCurrent = *ppBuffer; + + /* The variable size returned is the stored beacon size */ + variableSize = pBSSDesc->beaconBufSize; + + /* If we stored a beacon and its size was zero, set the variable + * size return value to the size of the brief scan response + * wlan_scan_create_brief_table_entry creates. Also used if + * we are not configured to store beacons in the first place + */ + if (variableSize == 0) { + variableSize = pBSSDesc->Ssid.SsidLength + 2; + variableSize += (sizeof(pBSSDesc->BeaconPeriod) + + sizeof(pBSSDesc->TimeStamp) + + sizeof(pBSSDesc->Cap)); + if (pBSSDesc->wmmIE.VendHdr.ElementId == WMM_IE) { + variableSize += (sizeof(IEEEtypes_Header_t) + + pBSSDesc->wmmIE.VendHdr.Len); + } + + if (pBSSDesc->wpaIE.VendHdr.ElementId == WPA_IE) { + variableSize += (sizeof(IEEEtypes_Header_t) + + pBSSDesc->wpaIE.VendHdr.Len); + } + + if (pBSSDesc->rsnIE.IeeeHdr.ElementId == RSN_IE) { + variableSize += (sizeof(IEEEtypes_Header_t) + + pBSSDesc->rsnIE.IeeeHdr.Len); + } + } + + spaceNeeded = fixedSize + variableSize; + + PRINTM(INFO, "GetScanTable: need(%d), left(%d)\n", + spaceNeeded, *pSpaceLeft); + + if (spaceNeeded >= *pSpaceLeft) { + *pSpaceLeft = 0; + LEAVE(); + return -E2BIG; + } + + *pSpaceLeft -= spaceNeeded; + + tmpRspEntry.fixedFieldLength = sizeof(pRspEntry->fixedFields); + + memcpy(tmpRspEntry.fixedFields.bssid, + pBSSDesc->MacAddress, sizeof(pRspEntry->fixedFields.bssid)); + + tmpRspEntry.fixedFields.rssi = pBSSDesc->Rssi; + tmpRspEntry.fixedFields.channel = pBSSDesc->Channel; + tmpRspEntry.fixedFields.networkTSF = pBSSDesc->networkTSF; + tmpRspEntry.bssInfoLength = variableSize; + + /* + * Copy fixed fields to user space + */ + if (copy_to_user(pCurrent, &tmpRspEntry, fixedSize)) { + PRINTM(INFO, "Copy to user failed\n"); + LEAVE(); + return -EFAULT; + } + + pCurrent += fixedSize; + + if (pBSSDesc->pBeaconBuf) { + /* + * Copy variable length elements to user space + */ + if (copy_to_user(pCurrent, pBSSDesc->pBeaconBuf, + pBSSDesc->beaconBufSize)) { + PRINTM(INFO, "Copy to user failed\n"); + LEAVE(); + return -EFAULT; + } + + pCurrent += pBSSDesc->beaconBufSize; + } else { + wlan_scan_create_brief_table_entry(&pCurrent, pBSSDesc); + } + + *ppBuffer = pCurrent; + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Retrieve the scan response/beacon table + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_get_scan_table_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter; + BSSDescriptor_t *pBSSDesc; + wlan_ioctl_get_scan_table_info *pRspInfo; + int retcode; + int retlen; + int spaceLeft; + u8 *pCurrent; + u8 *pBufferEnd; + u32 scanStart; + u32 numScansDone; + + numScansDone = 0; + retcode = WLAN_STATUS_SUCCESS; + Adapter = priv->adapter; + + if (copy_from_user(&scanStart, + wrq->u.data.pointer, sizeof(scanStart)) != 0) { + /* copy_from_user failed */ + PRINTM(INFO, "GetScanTable: copy from user failed\n"); + return -EFAULT; + } + + pRspInfo = (wlan_ioctl_get_scan_table_info *) wrq->u.data.pointer; + pCurrent = pRspInfo->scan_table_entry_buffer; + pBufferEnd = wrq->u.data.pointer + wrq->u.data.length - 1; + spaceLeft = pBufferEnd - pCurrent; + PRINTM(INFO, "GetScanTable: scanStart req = %d\n", scanStart); + PRINTM(INFO, "GetScanTable: length avail = %d\n", wrq->u.data.length); + + if (scanStart == 0) { + PRINTM(INFO, "GetScanTable: get current BSS Descriptor\n"); + + /* Use to get current association saved descriptor */ + pBSSDesc = &Adapter->CurBssParams.BSSDescriptor; + + retcode = wlan_get_scan_table_ret_entry(priv, + pBSSDesc, + &pCurrent, &spaceLeft); + + if (retcode == WLAN_STATUS_SUCCESS) { + numScansDone = 1; + } + + } else { + scanStart--; + + while (spaceLeft + && (scanStart + numScansDone < Adapter->NumInScanTable) + && (retcode == WLAN_STATUS_SUCCESS)) { + + pBSSDesc = &Adapter->ScanTable[scanStart + numScansDone]; + + PRINTM(INFO, "GetScanTable: get current BSS Descriptor [%d]\n", + scanStart + numScansDone); + + retcode = wlan_get_scan_table_ret_entry(priv, + pBSSDesc, + &pCurrent, &spaceLeft); + + if (retcode == WLAN_STATUS_SUCCESS) { + numScansDone++; + } + } + } + + pRspInfo->scanNumber = numScansDone; + retlen = pCurrent - (u8 *) wrq->u.data.pointer; + + wrq->u.data.length = retlen; + + /* Return retcode (EFAULT or E2BIG) in the case where no scan results were + * successfully encoded. + */ + + return (numScansDone ? WLAN_STATUS_SUCCESS : retcode); +} + +/** + * @brief Update the scan entry TSF timestamps to reflect a new association + * + * @param priv A pointer to wlan_private structure + * @param pNewBssDesc A pointer to the newly associated AP's scan table entry + * + * @return void + */ +void +wlan_scan_update_tsf_timestamps(wlan_private * priv, + BSSDescriptor_t * pNewBssDesc) +{ + wlan_adapter *Adapter = priv->adapter; + int tableIdx; + u64 newTsfBase; + s64 tsfDelta; + + memcpy(&newTsfBase, pNewBssDesc->TimeStamp, sizeof(newTsfBase)); + + tsfDelta = newTsfBase - pNewBssDesc->networkTSF; + + PRINTM(INFO, "TSF: Update TSF timestamps, 0x%016llx -> 0x%016llx\n", + pNewBssDesc->networkTSF, newTsfBase); + + for (tableIdx = 0; tableIdx < Adapter->NumInScanTable; tableIdx++) { + Adapter->ScanTable[tableIdx].networkTSF += tsfDelta; + } +} + +/** + * @brief Private IOCTL entry to perform an app configured immediate scan + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure containing the + * wlan_ioctl_user_scan_cfg requesting this scan + * + * @return 0 if successful; IOCTL error code otherwise + */ +int +wlan_set_user_scan_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_ioctl_user_scan_cfg scanReq; + int retcode; + union iwreq_data wrqu; + +#ifdef REASSOCIATION + if (OS_ACQ_SEMAPHORE_BLOCK(&priv->adapter->ReassocSem)) { + PRINTM(ERROR, "Acquire semaphore error, wlan_extscan_ioctl\n"); + return -EBUSY; + } +#endif + + memset(&scanReq, 0x00, sizeof(scanReq)); + + if (copy_from_user(&scanReq, + wrq->u.data.pointer, + MIN(wrq->u.data.length, sizeof(scanReq))) != 0) { + /* copy_from_user failed */ + PRINTM(INFO, "SetUserScan: copy from user failed\n"); + retcode = -EFAULT; + + } else { + + retcode = wlan_scan_networks(priv, &scanReq); + + memset(&wrqu, 0x00, sizeof(union iwreq_data)); + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWSCAN, &wrqu, NULL); + + } + +#ifdef REASSOCIATION + OS_REL_SEMAPHORE(&priv->adapter->ReassocSem); +#endif + + return retcode; +} + +/** + * @brief Prepare a scan command to be sent to the firmware + * + * Use the wlan_scan_cmd_config sent to the command processing module in + * the PrepareAndSendCommand to configure a HostCmd_DS_802_11_SCAN command + * struct to send to firmware. + * + * The fixed fields specifying the BSS type and BSSID filters as well as a + * variable number/length of TLVs are sent in the command to firmware. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure to be sent to + * firmware with the HostCmd_DS_801_11_SCAN structure + * @param pdata_buf Void pointer cast of a wlan_scan_cmd_config struct used + * to set the fields/TLVs for the command sent to firmware + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + * + * @sa wlan_scan_create_channel_list + */ +int +wlan_cmd_802_11_scan(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pdata_buf) +{ + HostCmd_DS_802_11_SCAN *pScan = &cmd->params.scan; + wlan_scan_cmd_config *pScanCfg; + + ENTER(); + + pScanCfg = (wlan_scan_cmd_config *) pdata_buf; + + /* Set fixed field variables in scan command */ + pScan->BSSType = pScanCfg->bssType; + memcpy(pScan->BSSID, pScanCfg->specificBSSID, sizeof(pScan->BSSID)); + memcpy(pScan->TlvBuffer, pScanCfg->tlvBuffer, pScanCfg->tlvBufferLen); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_SCAN); + + /* Size is equal to the sizeof(fixed portions) + the TLV len + header */ + cmd->Size = wlan_cpu_to_le16(sizeof(pScan->BSSType) + + sizeof(pScan->BSSID) + + pScanCfg->tlvBufferLen + S_DS_GEN); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Store a beacon or probe response for a BSS returned in the scan + * + * Store a new scan response or an update for a previous scan response. New + * entries need to verify that they do not exceed the total amount of + * memory allocated for the table. + + * Replacement entries need to take into consideration the amount of space + * currently allocated for the beacon/probe response and adjust the entry + * as needed. + * + * A small amount of extra pad (SCAN_BEACON_ENTRY_PAD) is generally reserved + * for an entry in case it is a beacon since a probe response for the + * network will by larger per the standard. This helps to reduce the + * amount of memory copying to fit a new probe response into an entry + * already occupied by a network's previously stored beacon. + * + * @param priv A pointer to wlan_private structure + * @param beaconIdx Index in the scan table to store this entry; may be + * replacing an older duplicate entry for this BSS + * @param numInTable Number of entries currently in the table + * @param pNewBeacon Pointer to the new beacon/probe response to save + * + * @return void + */ +void +wlan_ret_802_11_scan_store_beacon(wlan_private * priv, + int beaconIdx, + int numInTable, + BSSDescriptor_t * pNewBeacon) +{ + wlan_adapter *Adapter = priv->adapter; + u8 *pBcnStore; + int newBcnSize; + int oldBcnSize; + int bcnSpace; + int adjIdx; + + if (Adapter->ScanTable[beaconIdx].pBeaconBuf != NULL) { + + newBcnSize = pNewBeacon->beaconBufSize; + oldBcnSize = Adapter->ScanTable[beaconIdx].beaconBufSize; + bcnSpace = Adapter->ScanTable[beaconIdx].beaconBufSizeMax; + pBcnStore = Adapter->ScanTable[beaconIdx].pBeaconBuf; + + /* Set the max to be the same as current entry unless changed below */ + pNewBeacon->beaconBufSizeMax = bcnSpace; + + if (newBcnSize == oldBcnSize) { + /* + * Beacon is the same size as the previous entry. + * Replace the previous contents with the scan result + */ + memcpy(pBcnStore, + pNewBeacon->pBeaconBuf, pNewBeacon->beaconBufSize); + + } else if (newBcnSize <= bcnSpace) { + /* + * New beacon size will fit in the amount of space + * we have previously allocated for it + */ + + /* Copy the new beacon buffer entry over the old one */ + memcpy(pBcnStore, pNewBeacon->pBeaconBuf, newBcnSize); + + /* If the old beacon size was less than the maximum + * we had alloted for the entry, and the new entry + * is even smaller, reset the max size to the old beacon + * entry and compress the storage space (leaving a new + * pad space of (oldBcnSize - newBcnSize). + */ + if (oldBcnSize < bcnSpace && newBcnSize != bcnSpace) { + /* + * Old Beacon size is smaller than the alloted storage size. + * Shrink the alloted storage space. + */ + PRINTM(INFO, "AppControl: Smaller Duplicate Beacon (%d), " + "old = %d, new = %d, space = %d, left = %d\n", + beaconIdx, oldBcnSize, newBcnSize, bcnSpace, + (sizeof(Adapter->beaconBuffer) - + (Adapter->pBeaconBufEnd - Adapter->beaconBuffer))); + + /* memmove (since the memory overlaps) the data + * after the beacon we just stored to the end of + * the current beacon. This cleans up any unused + * space the old larger beacon was using in the buffer + */ + memmove(pBcnStore + oldBcnSize, + pBcnStore + bcnSpace, + Adapter->pBeaconBufEnd - (pBcnStore + bcnSpace)); + + /* Decrement the end pointer by the difference between + * the old larger size and the new smaller size since + * we are using less space due to the new beacon being + * smaller + */ + Adapter->pBeaconBufEnd -= (bcnSpace - oldBcnSize); + + /* Set the maximum storage size to the old beacon size */ + pNewBeacon->beaconBufSizeMax = oldBcnSize; + + /* Adjust beacon buffer pointers that are past the current */ + for (adjIdx = 0; adjIdx < numInTable; adjIdx++) { + if (Adapter->ScanTable[adjIdx].pBeaconBuf > pBcnStore) { + Adapter->ScanTable[adjIdx].pBeaconBuf + -= (bcnSpace - oldBcnSize); + } + } + } + } else if (Adapter->pBeaconBufEnd + (newBcnSize - bcnSpace) + < (Adapter->beaconBuffer + sizeof(Adapter->beaconBuffer))) { + /* + * Beacon is larger than space previously allocated (bcnSpace) + * and there is enough space left in the beaconBuffer to store + * the additional data + */ + PRINTM(INFO, "AppControl: Larger Duplicate Beacon (%d), " + "old = %d, new = %d, space = %d, left = %d\n", + beaconIdx, oldBcnSize, newBcnSize, bcnSpace, + (sizeof(Adapter->beaconBuffer) - + (Adapter->pBeaconBufEnd - Adapter->beaconBuffer))); + + /* memmove (since the memory overlaps) the data + * after the beacon we just stored to the end of + * the current beacon. This moves the data for + * the beacons after this further in memory to + * make space for the new larger beacon we are + * about to copy in. + */ + memmove(pBcnStore + newBcnSize, + pBcnStore + bcnSpace, + Adapter->pBeaconBufEnd - (pBcnStore + bcnSpace)); + + /* Copy the new beacon buffer entry over the old one */ + memcpy(pBcnStore, pNewBeacon->pBeaconBuf, newBcnSize); + + /* Move the beacon end pointer by the amount of new + * beacon data we are adding + */ + Adapter->pBeaconBufEnd += (newBcnSize - bcnSpace); + + /* This entry is bigger than the alloted max space + * previously reserved. Increase the max space to + * be equal to the new beacon size + */ + pNewBeacon->beaconBufSizeMax = newBcnSize; + + /* Adjust beacon buffer pointers that are past the current */ + for (adjIdx = 0; adjIdx < numInTable; adjIdx++) { + if (Adapter->ScanTable[adjIdx].pBeaconBuf > pBcnStore) { + Adapter->ScanTable[adjIdx].pBeaconBuf + += (newBcnSize - bcnSpace); + } + } + } else { + /* + * Beacon is larger than the previously allocated space, but + * there is not enough free space to store the additional data + */ + PRINTM(INFO, + "AppControl: Failed: Larger Duplicate Beacon (%d)," + " old = %d, new = %d, space = %d, left = %d\n", + beaconIdx, oldBcnSize, newBcnSize, bcnSpace, + (sizeof(Adapter->beaconBuffer) - + (Adapter->pBeaconBufEnd - Adapter->beaconBuffer))); + + /* Storage failure, keep old beacon intact */ + pNewBeacon->beaconBufSize = oldBcnSize; + } + + /* Point the new entry to its permanent storage space */ + pNewBeacon->pBeaconBuf = pBcnStore; + + } else { + /* No existing beacon data exists for this entry, check to see + * if we can fit it in the remaining space + */ + if (Adapter->pBeaconBufEnd + pNewBeacon->beaconBufSize + + SCAN_BEACON_ENTRY_PAD < (Adapter->beaconBuffer + + sizeof(Adapter->beaconBuffer))) { + + /* Copy the beacon buffer data from the local entry to the + * adapter dev struct buffer space used to store the raw + * beacon data for each entry in the scan table + */ + memcpy(Adapter->pBeaconBufEnd, pNewBeacon->pBeaconBuf, + pNewBeacon->beaconBufSize); + + /* Update the beacon ptr to point to the table save area */ + pNewBeacon->pBeaconBuf = Adapter->pBeaconBufEnd; + pNewBeacon->beaconBufSizeMax = (pNewBeacon->beaconBufSize + + SCAN_BEACON_ENTRY_PAD); + + /* Increment the end pointer by the size reserved */ + Adapter->pBeaconBufEnd += pNewBeacon->beaconBufSizeMax; + + PRINTM(INFO, "AppControl: Beacon[%02d] sz=%03d," + " used = %04d, left = %04d\n", + beaconIdx, + pNewBeacon->beaconBufSize, + (Adapter->pBeaconBufEnd - Adapter->beaconBuffer), + (sizeof(Adapter->beaconBuffer) - + (Adapter->pBeaconBufEnd - Adapter->beaconBuffer))); + } else { + /* + * No space for new beacon + */ + PRINTM(INFO, "AppControl: No space beacon (%d): " + "%02x:%02x:%02x:%02x:%02x:%02x; sz=%03d, left=%03d\n", + beaconIdx, + pNewBeacon->MacAddress[0], pNewBeacon->MacAddress[1], + pNewBeacon->MacAddress[2], pNewBeacon->MacAddress[3], + pNewBeacon->MacAddress[4], pNewBeacon->MacAddress[5], + pNewBeacon->beaconBufSize, + (sizeof(Adapter->beaconBuffer) - + (Adapter->pBeaconBufEnd - Adapter->beaconBuffer))); + + /* Storage failure; clear storage records for this bcn */ + pNewBeacon->pBeaconBuf = NULL; + pNewBeacon->beaconBufSize = 0; + pNewBeacon->beaconBufSizeMax = 0; + } + } +} + +/** + * @brief This function handles the command response of scan + * + * The response buffer for the scan command has the following + * memory layout: + * + * .-----------------------------------------------------------. + * | Header (4 * sizeof(u16)): Standard command response hdr | + * .-----------------------------------------------------------. + * | BufSize (u16) : sizeof the BSS Description data | + * .-----------------------------------------------------------. + * | NumOfSet (u8) : Number of BSS Descs returned | + * .-----------------------------------------------------------. + * | BSSDescription data (variable, size given in BufSize) | + * .-----------------------------------------------------------. + * | TLV data (variable, size calculated using Header->Size, | + * | BufSize and sizeof the fixed fields above) | + * .-----------------------------------------------------------. + * + * @param priv A pointer to wlan_private structure + * @param resp A pointer to HostCmd_DS_COMMAND + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_ret_802_11_scan(wlan_private * priv, HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_SCAN_RSP *pScan; + BSSDescriptor_t newBssEntry; + MrvlIEtypes_Data_t *pTlv; + MrvlIEtypes_TsfTimestamp_t *pTsfTlv; + u8 *pBssInfo; + u16 scanRespSize; + int bytesLeft; + int numInTable; + int bssIdx; + int idx; + int tlvBufSize; + u64 tsfVal; + BOOLEAN bgScanResp; + + ENTER(); + + bgScanResp = (resp->Command == HostCmd_RET_802_11_BG_SCAN_QUERY); + if (bgScanResp) { + pScan = &resp->params.bgscanqueryresp.scanresp; + } else { + pScan = &resp->params.scanresp; + } + + if (pScan->NumberOfSets > MRVDRV_MAX_BSSID_LIST) { + PRINTM(INFO, "SCAN_RESP: Invalid number of AP returned (%d)!!\n", + pScan->NumberOfSets); + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + bytesLeft = wlan_le16_to_cpu(pScan->BSSDescriptSize); + PRINTM(INFO, "SCAN_RESP: BSSDescriptSize %d\n", bytesLeft); + + scanRespSize = resp->Size; + + PRINTM(CMND, "SCAN_RESP: returned %d APs before parsing\n", + pScan->NumberOfSets); + + numInTable = Adapter->NumInScanTable; + pBssInfo = pScan->BssDescAndTlvBuffer; + + /* The size of the TLV buffer is equal to the entire command response + * size (scanRespSize) minus the fixed fields (sizeof()'s), the + * BSS Descriptions (BSSDescriptSize as bytesLef) and the command + * response header (S_DS_GEN) + */ + tlvBufSize = scanRespSize - (bytesLeft + sizeof(pScan->BSSDescriptSize) + + sizeof(pScan->NumberOfSets) + + S_DS_GEN); + + pTlv = (MrvlIEtypes_Data_t *) (pScan->BssDescAndTlvBuffer + bytesLeft); + + /* Search the TLV buffer space in the scan response for any valid TLVs */ + wlan_ret_802_11_scan_get_tlv_ptrs(pTlv, tlvBufSize, &pTsfTlv); + + /* + * Process each scan response returned (pScan->NumberOfSets). Save + * the information in the newBssEntry and then insert into the + * driver scan table either as an update to an existing entry + * or as an addition at the end of the table + */ + for (idx = 0; idx < pScan->NumberOfSets && bytesLeft; idx++) { + /* Zero out the newBssEntry we are about to store info in */ + memset(&newBssEntry, 0x00, sizeof(newBssEntry)); + + /* Process the data fields and IEs returned for this BSS */ + if ((InterpretBSSDescriptionWithIE(&newBssEntry, + &pBssInfo, + &bytesLeft) == WLAN_STATUS_SUCCESS) + && CHECK_SSID_IS_VALID(&newBssEntry.Ssid)) { + + PRINTM(INFO, "SCAN_RESP: BSSID = %02x:%02x:%02x:%02x:%02x:%02x\n", + newBssEntry.MacAddress[0], newBssEntry.MacAddress[1], + newBssEntry.MacAddress[2], newBssEntry.MacAddress[3], + newBssEntry.MacAddress[4], newBssEntry.MacAddress[5]); + + /* + * Search the scan table for the same bssid + */ + for (bssIdx = 0; bssIdx < numInTable; bssIdx++) { + if (memcmp(newBssEntry.MacAddress, + Adapter->ScanTable[bssIdx].MacAddress, + sizeof(newBssEntry.MacAddress)) == 0) { + /* + * If the SSID matches as well, it is a duplicate of + * this entry. Keep the bssIdx set to this + * entry so we replace the old contents in the table + */ + if ((newBssEntry.Ssid.SsidLength == + Adapter->ScanTable[bssIdx].Ssid.SsidLength) + && (memcmp(newBssEntry.Ssid.Ssid, + Adapter->ScanTable[bssIdx].Ssid.Ssid, + newBssEntry.Ssid.SsidLength) == 0)) { + PRINTM(INFO, "SCAN_RESP: Duplicate of index: %d\n", + bssIdx); + break; + } + } + } + /* + * If the bssIdx is equal to the number of entries in the table, + * the new entry was not a duplicate; append it to the scan + * table + */ + if (bssIdx == numInTable) { + /* Range check the bssIdx, keep it limited to the last entry */ + if (bssIdx == MRVDRV_MAX_BSSID_LIST) { + bssIdx--; + } else { + numInTable++; + } + } + + /* + * Save the beacon/probe response returned for later application + * retrieval. Duplicate beacon/probe responses are updated if + * possible + */ + wlan_ret_802_11_scan_store_beacon(priv, + bssIdx, + numInTable, &newBssEntry); + /* + * If the TSF TLV was appended to the scan results, save + * this entry's TSF value in the networkTSF field. The + * networkTSF is the firmware's TSF value at the time the + * beacon or probe response was received. + */ + if (pTsfTlv) { + memcpy(&tsfVal, &pTsfTlv->tsfTable[idx], sizeof(tsfVal)); + tsfVal = wlan_le64_to_cpu(tsfVal); + + memcpy(&newBssEntry.networkTSF, + &tsfVal, sizeof(newBssEntry.networkTSF)); + } + + /* Copy the locally created newBssEntry to the scan table */ + memcpy(&Adapter->ScanTable[bssIdx], + &newBssEntry, sizeof(Adapter->ScanTable[bssIdx])); + + } else { + + /* Error parsing/interpreting the scan response, skipped */ + PRINTM(INFO, "SCAN_RESP: " + "InterpretBSSDescriptionWithIE returned ERROR\n"); + } + } + + PRINTM(CMND, "SCAN_RESP: Scanned %2d APs, %d valid, %d total\n", + pScan->NumberOfSets, numInTable - Adapter->NumInScanTable, + numInTable); + + /* Update the total number of BSSIDs in the scan table */ + Adapter->NumInScanTable = numInTable; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief scan network with specific ssid + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_extscan_ioctl(wlan_private * priv, struct ifreq *req) +{ + wlan_adapter *Adapter = priv->adapter; + WLAN_802_11_SSID Ext_Scan_SSID; + wlan_ioctl_user_scan_cfg scanCfg; + union iwreq_data wrqu; + + ENTER(); + + if (copy_from_user(&Ext_Scan_SSID, req->ifr_data, sizeof(Ext_Scan_SSID))) { + PRINTM(INFO, "copy of SSID for ext scan from user failed \n"); + LEAVE(); + return -EFAULT; + } +#ifdef REASSOCIATION + if (OS_ACQ_SEMAPHORE_BLOCK(&Adapter->ReassocSem)) { + PRINTM(ERROR, "Acquire semaphore error, wlan_extscan_ioctl\n"); + return -EBUSY; + } +#endif + + memset(&scanCfg, 0x00, sizeof(scanCfg)); + + memcpy(scanCfg.ssidList[0].ssid, Ext_Scan_SSID.Ssid, + Ext_Scan_SSID.SsidLength); + + wlan_scan_networks(priv, &scanCfg); + + memset(&wrqu, 0, sizeof(union iwreq_data)); + wireless_send_event(priv->wlan_dev.netdev, SIOCGIWSCAN, &wrqu, NULL); + +#ifdef REASSOCIATION + OS_REL_SEMAPHORE(&Adapter->ReassocSem); +#endif + + if (Adapter->SurpriseRemoved) + return WLAN_STATUS_FAILURE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function sends BG_SCAN query command to firmware. + * + * @param priv A pointer to wlan_private structure + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +sendBgScanQueryCmd(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + /* Clear the previous scan result */ + memset(Adapter->ScanTable, 0x00, + sizeof(BSSDescriptor_t) * MRVDRV_MAX_BSSID_LIST); + Adapter->NumInScanTable = 0; + Adapter->pBeaconBufEnd = Adapter->beaconBuffer; + + return PrepareAndSendCommand(priv, HostCmd_CMD_802_11_BG_SCAN_QUERY, + 0, 0, 0, NULL); +} + +/** + * @brief Enable/Disable BG Scan + * + * @param priv A pointer to wlan_private structure + * @param enable TRUE-enable, FALSE-disable + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_bg_scan_enable(wlan_private * priv, BOOLEAN enable) +{ + int ret; + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_BG_SCAN_CONFIG, + 0, HostCmd_OPTION_WAITFORRSP, 0, &enable); + return ret; +} + +/** + * @brief config BGSCAN parameter + + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_do_bg_scan_config_ioctl(wlan_private * priv, struct ifreq *req) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + u8 action; + u8 *buf = NULL; + HostCmd_DS_802_11_BG_SCAN_CONFIG *tmp; + + ENTER(); + + action = *((u8 *) req->ifr_data + SKIP_CMDNUM + SKIP_SIZE); + PRINTM(INFO, "Action = %d\n", action); + + switch (action) { + case HostCmd_ACT_GEN_GET: + buf = kmalloc(Adapter->bgScanConfigSize + SKIP_TYPE_SIZE, GFP_KERNEL); + if (!buf) { + PRINTM(MSG, "kmalloc no memory !!\n"); + return -ENOMEM; + } + memcpy(buf, &Adapter->bgScanConfigSize, SKIP_SIZE); + memcpy(buf + SKIP_TYPE_SIZE, Adapter->bgScanConfig, + Adapter->bgScanConfigSize); + + if (copy_to_user(req->ifr_data, buf, + Adapter->bgScanConfigSize + SKIP_TYPE_SIZE)) { + PRINTM(INFO, "Copy to user failed\n"); + kfree(buf); + return -EFAULT; + } + + kfree(buf); + + break; + + case HostCmd_ACT_GEN_SET: + Adapter->bgScanConfigSize = *(u16 *) (req->ifr_data + SKIP_CMDNUM); + PRINTM(INFO, "bgscanConfigSize = %d\n", Adapter->bgScanConfigSize); + + if (!(tmp = kmalloc(Adapter->bgScanConfigSize, GFP_KERNEL))) { + PRINTM(MSG, "kmalloc no memory !!\n"); + Adapter->bgScanConfigSize = 0; + return -ENOMEM; + } + + HEXDUMP("treq", req->ifr_data + SKIP_CMDNUM + SKIP_SIZE, + Adapter->bgScanConfigSize); + + if (copy_from_user(tmp, req->ifr_data + SKIP_CMDNUM + SKIP_SIZE, + Adapter->bgScanConfigSize)) { + PRINTM(INFO, "Copy from user failed\n"); + kfree(tmp); + return -EFAULT; + } + + if (Adapter->bgScanConfig) { + tmp->Enable = Adapter->bgScanConfig->Enable; + buf = (u8 *) Adapter->bgScanConfig; + } + Adapter->bgScanConfig = tmp; + if (buf) + kfree(buf); + + break; + } + + LEAVE(); + + return ret; +} + +/** + * @brief This function prepares command of bg_scan_config. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * @param cmd_action the action: GET or SET + * @param pdata_buf A pointer to data buffer + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_bg_scan_config(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int cmd_action, void *pdata_buf) +{ + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_BG_SCAN_CONFIG *bgcfg = &cmd->params.bgscancfg; + BOOLEAN enable = *((BOOLEAN *) pdata_buf); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_BG_SCAN_CONFIG); + cmd->Size = + wlan_cpu_to_le16((priv->adapter->bgScanConfigSize) + S_DS_GEN); + + Adapter->bgScanConfig->Enable = enable; + + memcpy(bgcfg, Adapter->bgScanConfig, Adapter->bgScanConfigSize); + + bgcfg->Action = wlan_cpu_to_le16(bgcfg->Action); + bgcfg->ScanInterval = wlan_cpu_to_le32(bgcfg->ScanInterval); + bgcfg->StoreCondition = wlan_cpu_to_le32(bgcfg->StoreCondition); + bgcfg->ReportConditions = wlan_cpu_to_le32(bgcfg->ReportConditions); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief This function prepares command of bg_scan_query. + * + * @param priv A pointer to wlan_private structure + * @param cmd A pointer to HostCmd_DS_COMMAND structure + * + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_cmd_802_11_bg_scan_query(wlan_private * priv, HostCmd_DS_COMMAND * cmd) +{ + HostCmd_DS_802_11_BG_SCAN_QUERY *bgquery = &cmd->params.bgscanquery; + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_802_11_BG_SCAN_QUERY); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_802_11_BG_SCAN_QUERY) + S_DS_GEN); + + bgquery->Flush = 1; + + return WLAN_STATUS_SUCCESS; +} diff --git a/drivers/net/wireless/marvell8686/wlan_scan.h b/drivers/net/wireless/marvell8686/wlan_scan.h new file mode 100644 index 0000000..2274cd3 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_scan.h @@ -0,0 +1,284 @@ +/** @file wlan_scan.h + * + * @brief Interface for the wlan network scan routines + * + * Driver interface functions and type declarations for the scan module + * implemented in wlan_scan.c. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + * + * @sa wlan_scan.c + */ +/************************************************************* +Change Log: + 01/11/06: Initial revision. New scan code, relocate related functions + +************************************************************/ + +#ifndef _WLAN_SCAN_H +#define _WLAN_SCAN_H + +//! Infrastructure BSS scan type in wlan_scan_cmd_config +#define WLAN_SCAN_BSS_TYPE_BSS 1 + +//! Adhoc BSS scan type in wlan_scan_cmd_config +#define WLAN_SCAN_BSS_TYPE_IBSS 2 + +//! Adhoc or Infrastructure BSS scan type in wlan_scan_cmd_config, no filter +#define WLAN_SCAN_BSS_TYPE_ANY 3 + +/** @brief Maximum buffer space for beacons retrieved from scan responses + * 4000 has successfully stored up to 40 beacons + * 6000 has successfully stored the max scan results (max 64) + */ +#define MAX_SCAN_BEACON_BUFFER 6000 + +/** + * @brief Buffer pad space for newly allocated beacons/probe responses + * + * Beacons are typically 6 bytes longer than an equivalent probe response. + * For each scan response stored, allocate an extra byte pad at the end to + * allow easy expansion to store a beacon in the same memory a probe reponse + * previously contained + */ +#define SCAN_BEACON_ENTRY_PAD 6 + +//! Scan time specified in the channel TLV for each channel for passive scans +#define MRVDRV_PASSIVE_SCAN_CHAN_TIME 100 + +//! Scan time specified in the channel TLV for each channel for active scans +#define MRVDRV_ACTIVE_SCAN_CHAN_TIME 100 + +//! Scan time specified in the channel TLV for each channel for specific scans +#define MRVDRV_SPECIFIC_SCAN_CHAN_TIME 100 + +//! Max passive scan time for each channel in milliseconds +#define MRVDRV_MAX_PASSIVE_SCAN_CHAN_TIME 2000 + +//! Max active scan time for each channel in milliseconds +#define MRVDRV_MAX_ACTIVE_SCAN_CHAN_TIME 500 + +/** + * Max total scan time in milliseconds + * The total scan time should be less than scan command timeout value (10s) + */ +#define MRVDRV_MAX_TOTAL_SCAN_TIME (MRVDRV_TIMER_10S - MRVDRV_TIMER_1S) + +/** + * @brief Structure used internally in the wlan driver to configure a scan. + * + * Sent to the command processing module to configure the firmware + * scan command prepared by wlan_cmd_802_11_scan. + * + * @sa wlan_scan_networks + * + */ +typedef struct +{ + /** + * @brief BSS Type to be sent in the firmware command + * + * Field can be used to restrict the types of networks returned in the + * scan. Valid settings are: + * + * - WLAN_SCAN_BSS_TYPE_BSS (infrastructure) + * - WLAN_SCAN_BSS_TYPE_IBSS (adhoc) + * - WLAN_SCAN_BSS_TYPE_ANY (unrestricted, adhoc and infrastructure) + */ + u8 bssType; + + /** + * @brief Specific BSSID used to filter scan results in the firmware + */ + u8 specificBSSID[MRVDRV_ETH_ADDR_LEN]; + + /** + * @brief Length of TLVs sent in command starting at tlvBuffer + */ + int tlvBufferLen; + + /** + * @brief SSID TLV(s) and ChanList TLVs to be sent in the firmware command + * + * @sa TLV_TYPE_CHANLIST, MrvlIEtypes_ChanListParamSet_t + * @sa TLV_TYPE_SSID, MrvlIEtypes_SsIdParamSet_t + */ + u8 tlvBuffer[1]; //!< SSID TLV(s) and ChanList TLVs are stored here +} wlan_scan_cmd_config; + +/** + * @brief Sub-structure passed in wlan_ioctl_get_scan_table_entry for each BSS + * + * Fixed field information returned for the scan response in the IOCTL + * response. + */ +typedef struct +{ + u8 bssid[6]; //!< BSSID of this network + u8 channel; //!< Channel this beacon/probe response was detected + u8 rssi; //!< RSSI for the received packet + u64 networkTSF; //!< TSF value from the firmware at packet reception +} __ATTRIB_PACK__ wlan_ioctl_get_scan_table_fixed; + +/** + * @brief Structure passed in the wlan_ioctl_get_scan_table_info for each + * BSS returned in the WLAN_GET_SCAN_RESP IOCTL + * + * @sa wlan_get_scan_table_ioctl + */ +typedef struct +{ + + /** + * @brief Fixed field length included in the response. + * + * Length value is included so future fixed fields can be added to the + * response without breaking backwards compatibility. Use the length + * to find the offset for the bssInfoLength field, not a sizeof() calc. + */ + u32 fixedFieldLength; + + /** + * @brief Always present, fixed length data fields for the BSS + */ + wlan_ioctl_get_scan_table_fixed fixedFields; + + /** + * @brief Length of the BSS Information (probe resp or beacon) that + * follows starting at bssInfoBuffer + */ + u32 bssInfoLength; + + /** + * @brief Probe response or beacon scanned for the BSS. + * + * Field layout: + * - TSF 8 octets + * - Beacon Interval 2 octets + * - Capability Info 2 octets + * + * - IEEE Infomation Elements; variable number & length per 802.11 spec + */ + u8 bssInfoBuffer[1]; +} __ATTRIB_PACK__ wlan_ioctl_get_scan_table_entry; + +/** + * @brief WLAN_GET_SCAN_RESP private IOCTL struct to retrieve the scan table + * + * @sa wlan_get_scan_table_ioctl + */ +typedef struct +{ + + /** + * - Zero based scan entry to start retrieval in command request + * - Number of scans entires returned in command response + */ + u32 scanNumber; + + /** + * Buffer marker for multiple wlan_ioctl_get_scan_table_entry structures. + * Each struct is padded to the nearest 32 bit boundary. + */ + u8 scan_table_entry_buffer[1]; + +} __ATTRIB_PACK__ wlan_ioctl_get_scan_table_info; + +/** + * @brief Structure used to store information for each beacon/probe response + */ +typedef struct +{ + WLAN_802_11_MAC_ADDRESS MacAddress; + + WLAN_802_11_SSID Ssid; + + /* WEP encryption requirement */ + u32 Privacy; + + /* receive signal strength in dBm */ + WLAN_802_11_RSSI Rssi; + + u32 Channel; + + u16 BeaconPeriod; + + u32 ATIMWindow; + u8 ERPFlags; + + WLAN_802_11_NETWORK_TYPE NetworkTypeInUse; + WLAN_802_11_NETWORK_INFRASTRUCTURE InfrastructureMode; + WLAN_802_11_RATES SupportedRates; + IEEEtypes_WmmParameter_t wmmIE; + + int extra_ie; + + u8 TimeStamp[8]; //!< TSF value included in the beacon/probe response + + IEEEtypes_PhyParamSet_t PhyParamSet; + IEEEtypes_SsParamSet_t SsParamSet; + IEEEtypes_CapInfo_t Cap; + + u8 DataRates[WLAN_SUPPORTED_RATES]; + + u64 networkTSF; //!< TSF timestamp from the current firmware TSF + + IEEEtypes_CountryInfoFullSet_t CountryInfo; + + IEEEtypes_VendorSpecific_t wpaIE; + IEEEtypes_Generic_t rsnIE; + + IEEEtypes_VendorSpecific_t wpsIE; + + u8 *pBeaconBuf; //!< Pointer to the returned scan response + uint beaconBufSize; //!< Length of the stored scan response + uint beaconBufSizeMax; //!< Max allocated size for updated scan response + +} BSSDescriptor_t; + +extern int SSIDcmp(WLAN_802_11_SSID * ssid1, WLAN_802_11_SSID * ssid2); +extern int FindSSIDInList(wlan_adapter * Adapter, WLAN_802_11_SSID * ssid, + u8 * bssid, int mode); +extern int FindBestSSIDInList(wlan_adapter * Adapter); +extern int FindBSSIDInList(wlan_adapter * Adapter, u8 * bssid, int mode); + +extern int FindBestNetworkSsid(wlan_private * priv, WLAN_802_11_SSID * pSSID); + +extern int SendSpecificSSIDScan(wlan_private * priv, + WLAN_802_11_SSID * pRequestedSSID); +extern int SendSpecificBSSIDScan(wlan_private * priv, u8 * bssid); + +extern int wlan_get_scan_table_ioctl(wlan_private * priv, struct iwreq *wrq); +extern int wlan_set_user_scan_ioctl(wlan_private * priv, struct iwreq *wrq); + +extern int wlan_associate(wlan_private * priv, BSSDescriptor_t * pBSSDesc); + +extern int wlan_cmd_802_11_scan(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *pdata_buf); + +extern void wlan_scan_update_tsf_timestamps(wlan_private * priv, + BSSDescriptor_t * pNewBssDesc); + +extern int wlan_ret_802_11_scan(wlan_private * priv, + HostCmd_DS_COMMAND * resp); + +extern int wlan_extscan_ioctl(wlan_private * priv, struct ifreq *req); + +extern int sendBgScanQueryCmd(wlan_private * priv); +extern int wlan_bg_scan_enable(wlan_private * priv, BOOLEAN enable); +extern int wlan_do_bg_scan_config_ioctl(wlan_private * priv, + struct ifreq *req); +extern int wlan_cmd_802_11_bg_scan_config(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, + int cmd_action, void *pdata_buf); +extern int wlan_cmd_802_11_bg_scan_query(wlan_private * priv, + HostCmd_DS_COMMAND * cmd); + +#ifdef __KERNEL__ +extern int wlan_get_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra); +extern int wlan_set_scan(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra); +#endif + +#endif /* _WLAN_SCAN_H */ diff --git a/drivers/net/wireless/marvell8686/wlan_thread.h b/drivers/net/wireless/marvell8686/wlan_thread.h new file mode 100644 index 0000000..3d9c01c --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_thread.h @@ -0,0 +1,58 @@ +/* + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ + +#ifndef __WLAN_THREAD_H_ +#define __WLAN_THREAD_H_ + +#include + +typedef struct +{ + struct task_struct *task; + wait_queue_head_t waitQ; + pid_t pid; + void *priv; +} wlan_thread; + +static inline void +wlan_activate_thread(wlan_thread * thr) +{ + /** Record the thread pid */ + thr->pid = current->pid; + + /** Initialize the wait queue */ + init_waitqueue_head(&thr->waitQ); +} + +static inline void +wlan_deactivate_thread(wlan_thread * thr) +{ + ENTER(); + + LEAVE(); +} + +static inline void +wlan_create_thread(int (*wlanfunc) (void *), wlan_thread * thr, char *name) +{ + thr->task = kthread_run(wlanfunc, thr, "%s", name); +} + +static inline int +wlan_terminate_thread(wlan_thread * thr) +{ + ENTER(); + + /* Check if the thread is active or not */ + if (!thr->pid) { + PRINTM(INFO, "Thread does not exist\n"); + return -1; + } + kthread_stop(thr->task); + + LEAVE(); + return 0; +} + +#endif diff --git a/drivers/net/wireless/marvell8686/wlan_tx.c b/drivers/net/wireless/marvell8686/wlan_tx.c new file mode 100644 index 0000000..f04f6e1 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_tx.c @@ -0,0 +1,286 @@ +/** @file wlan_tx.c + * @brief This file contains the handling of TX in wlan + * driver. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 09/28/05: Add Doxygen format comments + 12/13/05: Add Proprietary periodic sleep support + 01/05/06: Add kernel 2.6.x support + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support +********************************************************/ + +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ + +/******************************************************** + Global Variables +********************************************************/ + +/******************************************************** + Local Functions +********************************************************/ + +/** + * @brief This function processes a single packet and sends + * to IF layer + * + * @param priv A pointer to wlan_private structure + * @param skb A pointer to skb which includes TX packet + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +static int +SendSinglePacket(wlan_private * priv, struct sk_buff *skb) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + TxPD LocalTxPD; + TxPD *pLocalTxPD = &LocalTxPD; + u8 *ptr = Adapter->TmpTxBuf; + + ENTER(); + + if (!skb->len || (skb->len > MRVDRV_ETH_TX_PACKET_BUFFER_SIZE)) { + PRINTM(ERROR, "Tx Error: Bad skb length %d : %d\n", + skb->len, MRVDRV_ETH_TX_PACKET_BUFFER_SIZE); + ret = WLAN_STATUS_FAILURE; + goto done; + } + + memset(pLocalTxPD, 0, sizeof(TxPD)); + + pLocalTxPD->TxPacketLength = skb->len; + + if (Adapter->wmm.enabled) { + /* + * original skb->priority has been overwritten + * by wmm_map_and_add_skb() + */ + pLocalTxPD->Priority = (u8) skb->priority; + + pLocalTxPD->PktDelay_2ms = wmm_compute_driver_packet_delay(skb); + } + + if (pLocalTxPD->Priority < NELEMENTS(Adapter->wmm.userPriPktTxCtrl)) { + /* + * Set the priority specific TxControl field, setting of 0 will + * cause the default value to be used later in this function + */ + pLocalTxPD->TxControl + = Adapter->wmm.userPriPktTxCtrl[pLocalTxPD->Priority]; + } + + if (Adapter->PSState != PS_STATE_FULL_POWER) { + if (TRUE == CheckLastPacketIndication(priv)) { + Adapter->TxLockFlag = TRUE; + pLocalTxPD->Flags = MRVDRV_TxPD_POWER_MGMT_LAST_PACKET; + } + } + + /* offset of actual data */ + pLocalTxPD->TxPacketLocation = sizeof(TxPD); + + if (pLocalTxPD->TxControl == 0) { + /* TxCtrl set by user or default */ + pLocalTxPD->TxControl = Adapter->PktTxCtrl; + } + + endian_convert_TxPD(pLocalTxPD); + + memcpy((u8 *) pLocalTxPD->TxDestAddr, skb->data, MRVDRV_ETH_ADDR_LEN); + + ptr += SDIO_HEADER_LEN; + memcpy(ptr, pLocalTxPD, sizeof(TxPD)); + + ptr += sizeof(TxPD); + + memcpy(ptr, skb->data, skb->len); + + ret = sbi_host_to_card(priv, MVMS_DAT, Adapter->TmpTxBuf, + skb->len + sizeof(TxPD)); + if (ret) { + PRINTM(ERROR, + "SendSinglePacket Error: sbi_host_to_card failed: 0x%X\n", + ret); + Adapter->dbg.num_tx_host_to_card_failure++; + goto done; + } + + PRINTM(DATA, "Data => FW\n"); + DBG_HEXDUMP(DAT_D, "Tx", ptr - sizeof(TxPD), + MIN(skb->len + sizeof(TxPD), MAX_DATA_DUMP_LEN)); + + wmm_process_fw_iface_tx_xfer_start(priv); + + done: + if (!ret) { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + } else { + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + } + + /* need to be freed in all cases */ + os_free_tx_packet(priv); + + LEAVE(); + return ret; +} + +/******************************************************** + Global functions +********************************************************/ + +/** + * @brief This function checks the conditions and sends packet to IF + * layer if everything is ok. + * + * @param priv A pointer to wlan_private structure + * @return n/a + */ +void +wlan_process_tx(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + OS_INTERRUPT_SAVE_AREA; + + if (priv->wlan_dev.dnld_sent) { + PRINTM(MSG, "TX Error: dnld_sent = %d, not sending\n", + priv->wlan_dev.dnld_sent); + goto done; + } + + SendSinglePacket(priv, Adapter->CurrentTxSkb); + OS_INT_DISABLE; + priv->adapter->HisRegCpy &= ~HIS_TxDnLdRdy; + OS_INT_RESTORE; + + done: + LEAVE(); +} + +/** + * @brief This function queues the packet received from + * kernel/upper layer and wake up the main thread to handle it. + * + * @param priv A pointer to wlan_private structure + * @param skb A pointer to skb which includes TX packet + * @return WLAN_STATUS_SUCCESS or WLAN_STATUS_FAILURE + */ +int +wlan_tx_packet(wlan_private * priv, struct sk_buff *skb) +{ + ulong flags; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + HEXDUMP("TX Data", skb->data, MIN(skb->len, 100)); + + spin_lock_irqsave(&Adapter->CurrentTxLock, flags); + + wmm_map_and_add_skb(priv, skb); + wake_up_interruptible(&priv->MainThread.waitQ); + spin_unlock_irqrestore(&Adapter->CurrentTxLock, flags); + + LEAVE(); + + return ret; +} + +/** + * @brief This function tells firmware to send a NULL data packet. + * + * @param priv A pointer to wlan_private structure + * @param flags Trasnit Pkt Flags + * @return n/a + */ +int +SendNullPacket(wlan_private * priv, u8 flags) +{ + wlan_adapter *Adapter = priv->adapter; + TxPD txpd = { 0 }; + int ret = WLAN_STATUS_SUCCESS; + u8 *ptr = Adapter->TmpTxBuf; + + ENTER(); + + if (Adapter->SurpriseRemoved == TRUE) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + if (Adapter->MediaConnectStatus == WlanMediaStateDisconnected) { + ret = WLAN_STATUS_FAILURE; + goto done; + } + + memset(&txpd, 0, sizeof(TxPD)); + + txpd.TxControl = Adapter->PktTxCtrl; + txpd.Flags = flags; + txpd.Priority = WMM_HIGHEST_PRIORITY; + txpd.TxPacketLocation = sizeof(TxPD); + + endian_convert_TxPD(&txpd); + + ptr += SDIO_HEADER_LEN; + memcpy(ptr, &txpd, sizeof(TxPD)); + + ret = sbi_host_to_card(priv, MVMS_DAT, Adapter->TmpTxBuf, sizeof(TxPD)); + + if (ret != 0) { + PRINTM(ERROR, "TX Error: SendNullPacket failed!\n"); + Adapter->dbg.num_tx_host_to_card_failure++; + goto done; + } + PRINTM(DATA, "Null data => FW\n"); + DBG_HEXDUMP(DAT_D, "Tx", ptr, sizeof(TxPD)); + + done: + LEAVE(); + return ret; +} + +/** + * @brief This function check if we need send last packet indication. + * + * @param priv A pointer to wlan_private structure + * + * @return TRUE or FALSE + */ +BOOLEAN +CheckLastPacketIndication(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + BOOLEAN ret = FALSE; + BOOLEAN prop_ps = TRUE; + + ENTER(); + + if (Adapter->sleep_period.period == 0 || Adapter->gen_null_pkg == FALSE /* for UPSD certification tests */ + ) { + LEAVE(); + return ret; + } + + if (wmm_lists_empty(priv)) { + if (((Adapter->CurBssParams.wmm_uapsd_enabled == TRUE) + && (Adapter->wmm.qosinfo != 0)) || prop_ps) { + ret = TRUE; + } + } + + LEAVE(); + return ret; +} diff --git a/drivers/net/wireless/marvell8686/wlan_types.h b/drivers/net/wireless/marvell8686/wlan_types.h new file mode 100644 index 0000000..76e4f25 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_types.h @@ -0,0 +1,1050 @@ +/** @file wlan_types.h + * @brief This header file contains definition for global types + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/************************************************************* +Change log: + 10/11/05: add Doxygen format comments + 01/11/06: Add IEEE Association response type. Add TSF TLV information. + 01/31/06: Add support to selectively enabe the FW Scan channel filter + 04/10/06: Add power_adapt_cfg_ext command + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/03/06: Add auto_tx hostcmd + 08/28/06: Add LED_CTRL hostcmd +************************************************************/ + +#ifndef _WLAN_TYPES_ +#define _WLAN_TYPES_ + +#ifndef __KERNEL__ +typedef char s8; +typedef unsigned char u8; + +typedef signed short s16; +typedef unsigned short u16; + +typedef signed long s32; +typedef unsigned long u32; + +typedef signed long long s64; +typedef unsigned long long u64; + +typedef u32 dma_addr_t; +typedef u32 dma64_addr_t; +/* Dma addresses are 32-bits wide. */ +#ifndef __ATTRIB_ALIGN__ +#define __ATTRIB_ALIGN__ __attribute__((aligned(4))) +#endif + +#ifndef __ATTRIB_PACK__ +#define __ATTRIB_PACK__ __attribute__ ((packed)) +#endif +#endif + +typedef long LONG; +typedef unsigned long long ULONGLONG; +typedef u32 WLAN_OID; + +#define MRVDRV_MAX_MULTICAST_LIST_SIZE 32 +#define MRVDRV_MAX_CHANNEL_SIZE 14 +#define MRVDRV_ETH_ADDR_LEN 6 +#define MRVDRV_MAX_SSID_LENGTH 32 +#define MRVDRV_MAX_BSS_DESCRIPTS 16 +/** WEP list macros & data structures */ +#define MRVL_KEY_BUFFER_SIZE_IN_BYTE 16 +#define MRVL_MAX_KEY_WPA_KEY_LENGTH 32 + +#define HOSTCMD_SUPPORTED_RATES G_SUPPORTED_RATES + +#define BAND_B (0x01) +#define BAND_G (0x02) +#define ALL_802_11_BANDS (BAND_B | BAND_G) + +#define B_SUPPORTED_RATES 8 +#define G_SUPPORTED_RATES 14 + +#define WLAN_SUPPORTED_RATES 14 +#define WLAN_MAX_SSID_LENGTH 32 + +#define MAX_POWER_ADAPT_GROUP 5 + +#define MRVDRV_MAX_SSID_LIST_LENGTH 10 + +typedef u8 WLAN_802_11_RATES[WLAN_SUPPORTED_RATES]; +typedef u8 WLAN_802_11_MAC_ADDRESS[ETH_ALEN]; + +/** WLAN_802_11_NETWORK_TYPE */ +typedef enum _WLAN_802_11_NETWORK_TYPE +{ + Wlan802_11FH, + Wlan802_11DS, + /*defined as upper bound */ + Wlan802_11NetworkTypeMax +} WLAN_802_11_NETWORK_TYPE, *PWLAN_802_11_NETWORK_TYPE; + +/** WLAN_802_11_NETWORK_INFRASTRUCTURE */ +typedef enum _WLAN_802_11_NETWORK_INFRASTRUCTURE +{ + Wlan802_11IBSS, + Wlan802_11Infrastructure, + Wlan802_11AutoUnknown, + /*defined as upper bound */ + Wlan802_11InfrastructureMax +} WLAN_802_11_NETWORK_INFRASTRUCTURE, *PWLAN_802_11_NETWORK_INFRASTRUCTURE; + +#define IEEE_MAX_IE_SIZE 256 + +/** IEEE Type definitions */ +typedef enum _IEEEtypes_ElementId_e +{ + SSID = 0, + SUPPORTED_RATES = 1, + FH_PARAM_SET = 2, + DS_PARAM_SET = 3, + CF_PARAM_SET = 4, + + IBSS_PARAM_SET = 6, + + COUNTRY_INFO = 7, + + ERP_INFO = 42, + EXTENDED_SUPPORTED_RATES = 50, + + VENDOR_SPECIFIC_221 = 221, + WMM_IE = VENDOR_SPECIFIC_221, + + WPS_IE = VENDOR_SPECIFIC_221, + + WPA_IE = VENDOR_SPECIFIC_221, + RSN_IE = 48, + + EXTRA_IE = 133, +} __ATTRIB_PACK__ IEEEtypes_ElementId_e; + +#define CAPINFO_MASK (~( W_BIT_15 | W_BIT_14 | \ + W_BIT_12 | W_BIT_11 | W_BIT_9) ) + +typedef struct _IEEEtypes_CapInfo_t +{ + u8 Ess:1; + u8 Ibss:1; + u8 CfPollable:1; + u8 CfPollRqst:1; + u8 Privacy:1; + u8 ShortPreamble:1; + u8 Pbcc:1; + u8 ChanAgility:1; + u8 SpectrumMgmt:1; + u8 Rsrvd3:1; + u8 ShortSlotTime:1; + u8 Apsd:1; + u8 Rsvrd2:1; + u8 DSSSOFDM:1; + u8 Rsrvd1:2; +} __ATTRIB_PACK__ IEEEtypes_CapInfo_t; + +typedef struct +{ + u8 ElementId; + u8 Len; +} __ATTRIB_PACK__ IEEEtypes_Header_t; + +/** IEEEtypes_CfParamSet_t */ +typedef struct _IEEEtypes_CfParamSet_t +{ + u8 ElementId; + u8 Len; + u8 CfpCnt; + u8 CfpPeriod; + u16 CfpMaxDuration; + u16 CfpDurationRemaining; +} __ATTRIB_PACK__ IEEEtypes_CfParamSet_t; + +typedef struct IEEEtypes_IbssParamSet_t +{ + u8 ElementId; + u8 Len; + u16 AtimWindow; +} __ATTRIB_PACK__ IEEEtypes_IbssParamSet_t; + +/** IEEEtypes_SsParamSet_t */ +typedef union _IEEEtypes_SsParamSet_t +{ + IEEEtypes_CfParamSet_t CfParamSet; + IEEEtypes_IbssParamSet_t IbssParamSet; +} __ATTRIB_PACK__ IEEEtypes_SsParamSet_t; + +/** IEEEtypes_FhParamSet_t */ +typedef struct _IEEEtypes_FhParamSet_t +{ + u8 ElementId; + u8 Len; + u16 DwellTime; + u8 HopSet; + u8 HopPattern; + u8 HopIndex; +} __ATTRIB_PACK__ IEEEtypes_FhParamSet_t; + +typedef struct _IEEEtypes_DsParamSet_t +{ + u8 ElementId; + u8 Len; + u8 CurrentChan; +} __ATTRIB_PACK__ IEEEtypes_DsParamSet_t; + +/** IEEEtypes_DsParamSet_t */ +typedef union IEEEtypes_PhyParamSet_t +{ + IEEEtypes_FhParamSet_t FhParamSet; + IEEEtypes_DsParamSet_t DsParamSet; +} __ATTRIB_PACK__ IEEEtypes_PhyParamSet_t; + +typedef struct _IEEEtypes_ERPInfo_t +{ + u8 ElementId; + u8 Len; + u8 ERPFlags; +} __ATTRIB_PACK__ IEEEtypes_ERPInfo_t; + +typedef u16 IEEEtypes_AId_t; +typedef u16 IEEEtypes_StatusCode_t; + +typedef struct +{ + IEEEtypes_CapInfo_t Capability; + IEEEtypes_StatusCode_t StatusCode; + IEEEtypes_AId_t AId; + u8 IEBuffer[1]; +} __ATTRIB_PACK__ IEEEtypes_AssocRsp_t; + +typedef struct +{ + u8 ElementId; + u8 Len; + u8 Oui[3]; + u8 OuiType; + u8 OuiSubtype; + u8 Version; +} __ATTRIB_PACK__ IEEEtypes_VendorHeader_t; + +typedef struct +{ + IEEEtypes_VendorHeader_t VendHdr; + + /* IE Max - size of previous fields */ + u8 Data[IEEE_MAX_IE_SIZE - sizeof(IEEEtypes_VendorHeader_t)]; + +} +__ATTRIB_PACK__ IEEEtypes_VendorSpecific_t; + +typedef struct +{ + IEEEtypes_Header_t IeeeHdr; + + /* IE Max - size of previous fields */ + u8 Data[IEEE_MAX_IE_SIZE - sizeof(IEEEtypes_Header_t)]; + +} +__ATTRIB_PACK__ IEEEtypes_Generic_t; + +/** TLV type ID definition */ +#define PROPRIETARY_TLV_BASE_ID 0x0100 + +/* Terminating TLV Type */ +#define MRVL_TERMINATE_TLV_ID 0xffff + +#define TLV_TYPE_SSID 0x0000 +#define TLV_TYPE_RATES 0x0001 +#define TLV_TYPE_PHY_FH 0x0002 +#define TLV_TYPE_PHY_DS 0x0003 +#define TLV_TYPE_CF 0x0004 +#define TLV_TYPE_IBSS 0x0006 + +#define TLV_TYPE_DOMAIN 0x0007 + +#define TLV_TYPE_POWER_CAPABILITY 0x0021 + +#define TLV_TYPE_KEY_MATERIAL (PROPRIETARY_TLV_BASE_ID + 0) +#define TLV_TYPE_CHANLIST (PROPRIETARY_TLV_BASE_ID + 1) +#define TLV_TYPE_NUMPROBES (PROPRIETARY_TLV_BASE_ID + 2) +#define TLV_TYPE_RSSI_LOW (PROPRIETARY_TLV_BASE_ID + 4) +#define TLV_TYPE_SNR_LOW (PROPRIETARY_TLV_BASE_ID + 5) +#define TLV_TYPE_FAILCOUNT (PROPRIETARY_TLV_BASE_ID + 6) +#define TLV_TYPE_BCNMISS (PROPRIETARY_TLV_BASE_ID + 7) +#define TLV_TYPE_LED_GPIO (PROPRIETARY_TLV_BASE_ID + 8) +#define TLV_TYPE_LEDBEHAVIOR (PROPRIETARY_TLV_BASE_ID + 9) +#define TLV_TYPE_PASSTHROUGH (PROPRIETARY_TLV_BASE_ID + 10) +#define TLV_TYPE_POWER_TBL_2_4GHZ (PROPRIETARY_TLV_BASE_ID + 12) +#define TLV_TYPE_POWER_TBL_5GHZ (PROPRIETARY_TLV_BASE_ID + 13) +#define TLV_TYPE_WMMQSTATUS (PROPRIETARY_TLV_BASE_ID + 16) +#define TLV_TYPE_WILDCARDSSID (PROPRIETARY_TLV_BASE_ID + 18) +#define TLV_TYPE_TSFTIMESTAMP (PROPRIETARY_TLV_BASE_ID + 19) +#define TLV_TYPE_POWERADAPTCFGEXT (PROPRIETARY_TLV_BASE_ID + 20) +#define TLV_TYPE_RSSI_HIGH (PROPRIETARY_TLV_BASE_ID + 22) +#define TLV_TYPE_SNR_HIGH (PROPRIETARY_TLV_BASE_ID + 23) +#define TLV_TYPE_AUTO_TX (PROPRIETARY_TLV_BASE_ID + 24) +#define TLV_TYPE_WPS_ENROLLEE_PROBE_REQ_TLV (PROPRIETARY_TLV_BASE_ID + 27) +#define TLV_TYPE_STARTBGSCANLATER (PROPRIETARY_TLV_BASE_ID + 30) +#define TLV_TYPE_AUTH_TYPE (PROPRIETARY_TLV_BASE_ID + 31) +#define TLV_TYPE_PRE_BEACON_LOST (PROPRIETARY_TLV_BASE_ID + 73) + +/** TLV related data structures*/ +/** MrvlIEtypesHeader_t */ +typedef struct _MrvlIEtypesHeader +{ + u16 Type; + u16 Len; +} __ATTRIB_PACK__ MrvlIEtypesHeader_t; + +/** MrvlIEtypes_Data_t */ +typedef struct _MrvlIEtypes_Data_t +{ + MrvlIEtypesHeader_t Header; + u8 Data[1]; +} __ATTRIB_PACK__ MrvlIEtypes_Data_t; + +/** MrvlIEtypes_RatesParamSet_t */ +typedef struct _MrvlIEtypes_RatesParamSet_t +{ + MrvlIEtypesHeader_t Header; + u8 Rates[1]; +} __ATTRIB_PACK__ MrvlIEtypes_RatesParamSet_t; + +/** MrvlIEtypes_SsIdParamSet_t */ +typedef struct _MrvlIEtypes_SsIdParamSet_t +{ + MrvlIEtypesHeader_t Header; + u8 SsId[1]; +} __ATTRIB_PACK__ MrvlIEtypes_SsIdParamSet_t; + +/** MrvlIEtypes_WildCardSsIdParamSet_t */ +typedef struct _MrvlIEtypes_WildCardSsIdParamSet_t +{ + MrvlIEtypesHeader_t Header; + u8 MaxSsidLength; + u8 SsId[1]; +} __ATTRIB_PACK__ MrvlIEtypes_WildCardSsIdParamSet_t; + +/** ChanScanMode_t */ +typedef struct +{ + u8 PassiveScan:1; + u8 DisableChanFilt:1; + u8 Reserved_2_7:6; +} __ATTRIB_PACK__ ChanScanMode_t; + +/** ChanScanParamSet_t */ +typedef struct _ChanScanParamSet_t +{ + u8 RadioType; + u8 ChanNumber; + ChanScanMode_t ChanScanMode; + u16 MinScanTime; + u16 MaxScanTime; +} __ATTRIB_PACK__ ChanScanParamSet_t; + +/** MrvlIEtypes_ChanListParamSet_t */ +typedef struct _MrvlIEtypes_ChanListParamSet_t +{ + MrvlIEtypesHeader_t Header; + ChanScanParamSet_t ChanScanParam[1]; +} __ATTRIB_PACK__ MrvlIEtypes_ChanListParamSet_t; + +/** CfParamSet_t */ +typedef struct _CfParamSet_t +{ + u8 CfpCnt; + u8 CfpPeriod; + u16 CfpMaxDuration; + u16 CfpDurationRemaining; +} __ATTRIB_PACK__ CfParamSet_t; + +/** IbssParamSet_t */ +typedef struct _IbssParamSet_t +{ + u16 AtimWindow; +} __ATTRIB_PACK__ IbssParamSet_t; + +/** MrvlIEtypes_SsParamSet_t */ +typedef struct _MrvlIEtypes_SsParamSet_t +{ + MrvlIEtypesHeader_t Header; + union + { + CfParamSet_t CfParamSet[1]; + IbssParamSet_t IbssParamSet[1]; + } cf_ibss; +} __ATTRIB_PACK__ MrvlIEtypes_SsParamSet_t; + +/** FhParamSet_t */ +typedef struct _FhParamSet_t +{ + u16 DwellTime; + u8 HopSet; + u8 HopPattern; + u8 HopIndex; +} __ATTRIB_PACK__ FhParamSet_t; + +/** DsParamSet_t */ +typedef struct _DsParamSet_t +{ + u8 CurrentChan; +} __ATTRIB_PACK__ DsParamSet_t; + +/** MrvlIEtypes_PhyParamSet_t */ +typedef struct _MrvlIEtypes_PhyParamSet_t +{ + MrvlIEtypesHeader_t Header; + union + { + FhParamSet_t FhParamSet[1]; + DsParamSet_t DsParamSet[1]; + } fh_ds; +} __ATTRIB_PACK__ MrvlIEtypes_PhyParamSet_t; + +/** MrvlIEtypes_RsnParamSet_t */ +typedef struct _MrvlIEtypes_RsnParamSet_t +{ + MrvlIEtypesHeader_t Header; + u8 RsnIE[1]; +} __ATTRIB_PACK__ MrvlIEtypes_RsnParamSet_t; + +/** MrvlIEtypes_WmmParamSet_t */ +typedef struct _MrvlIEtypes_WmmParamSet_t +{ + MrvlIEtypesHeader_t Header; + u8 WmmIE[1]; +} __ATTRIB_PACK__ MrvlIEtypes_WmmParamSet_t; + +typedef struct +{ + MrvlIEtypesHeader_t Header; + u8 QueueIndex; + u8 Disabled; + u8 Reserved1; + u8 Reserved2; + u8 FlowRequired; + u8 FlowCreated; + u32 Reserved3; +} __ATTRIB_PACK__ MrvlIEtypes_WmmQueueStatus_t; + +/** Table of TSF values returned in the scan result */ +typedef struct +{ + MrvlIEtypesHeader_t Header; + u64 tsfTable[1]; +} __ATTRIB_PACK__ MrvlIEtypes_TsfTimestamp_t; + +/** Local Power Capability */ +typedef struct _MrvlIEtypes_PowerCapability_t +{ + MrvlIEtypesHeader_t Header; + s8 MinPower; + s8 MaxPower; +} __ATTRIB_PACK__ MrvlIEtypes_PowerCapability_t; + +/** MrvlIEtypes_RssiParamSet_t */ +typedef struct _MrvlIEtypes_RssiThreshold_t +{ + MrvlIEtypesHeader_t Header; + u8 RSSIValue; + u8 RSSIFreq; +} __ATTRIB_PACK__ MrvlIEtypes_RssiThreshold_t; + +/** MrvlIEtypes_SnrThreshold_t */ +typedef struct _MrvlIEtypes_SnrThreshold_t +{ + MrvlIEtypesHeader_t Header; + u8 SNRValue; + u8 SNRFreq; +} __ATTRIB_PACK__ MrvlIEtypes_SnrThreshold_t; + +/** MrvlIEtypes_PreBeaconLost_t */ +typedef struct _MrvlIEtypes_PreBeaconLost_t +{ + MrvlIEtypesHeader_t Header; + u8 PreBeaconLost; + u8 Reserved; +} __ATTRIB_PACK__ MrvlIEtypes_PreBeaconLost_t; + +/** MrvlIEtypes_FailureCount_t */ +typedef struct _MrvlIEtypes_FailureCount_t +{ + MrvlIEtypesHeader_t Header; + u8 FailValue; + u8 FailFreq; +} __ATTRIB_PACK__ MrvlIEtypes_FailureCount_t; + +/** MrvlIEtypes_BeaconsMissed_t */ +typedef struct _MrvlIEtypes_BeaconsMissed_t +{ + MrvlIEtypesHeader_t Header; + u8 BeaconMissed; + u8 Reserved; +} __ATTRIB_PACK__ MrvlIEtypes_BeaconsMissed_t; + +/** MrvlIEtypes_NumProbes_t */ +typedef struct _MrvlIEtypes_NumProbes_t +{ + MrvlIEtypesHeader_t Header; + u16 NumProbes; +} __ATTRIB_PACK__ MrvlIEtypes_NumProbes_t; + +/** MrvlIEtypes_StartBGScanLater_t */ +typedef struct _MrvlIEtypes_StartBGScanLater_t +{ + MrvlIEtypesHeader_t Header; + u16 StartLater; +} __ATTRIB_PACK__ MrvlIEtypes_StartBGScanLater_t; + +typedef struct _LedGpio_t +{ + u8 LedNum; /* LED # mapped to GPIO pin # below */ + u8 GpioNum; /* GPIO pin # used to control LED # above */ +} __ATTRIB_PACK__ LedGpio_t; + +/** MrvlIEtypes_LedGpio_t */ +typedef struct _MrvlIEtypes_LedGpio_t +{ + MrvlIEtypesHeader_t Header; + LedGpio_t LedGpio[1]; +} __ATTRIB_PACK__ MrvlIEtypes_LedGpio_t; + +/** MrvlIEtypes_LedBehavior_t */ +typedef struct _MrvlIEtypes_LedBehavior_t +{ + MrvlIEtypesHeader_t Header; + u8 FirmwareState; /* Firmware State */ + u8 LedNum; /* LED # */ + u8 LedState; /* LED State corresponding to Firmware State */ + u8 LedArgs; /* Arguments for LED State */ +} __ATTRIB_PACK__ MrvlIEtypes_LedBehavior_t; + +typedef struct _PA_Group_t +{ + u16 PowerAdaptLevel; + u16 RateBitmap; + u32 Reserved; +} __ATTRIB_PACK__ PA_Group_t; + +/** MrvlIEtypes_PA_Group_t */ +typedef struct _MrvlIEtypes_PowerAdapt_Group_t +{ + MrvlIEtypesHeader_t Header; + PA_Group_t PA_Group[MAX_POWER_ADAPT_GROUP]; +} __ATTRIB_PACK__ MrvlIEtypes_PowerAdapt_Group_t; + +typedef struct _AutoTx_MacFrame_t +{ + u16 Interval; /* in seconds */ + u8 Priority; /* User Priority: 0~7, ignored if non-WMM */ + u8 Reserved; /* set to 0 */ + u16 FrameLen; /* Length of MAC frame payload */ + u8 DestMacAddr[ETH_ALEN]; + u8 SrcMacAddr[ETH_ALEN]; + u8 Payload[]; +} __ATTRIB_PACK__ AutoTx_MacFrame_t; + +/** MrvlIEtypes_AutoTx_t */ +typedef struct _MrvlIEtypes_AutoTx_t +{ + MrvlIEtypesHeader_t Header; + AutoTx_MacFrame_t AutoTx_MacFrame; +} __ATTRIB_PACK__ MrvlIEtypes_AutoTx_t; + +typedef struct +{ + u8 value; + u8 Freq; +} Threshold; + +typedef struct +{ + u16 EventsBitmap; /* bit 0: RSSI low, bit 1: SNR low, + * bit 2: RSSI high, bit 3: SNR high + */ + Threshold Rssi_low; + Threshold Snr_low; + Threshold Rssi_high; + Threshold Snr_high; + u8 Rssi_low_count; + u8 Snr_low_count; + u8 Rssi_high_count; + u8 Snr_high_count; +} wlan_subscribe_event; + +/** Auth type to be used in the Authentication portion of an Assoc seq */ +typedef struct +{ + MrvlIEtypesHeader_t Header; + u16 AuthType; +} __ATTRIB_PACK__ MrvlIEtypes_AuthType_t; + +#define MRVDRV_MAX_SUBBAND_802_11D 83 +#define COUNTRY_CODE_LEN 3 + +/** Data structure for Country IE*/ +typedef struct _IEEEtypes_SubbandSet +{ + u8 FirstChan; + u8 NoOfChan; + u8 MaxTxPwr; +} __ATTRIB_PACK__ IEEEtypes_SubbandSet_t; + +typedef struct _IEEEtypes_CountryInfoSet +{ + u8 ElementId; + u8 Len; + u8 CountryCode[COUNTRY_CODE_LEN]; + IEEEtypes_SubbandSet_t Subband[1]; +} __ATTRIB_PACK__ IEEEtypes_CountryInfoSet_t; + +typedef struct _IEEEtypes_CountryInfoFullSet +{ + u8 ElementId; + u8 Len; + u8 CountryCode[COUNTRY_CODE_LEN]; + IEEEtypes_SubbandSet_t Subband[MRVDRV_MAX_SUBBAND_802_11D]; +} __ATTRIB_PACK__ IEEEtypes_CountryInfoFullSet_t; + +typedef struct _MrvlIEtypes_DomainParamSet +{ + MrvlIEtypesHeader_t Header; + u8 CountryCode[COUNTRY_CODE_LEN]; + IEEEtypes_SubbandSet_t Subband[1]; +} __ATTRIB_PACK__ MrvlIEtypes_DomainParamSet_t; + +/** Size of a TSPEC. Used to allocate necessary buffer space in commands */ +#define WMM_TSPEC_SIZE 63 + +/** Extra IE bytes allocated in messages for appended IEs after a TSPEC */ +#define WMM_ADDTS_EXTRA_IE_BYTES 256 + +/** Extra TLV bytes allocated in messages for configuring WMM Queues */ +#define WMM_QUEUE_CONFIG_EXTRA_TLV_BYTES 64 +/** Maximum number of AC QOS queues available in the driver/firmware */ +#define MAX_AC_QUEUES 4 + +/** enum of WMM AC_QUEUES */ +typedef enum +{ + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VO, +} __ATTRIB_PACK__ wlan_wmm_ac_e; + +/** data structure of WMM QoS information */ +typedef struct +{ + u8 ParaSetCount:4; + u8 Reserved:3; + u8 QosUAPSD:1; +} __ATTRIB_PACK__ IEEEtypes_WmmQosInfo_t; + +typedef struct +{ + u8 Aifsn:4; + u8 Acm:1; + u8 Aci:2; + u8 Reserved:1; +} __ATTRIB_PACK__ IEEEtypes_WmmAciAifsn_t; + +/** data structure of WMM ECW */ +typedef struct +{ + u8 EcwMin:4; + u8 EcwMax:4; +} __ATTRIB_PACK__ IEEEtypes_WmmEcw_t; + +/** data structure of WMM AC parameters */ +typedef struct +{ + IEEEtypes_WmmAciAifsn_t AciAifsn; + IEEEtypes_WmmEcw_t Ecw; + u16 TxopLimit; +} __ATTRIB_PACK__ IEEEtypes_WmmAcParameters_t; + +/** data structure of WMM Info IE */ +typedef struct +{ + + /** + * WMM Info IE - Vendor Specific Header: + * ElementId [221/0xdd] + * Len [7] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [0] + * Version [1] + */ + IEEEtypes_VendorHeader_t VendHdr; + + IEEEtypes_WmmQosInfo_t QoSInfo; + +} __ATTRIB_PACK__ IEEEtypes_WmmInfo_t; + +/** data structure of WMM parameter IE */ +typedef struct +{ + /** + * WMM Parameter IE - Vendor Specific Header: + * ElementId [221/0xdd] + * Len [24] + * Oui [00:50:f2] + * OuiType [2] + * OuiSubType [1] + * Version [1] + */ + IEEEtypes_VendorHeader_t VendHdr; + + IEEEtypes_WmmQosInfo_t QoSInfo; + u8 Reserved; + + /** AC Parameters Record WMM_AC_BE, WMM_AC_BK, WMM_AC_VI, WMM_AC_VO */ + IEEEtypes_WmmAcParameters_t AcParams[MAX_AC_QUEUES]; + +} __ATTRIB_PACK__ IEEEtypes_WmmParameter_t; + +/** + * @brief Firmware command structure to retrieve the firmware WMM status. + * + * Used to retrieve the status of each WMM AC Queue in TLV + * format (MrvlIEtypes_WmmQueueStatus_t) as well as the current WMM + * parameter IE advertised by the AP. + * + * Used in response to a MACREG_INT_CODE_WMM_STATUS_CHANGE event signalling + * a QOS change on one of the ACs or a change in the WMM Parameter in + * the Beacon. + * + * TLV based command, byte arrays used for max sizing purpose. There are no + * arguments sent in the command, the TLVs are returned by the firmware. + */ +typedef struct +{ + u8 queueStatusTlv[sizeof(MrvlIEtypes_WmmQueueStatus_t) * MAX_AC_QUEUES]; + u8 wmmParamTlv[sizeof(IEEEtypes_WmmParameter_t) + 2]; + +} +__ATTRIB_PACK__ HostCmd_DS_WMM_GET_STATUS; + +/** + * @brief Enumeration for the command result from an ADDTS or DELTS command + */ +typedef enum +{ + TSPEC_RESULT_SUCCESS = 0, + TSPEC_RESULT_EXEC_FAILURE = 1, + TSPEC_RESULT_TIMEOUT = 2, + TSPEC_RESULT_DATA_INVALID = 3, + +} __ATTRIB_PACK__ wlan_wmm_tspec_result_e; + +/** + * @brief IOCTL structure to send an ADDTS request and retrieve the response. + * + * IOCTL structure from the application layer relayed to firmware to + * instigate an ADDTS management frame with an appropriate TSPEC IE as well + * as any additional IEs appended in the ADDTS Action frame. + * + * @sa wlan_wmm_addts_req_ioctl + */ +typedef struct +{ + wlan_wmm_tspec_result_e commandResult; + u32 timeout_ms; + + u8 ieeeStatusCode; + + u8 tspecData[WMM_TSPEC_SIZE]; + + u8 addtsExtraIEBuf[WMM_ADDTS_EXTRA_IE_BYTES]; + +} __ATTRIB_PACK__ wlan_ioctl_wmm_addts_req_t; + +/** + * @brief IOCTL structure to send a DELTS request. + * + * IOCTL structure from the application layer relayed to firmware to + * instigate an DELTS management frame with an appropriate TSPEC IE. + * + * @sa wlan_wmm_delts_req_ioctl + */ +typedef struct +{ + wlan_wmm_tspec_result_e commandResult; //!< Firmware execution result + + u8 ieeeReasonCode; //!< IEEE reason code sent, unused for WMM + + u8 tspecData[WMM_TSPEC_SIZE]; //!< TSPEC to send in the DELTS + +} __ATTRIB_PACK__ wlan_ioctl_wmm_delts_req_t; + +/** + * @brief Internal command structure used in executing an ADDTS command. + * + * Relay information between the IOCTL layer and the firmware command and + * command response procedures. + * + * @sa wlan_wmm_addts_req_ioctl + * @sa wlan_cmd_wmm_addts_req + * @sa wlan_cmdresp_wmm_addts_req + */ +typedef struct +{ + wlan_wmm_tspec_result_e commandResult; + u32 timeout_ms; + + u8 dialogToken; + u8 ieeeStatusCode; + + int tspecDataLen; + u8 tspecData[WMM_TSPEC_SIZE]; + u8 addtsExtraIEBuf[WMM_ADDTS_EXTRA_IE_BYTES]; + +} wlan_cmd_wmm_addts_req_t; + +/** + * @brief Internal command structure used in executing an DELTS command. + * + * Relay information between the IOCTL layer and the firmware command and + * command response procedures. + * + * @sa wlan_wmm_delts_req_ioctl + * @sa wlan_cmd_wmm_delts_req + * @sa wlan_cmdresp_wmm_delts_req + */ +typedef struct +{ + wlan_wmm_tspec_result_e commandResult; + + u8 dialogToken; + + u8 ieeeReasonCode; + + int tspecDataLen; + u8 tspecData[WMM_TSPEC_SIZE]; + +} wlan_cmd_wmm_delts_req_t; + +/** + * @brief Command structure for the HostCmd_CMD_WMM_ADDTS_REQ firmware command + * + */ +typedef struct +{ + wlan_wmm_tspec_result_e commandResult; + u32 timeout_ms; + + u8 dialogToken; + u8 ieeeStatusCode; + u8 tspecData[WMM_TSPEC_SIZE]; + u8 addtsExtraIEBuf[WMM_ADDTS_EXTRA_IE_BYTES]; + +} __ATTRIB_PACK__ HostCmd_DS_WMM_ADDTS_REQ; + +/** + * @brief Command structure for the HostCmd_CMD_WMM_DELTS_REQ firmware command + */ +typedef struct +{ + wlan_wmm_tspec_result_e commandResult; + u8 dialogToken; + u8 ieeeReasonCode; + u8 tspecData[WMM_TSPEC_SIZE]; + +} __ATTRIB_PACK__ HostCmd_DS_WMM_DELTS_REQ; + +/** + * @brief Enumeration for the action field in the Queue configure command + */ +typedef enum +{ + WMM_QUEUE_CONFIG_ACTION_GET = 0, + WMM_QUEUE_CONFIG_ACTION_SET = 1, + WMM_QUEUE_CONFIG_ACTION_DEFAULT = 2, + + WMM_QUEUE_CONFIG_ACTION_MAX +} __ATTRIB_PACK__ wlan_wmm_queue_config_action_e; + +/** + * @brief Command structure for the HostCmd_CMD_WMM_QUEUE_CONFIG firmware cmd + * + * Set/Get/Default the Queue parameters for a specific AC in the firmware. + * + */ +typedef struct +{ + wlan_wmm_queue_config_action_e action; //!< Set, Get, or Default + wlan_wmm_ac_e accessCategory; //!< WMM_AC_BK(0) to WMM_AC_VO(3) + + /** @brief MSDU lifetime expiry per 802.11e + * + * - Ignored if 0 on a set command + * - Set to the 802.11e specified 500 TUs when defaulted + */ + u16 msduLifetimeExpiry; + + u8 tlvBuffer[WMM_QUEUE_CONFIG_EXTRA_TLV_BYTES]; //!< Not supported yet + +} __ATTRIB_PACK__ HostCmd_DS_WMM_QUEUE_CONFIG; + +/** + * @brief Internal command structure used in executing a queue config command. + * + * Relay information between the IOCTL layer and the firmware command and + * command response procedures. + * + * @sa wlan_wmm_queue_config_ioctl + * @sa wlan_cmd_wmm_queue_config + * @sa wlan_cmdresp_wmm_queue_config + */ +typedef struct +{ + wlan_wmm_queue_config_action_e action; //!< Set, Get, or Default + wlan_wmm_ac_e accessCategory; //!< WMM_AC_BK(0) to WMM_AC_VO(3) + u16 msduLifetimeExpiry; //!< lifetime expiry in TUs + + int tlvBufLen; //!< Not supported yet + u8 tlvBuffer[WMM_QUEUE_CONFIG_EXTRA_TLV_BYTES]; //!< Not supported yet + +} wlan_cmd_wmm_queue_config_t; + +/** + * @brief IOCTL structure to configure a specific AC Queue's parameters + * + * IOCTL structure from the application layer relayed to firmware to + * get, set, or default the WMM AC queue parameters. + * + * - msduLifetimeExpiry is ignored if set to 0 on a set command + * + * @sa wlan_wmm_queue_config_ioctl + */ +typedef struct +{ + wlan_wmm_queue_config_action_e action; //!< Set, Get, or Default + wlan_wmm_ac_e accessCategory; //!< WMM_AC_BK(0) to WMM_AC_VO(3) + u16 msduLifetimeExpiry; //!< lifetime expiry in TUs + + u8 supportedRates[10]; //!< Not supported yet + +} __ATTRIB_PACK__ wlan_ioctl_wmm_queue_config_t; + +/** + * @brief Enumeration for the action field in the queue stats command + */ +typedef enum +{ + WMM_STATS_ACTION_START = 0, + WMM_STATS_ACTION_STOP = 1, + WMM_STATS_ACTION_GET_CLR = 2, + WMM_STATS_ACTION_SET_CFG = 3, /* Not currently used */ + WMM_STATS_ACTION_GET_CFG = 4, /* Not currently used */ + + WMM_STATS_ACTION_MAX +} __ATTRIB_PACK__ wlan_wmm_stats_action_e; + +/** Number of bins in the histogram for the HostCmd_DS_WMM_QUEUE_STATS */ +#define WMM_STATS_PKTS_HIST_BINS 7 + +/** + * @brief Command structure for the HostCmd_CMD_WMM_QUEUE_STATS firmware cmd + * + * Turn statistical collection on/off for a given AC or retrieve the + * accumulated stats for an AC and clear them in the firmware. + */ +typedef struct +{ + wlan_wmm_stats_action_e action; //!< Start, Stop, or Get + wlan_wmm_ac_e accessCategory; //!< WMM_AC_BK(0) to WMM_AC_VO(3) + + u16 pktCount; //!< Number of successful packets transmitted + u16 pktLoss; //!< Packets lost; not included in pktCount + u32 avgQueueDelay; //!< Average Queue delay in microseconds + u32 avgTxDelay; //!< Average Transmission delay in microseconds + u32 usedTime; //!< Calculated medium time - Not currently used + + /** @brief Queue Delay Histogram; number of packets per queue delay range + * + * [0] - 0ms <= delay < 5ms + * [1] - 5ms <= delay < 10ms + * [2] - 10ms <= delay < 20ms + * [3] - 20ms <= delay < 30ms + * [4] - 30ms <= delay < 40ms + * [5] - 40ms <= delay < 50ms + * [6] - 50ms <= delay < msduLifetime (TUs) + */ + u16 delayHistogram[WMM_STATS_PKTS_HIST_BINS]; + + u16 reserved_u16_1; + +} __ATTRIB_PACK__ HostCmd_DS_WMM_QUEUE_STATS; + +/** + * @brief IOCTL structure to start, stop, and get statistics for a WMM AC + * + * IOCTL structure from the application layer relayed to firmware to + * start or stop statistical collection for a given AC. Also used to + * retrieve and clear the collected stats on a given AC. + * + * @sa wlan_wmm_queue_stats_ioctl + */ +typedef struct +{ + wlan_wmm_stats_action_e action; //!< Start, Stop, or Get + wlan_wmm_ac_e accessCategory; //!< WMM_AC_BK(0) to WMM_AC_VO(3) + u16 pktCount; //!< Number of successful packets transmitted + u16 pktLoss; //!< Packets lost; not included in pktCount + u32 avgQueueDelay; //!< Average Queue delay in microseconds + u32 avgTxDelay; //!< Average Transmission delay in microseconds + u32 usedTime; //!< Calculated medium time + + /** @brief Queue Delay Histogram; number of packets per queue delay range + * + * [0] - 0ms <= delay < 5ms + * [1] - 5ms <= delay < 10ms + * [2] - 10ms <= delay < 20ms + * [3] - 20ms <= delay < 30ms + * [4] - 30ms <= delay < 40ms + * [5] - 40ms <= delay < 50ms + * [6] - 50ms <= delay < msduLifetime (TUs) + */ + u16 delayHistogram[WMM_STATS_PKTS_HIST_BINS]; +} __ATTRIB_PACK__ wlan_ioctl_wmm_queue_stats_t; + +/** + * @brief IOCTL sub structure for a specific WMM AC Status + */ +typedef struct +{ + u8 wmmAcm; + u8 flowRequired; + u8 flowCreated; + u8 disabled; +} __ATTRIB_PACK__ wlan_ioctl_wmm_queue_status_ac_t; + +/** + * @brief IOCTL structure to retrieve the WMM AC Queue status + * + * IOCTL structure from the application layer to retrieve: + * - ACM bit setting for the AC + * - Firmware status (flow required, flow created, flow disabled) + * + * @sa wlan_wmm_queue_status_ioctl + */ +typedef struct +{ + wlan_ioctl_wmm_queue_status_ac_t acStatus[MAX_AC_QUEUES]; +} __ATTRIB_PACK__ wlan_ioctl_wmm_queue_status_t; + +/** Firmware status for a specific AC */ +typedef struct +{ + u8 Disabled; + u8 FlowRequired; + u8 FlowCreated; +} WmmAcStatus_t; + +#endif /* _WLAN_TYPES_ */ diff --git a/drivers/net/wireless/marvell8686/wlan_version.h b/drivers/net/wireless/marvell8686/wlan_version.h new file mode 100644 index 0000000..bacedd5 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_version.h @@ -0,0 +1,18 @@ +/** @file wlan_version.h + * @brief This file contains wlan driver version number. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 10/04/05: Add Doxygen format comments + +********************************************************/ +#include "release_version.h" + +const char driver_version[] = + "sd8686-%s-" DRIVER_RELEASE_VERSION "-(" "FP" "4" ")" +#ifdef DEBUG_LEVEL2 + "-dbg" +#endif + " "; diff --git a/drivers/net/wireless/marvell8686/wlan_wext.c b/drivers/net/wireless/marvell8686/wlan_wext.c new file mode 100644 index 0000000..85ee593 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_wext.c @@ -0,0 +1,8045 @@ +/** @file wlan_wext.c + * @brief This file contains ioctl functions + * + * Copyright ?Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/10/05: Add Doxygen format comments + 12/23/05: Modify FindBSSIDInList to search entire table for + duplicate BSSIDs when earlier matches are not compatible + 12/26/05: Remove errant memcpy in wlanidle_off; overwriting stack space + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join functions. + Update statics/externs. Move forward decl. from wlan_decl.h + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support + 04/10/06: Add hostcmd generic API + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 05/04/06: Add IBSS coalescing related new iwpriv command + 08/29/06: Add ledgpio private command + 10/23/06: Validate setbcnavg/setdataavg command parameters and + return error if out of range +********************************************************/ + +#include "include.h" + +#include "wlan_version.h" + +#define GETLOG_BUFSIZE 512 + +#define MAX_SCAN_CELL_SIZE (IW_EV_ADDR_LEN + \ + MRVDRV_MAX_SSID_LENGTH + \ + IW_EV_UINT_LEN + IW_EV_FREQ_LEN + \ + IW_EV_QUAL_LEN + MRVDRV_MAX_SSID_LENGTH + \ + IW_EV_PARAM_LEN + 40) /* 40 for WPAIE */ + +typedef struct _ioctl_cmd +{ + int cmd; + int subcmd; + BOOLEAN fixsize; +} ioctl_cmd; + +static ioctl_cmd Commands_Allowed_In_DeepSleep[] = { + {.cmd = WLANDEEPSLEEP,.subcmd = 0,.fixsize = FALSE}, + {.cmd = WLAN_SETONEINT_GETWORDCHAR,.subcmd = WLANVERSION,.fixsize = + FALSE}, + {.cmd = WLAN_SETINT_GETINT,.subcmd = WLANSDIOCLOCK,.fixsize = TRUE}, + {.cmd = WLAN_SET_GET_2K,.subcmd = WLAN_GET_CFP_TABLE,.fixsize = FALSE}, +#ifdef DEBUG_LEVEL1 + {.cmd = WLAN_SET_GET_SIXTEEN_INT,.subcmd = WLAN_DRV_DBG,.fixsize = FALSE}, +#endif +}; + +static ioctl_cmd Commands_Allowed_In_HostSleep[] = { + {.cmd = WLAN_SETONEINT_GETWORDCHAR,.subcmd = WLANVERSION,.fixsize = + FALSE}, + {.cmd = WLANDEEPSLEEP,.subcmd = 1,.fixsize = FALSE}, + {.cmd = WLANDEEPSLEEP,.subcmd = 0,.fixsize = FALSE}, + {.cmd = WLAN_SETINT_GETINT,.subcmd = WLANSDIOCLOCK,.fixsize = TRUE}, + {.cmd = WLAN_SET_GET_2K,.subcmd = WLAN_GET_CFP_TABLE,.fixsize = FALSE}, +#ifdef DEBUG_LEVEL1 + {.cmd = WLAN_SET_GET_SIXTEEN_INT,.subcmd = WLAN_DRV_DBG,.fixsize = FALSE}, +#endif +}; + +/******************************************************** + Local Variables +********************************************************/ + +/******************************************************** + Global Variables +********************************************************/ +#ifdef DEBUG_LEVEL1 +#ifdef DEBUG_LEVEL2 +#define DEFAULT_DEBUG_MASK (0xffffffff & ~DBG_EVENT) +#else +#define DEFAULT_DEBUG_MASK (DBG_MSG | DBG_FATAL | DBG_ERROR) +#endif +u32 drvdbg = DEFAULT_DEBUG_MASK; +u32 ifdbg = 0; +#endif + +/******************************************************** + Local Functions +********************************************************/ +static int wlan_set_rate(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra); +static int wlan_get_rate(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra); + +static int wlan_get_essid(struct net_device *dev, + struct iw_request_info *info, struct iw_point *dwrq, + char *extra); + +static int wlan_set_freq(struct net_device *dev, struct iw_request_info *info, + struct iw_freq *fwrq, char *extra); +static int wlan_get_freq(struct net_device *dev, struct iw_request_info *info, + struct iw_freq *fwrq, char *extra); + +static int wlan_set_mode(struct net_device *dev, struct iw_request_info *info, + u32 * uwrq, char *extra); +static int wlan_get_mode(struct net_device *dev, struct iw_request_info *info, + u32 * uwrq, char *extra); + +static int wlan_set_encode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra); +static int wlan_get_encode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, u8 * extra); + +static int wlan_set_txpow(struct net_device *dev, + struct iw_request_info *info, struct iw_param *vwrq, + char *extra); +static int wlan_get_txpow(struct net_device *dev, + struct iw_request_info *info, struct iw_param *vwrq, + char *extra); + +static int wlan_set_coalescing_ioctl(wlan_private * priv, struct iwreq *wrq); + +extern CHANNEL_FREQ_POWER *wlan_get_region_cfp_table(u8 region, u8 band, + int *cfp_no); + +/** + * @brief This function checks if the commans is allowed + * in deepsleep/hostsleep mode or not. + * + * @param req A pointer to ifreq structure + * @param cmd the command ID + * @return TRUE or FALSE + */ +static BOOLEAN +Is_Command_Allowed_In_Sleep(struct ifreq *req, int cmd, + ioctl_cmd * allowed_cmds, int count) +{ + int subcmd = 0; + struct iwreq *wrq = (struct iwreq *) req; + int i; + + for (i = 0; i < count; i++) { + if (cmd == allowed_cmds[i].cmd) { + if (allowed_cmds[i].subcmd == 0) + return TRUE; + if (allowed_cmds[i].fixsize == TRUE) + subcmd = (int) req->ifr_data; + else + subcmd = wrq->u.data.flags; + if (allowed_cmds[i].subcmd == subcmd) + return TRUE; + } + } + return FALSE; +} + +/** + * @brief This function checks if the command is allowed. + * + * @param priv A pointer to wlan_private structure + * @return TRUE or FALSE + */ +BOOLEAN +Is_Command_Allowed(wlan_private * priv) +{ + BOOLEAN ret = TRUE; + + if (priv->adapter->bHostSleepConfigured) { + PRINTM(INFO, "IOCTLS called when WLAN access is blocked\n"); + ret = FALSE; + } + if (!priv->adapter->IsAutoDeepSleepEnabled) { + if ((priv->adapter->IsDeepSleep == TRUE)) { + PRINTM(INFO, "IOCTLS called when station is in DeepSleep\n"); + ret = FALSE; + } + } + + return ret; +} + +/** + * @brief Find a character in a string. + * + * @param s A pointer to string + * @param c Character to be located + * @param dlen the length of string + * @return A pointer to the first occurrence of c in string, or NULL if c is not found. + */ +static void * +wlan_memchr(void *s, int c, int n) +{ + const u8 *p = s; + + while (n-- != 0) { + if ((u8) c == *p++) { + return (void *) (p - 1); + } + } + return NULL; +} + +#if WIRELESS_EXT > 14 +/** + * @brief Convert mw value to dbm value + * + * @param mw the value of mw + * @return the value of dbm + */ +static int +mw_to_dbm(int mw) +{ + if (mw < 2) + return 0; + else if (mw < 3) + return 3; + else if (mw < 4) + return 5; + else if (mw < 6) + return 7; + else if (mw < 7) + return 8; + else if (mw < 8) + return 9; + else if (mw < 10) + return 10; + else if (mw < 13) + return 11; + else if (mw < 16) + return 12; + else if (mw < 20) + return 13; + else if (mw < 25) + return 14; + else if (mw < 32) + return 15; + else if (mw < 40) + return 16; + else if (mw < 50) + return 17; + else if (mw < 63) + return 18; + else if (mw < 79) + return 19; + else if (mw < 100) + return 20; + else + return 21; +} + +/** + * @brief This function sends customized event to application. + * + * @param priv A pointer to wlan_private structure + * @para str A pointer to event string + * @return n/a + */ +void +send_iwevcustom_event(wlan_private * priv, s8 * str) +{ + union iwreq_data iwrq; + u8 buf[50]; + + ENTER(); + + memset(&iwrq, 0, sizeof(union iwreq_data)); + memset(buf, 0, sizeof(buf)); + + snprintf(buf, sizeof(buf) - 1, "%s", str); + + iwrq.data.pointer = buf; + iwrq.data.length = strlen(buf) + 1 + IW_EV_LCP_LEN; + + /* Send Event to upper layer */ + wireless_send_event(priv->wlan_dev.netdev, IWEVCUSTOM, &iwrq, buf); + PRINTM(INFO, "Wireless event %s is sent to app\n", str); + + LEAVE(); + return; +} +#endif + +/** + * @brief Find the channel frequency power info with specific channel + * + * @param adapter A pointer to wlan_adapter structure + * @param band it can be BAND_A, BAND_G or BAND_B + * @param channel the channel for looking + * @return A pointer to CHANNEL_FREQ_POWER structure or NULL if not find. + */ +CHANNEL_FREQ_POWER * +find_cfp_by_band_and_channel(wlan_adapter * adapter, u8 band, u16 channel) +{ + CHANNEL_FREQ_POWER *cfp = NULL; + + ENTER(); + + if (adapter->State11D.Enable11D == ENABLE_11D) + cfp = + get_cfp_by_band_and_channel(band, channel, + adapter->universal_channel); + else + cfp = + get_cfp_by_band_and_channel(band, channel, + adapter->region_channel); + + return cfp; +} + +/** + * @brief Find the channel frequency power info with specific frequency + * + * @param adapter A pointer to wlan_adapter structure + * @param band it can be BAND_A, BAND_G or BAND_B + * @param freq the frequency for looking + * @return Pointer to CHANNEL_FREQ_POWER structure; NULL if not found + */ +static CHANNEL_FREQ_POWER * +find_cfp_by_band_and_freq(wlan_adapter * adapter, u8 band, u32 freq) +{ + CHANNEL_FREQ_POWER *cfp = NULL; + REGION_CHANNEL *rc; + int count = sizeof(adapter->region_channel) / + sizeof(adapter->region_channel[0]); + int i, j; + + for (j = 0; !cfp && (j < count); j++) { + rc = &adapter->region_channel[j]; + + if (adapter->State11D.Enable11D == ENABLE_11D) { + rc = &adapter->universal_channel[j]; + } + + if (!rc->Valid || !rc->CFP) + continue; + if (rc->Band != band) + continue; + for (i = 0; i < rc->NrCFP; i++) { + if (rc->CFP[i].Freq == freq) { + cfp = &rc->CFP[i]; + break; + } + } + } + + if (!cfp && freq) + PRINTM(INFO, "find_cfp_by_band_and_freql(): cannot find cfp by " + "band %d & freq %d\n", band, freq); + + return cfp; +} + +#ifdef MFG_CMD_SUPPORT +/** + * @brief Manufacturing command ioctl function + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS--success, otherwise--fail + */ +static int +wlan_mfg_command(wlan_private * priv, struct iwreq *wrq) +{ + HostCmd_DS_GEN *pCmdPtr; + u8 *mfg_cmd; + u16 mfg_cmd_len; + int ret; + + ENTER(); + + /* allocate MFG command buffer */ + if (!(mfg_cmd = kmalloc(MRVDRV_SIZE_OF_CMD_BUFFER, GFP_KERNEL))) { + PRINTM(INFO, "allocate MFG command buffer failed!\n"); + return -ENOMEM; + } + + /* get MFG command header */ + if (copy_from_user(mfg_cmd, wrq->u.data.pointer, sizeof(HostCmd_DS_GEN))) { + PRINTM(INFO, "copy from user failed: MFG command header\n"); + ret = -EFAULT; + goto mfg_exit; + } + + /* get the command size */ + pCmdPtr = (HostCmd_DS_GEN *) mfg_cmd; + mfg_cmd_len = pCmdPtr->Size; + PRINTM(INFO, "MFG command len = %d\n", mfg_cmd_len); + + if (mfg_cmd_len > MRVDRV_SIZE_OF_CMD_BUFFER) { + ret = -EINVAL; + goto mfg_exit; + } + + /* get the whole command from user */ + if (copy_from_user(mfg_cmd, wrq->u.data.pointer, mfg_cmd_len)) { + PRINTM(INFO, "copy from user failed: MFG command\n"); + ret = -EFAULT; + goto mfg_exit; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_MFG_COMMAND, + 0, HostCmd_OPTION_WAITFORRSP, 0, mfg_cmd); + + /* copy the response back to user */ + if (!ret && pCmdPtr->Size) { + mfg_cmd_len = MIN(pCmdPtr->Size, mfg_cmd_len); + if (copy_to_user(wrq->u.data.pointer, mfg_cmd, mfg_cmd_len)) { + PRINTM(INFO, "copy to user failed: MFG command\n"); + ret = -EFAULT; + } + wrq->u.data.length = mfg_cmd_len; + } + + mfg_exit: + kfree(mfg_cmd); + LEAVE(); + return ret; +} +#endif + +/** + * @brief Check if Rate Auto + * + * @param priv A pointer to wlan_private structure + * @return TRUE/FALSE + */ +BOOLEAN +Is_Rate_Auto(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int i; + int ratenum = 0; + int bitsize = 0; + bitsize = sizeof(Adapter->RateBitmap) * 8; + for (i = 0; i < bitsize; i++) { + if (Adapter->RateBitmap & (1 << i)) + ratenum++; + if (ratenum > 1) + break; + } + if (ratenum > 1) + return TRUE; + else + return FALSE; +} + +/** + * @brief Covert Rate Bitmap to Rate index + * + * @param priv A pointer to wlan_private structure + * @return TRUE/FALSE + */ +int +GetRateIndex(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int bitsize = sizeof(Adapter->RateBitmap) * 8; + int i; + for (i = 0; i < bitsize; i++) { + if (Adapter->RateBitmap & (1 << i)) + return i; + } + return 0; +} + +/** + * @brief Update Current Channel + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +static int +UpdateCurrentChannel(wlan_private * priv) +{ + int ret; + + /* + ** the channel in f/w could be out of sync, get the current channel + */ + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RF_CHANNEL, + HostCmd_OPT_802_11_RF_CHANNEL_GET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + + PRINTM(INFO, "Current Channel = %d\n", + priv->adapter->CurBssParams.BSSDescriptor.Channel); + + return ret; +} + +/** + * @brief Set Current Channel + * + * @param priv A pointer to wlan_private structure + * @param channel The channel to be set. + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +static int +SetCurrentChannel(wlan_private * priv, int channel) +{ + PRINTM(INFO, "Set Channel = %d\n", channel); + + /* + ** Current channel is not set to AdhocChannel requested, set channel + */ + return (PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RF_CHANNEL, + HostCmd_OPT_802_11_RF_CHANNEL_SET, + HostCmd_OPTION_WAITFORRSP, 0, &channel)); +} + +/** + * @brief Change Adhoc Channel + * + * @param priv A pointer to wlan_private structure + * @param channel The channel to be set. + * @return WLAN_STATUS_SUCCESS--success, WLAN_STATUS_FAILURE--fail + */ +static int +ChangeAdhocChannel(wlan_private * priv, int channel) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + + Adapter->AdhocChannel = channel; + + UpdateCurrentChannel(priv); + + if (Adapter->CurBssParams.BSSDescriptor.Channel == Adapter->AdhocChannel) { + /* AdhocChannel is set to the current Channel already */ + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + PRINTM(INFO, "Updating Channel from %d to %d\n", + Adapter->CurBssParams.BSSDescriptor.Channel, + Adapter->AdhocChannel); + + SetCurrentChannel(priv, Adapter->AdhocChannel); + + UpdateCurrentChannel(priv); + + if (Adapter->CurBssParams.BSSDescriptor.Channel != Adapter->AdhocChannel) { + PRINTM(INFO, "Failed to updated Channel to %d, channel = %d\n", + Adapter->AdhocChannel, + Adapter->CurBssParams.BSSDescriptor.Channel); + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + int i; + WLAN_802_11_SSID curAdhocSsid; + + PRINTM(INFO, "Channel Changed while in an IBSS\n"); + + /* Copy the current ssid */ + memcpy(&curAdhocSsid, + &Adapter->CurBssParams.BSSDescriptor.Ssid, + sizeof(WLAN_802_11_SSID)); + + /* Exit Adhoc mode */ + PRINTM(INFO, "In ChangeAdhocChannel(): Sending Adhoc Stop\n"); + ret = StopAdhocNetwork(priv); + + if (ret) { + LEAVE(); + return ret; + } + + /* Scan for the network */ + SendSpecificSSIDScan(priv, &curAdhocSsid); + + // find out the BSSID that matches the current SSID + i = FindSSIDInList(Adapter, &curAdhocSsid, NULL, Wlan802_11IBSS); + + if (i >= 0) { + PRINTM(INFO, "SSID found at %d in List," "so join\n", i); + JoinAdhocNetwork(priv, &Adapter->ScanTable[i]); + } else { + // else send START command + PRINTM(INFO, "SSID not found in list, " + "so creating adhoc with ssid = %s\n", curAdhocSsid.Ssid); + StartAdhocNetwork(priv, &curAdhocSsid); + } // end of else (START command) + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set/Get WPA IE + * + * @param priv A pointer to wlan_private structure + * @param ie_data_ptr A pointer to IE + * @param ie_len Length of the IE + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_wpa_ie_helper(wlan_private * priv, u8 * ie_data_ptr, u16 ie_len) +{ + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (ie_len) { + if (ie_len > sizeof(Adapter->Wpa_ie)) { + PRINTM(INFO, "failed to copy WPA IE, too big \n"); + return -EFAULT; + } + if (copy_from_user(Adapter->Wpa_ie, ie_data_ptr, ie_len)) { + PRINTM(INFO, "failed to copy WPA IE \n"); + return -EFAULT; + } + Adapter->Wpa_ie_len = ie_len; + PRINTM(INFO, "Set Wpa_ie_len=%d IE=%#x\n", + Adapter->Wpa_ie_len, Adapter->Wpa_ie[0]); + HEXDUMP("Wpa_ie", Adapter->Wpa_ie, Adapter->Wpa_ie_len); + + if (Adapter->Wpa_ie[0] == WPA_IE) { + Adapter->SecInfo.WPAEnabled = TRUE; + } else if (Adapter->Wpa_ie[0] == RSN_IE) { + Adapter->SecInfo.WPA2Enabled = TRUE; + } else { + Adapter->SecInfo.WPAEnabled = FALSE; + Adapter->SecInfo.WPA2Enabled = FALSE; + } + } else { + memset(Adapter->Wpa_ie, 0, sizeof(Adapter->Wpa_ie)); + Adapter->Wpa_ie_len = ie_len; + PRINTM(INFO, "Reset Wpa_ie_len=%d IE=%#x\n", + Adapter->Wpa_ie_len, Adapter->Wpa_ie[0]); + Adapter->SecInfo.WPAEnabled = FALSE; + Adapter->SecInfo.WPA2Enabled = FALSE; + } + + LEAVE(); + return ret; +} + +/** + * @brief Set/Get WPA IE + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_wpa_ie_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *) req; + + return wlan_set_wpa_ie_helper(priv, + wrq->u.data.pointer, wrq->u.data.length); +} + +/** + * @brief Set WPA key + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_encode_wpa(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + WLAN_802_11_KEY *pKey; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + pKey = (WLAN_802_11_KEY *) extra; + + HEXDUMP("Key buffer: ", extra, dwrq->length); + + // current driver only supports key length of up to 32 bytes + if (pKey->KeyLength > MRVL_MAX_WPA_KEY_LENGTH) { + PRINTM(INFO, " Error in key length \n"); + return WLAN_STATUS_FAILURE; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, + KEY_INFO_ENABLED, pKey); + + if (ret) { + LEAVE(); + return ret; + } + + LEAVE(); + return ret; +} + +/* + * iwconfig ethX key on: WEPEnabled; + * iwconfig ethX key off: WEPDisabled; + * iwconfig ethX key [x]: CurrentWepKeyIndex = x; WEPEnabled; + * iwconfig ethX key [x] kstr: WepKey[x] = kstr; + * iwconfig ethX key kstr: WepKey[CurrentWepKeyIndex] = kstr; + * + * all: Send command SET_WEP; + SetMacPacketFilter; + */ + +/** + * @brief Set WEP key + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_encode_nonwpa(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + MRVL_WEP_KEY *pWep; + int index, PrevAuthMode; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (Adapter->CurrentWepKeyIndex >= MRVL_NUM_WEP_KEY) + Adapter->CurrentWepKeyIndex = 0; + pWep = &Adapter->WepKey[Adapter->CurrentWepKeyIndex]; + PrevAuthMode = Adapter->SecInfo.AuthenticationMode; + + index = (dwrq->flags & IW_ENCODE_INDEX) - 1; + + if (index >= 4) { + PRINTM(INFO, "Key index #%d out of range.\n", index + 1); + return -EINVAL; + } + + PRINTM(INFO, "Flags=0x%x, Length=%d Index=%d CurrentWepKeyIndex=%d\n", + dwrq->flags, dwrq->length, index, Adapter->CurrentWepKeyIndex); + + if (dwrq->length > 0) { + /* iwconfig ethX key [n] xxxxxxxxxxx + * Key has been provided by the user + */ + + /* + * Check the size of the key + */ + + if (dwrq->length > MAX_WEP_KEY_SIZE) { + return -EINVAL; + } + + /* + * Check the index (none -> use current) + */ + + if (index < 0 || index > 3) //invalid index or no index + index = Adapter->CurrentWepKeyIndex; + else //index is given & valid + pWep = &Adapter->WepKey[index]; + + /* + * Check if the key is not marked as invalid + */ + if (!(dwrq->flags & IW_ENCODE_NOKEY)) { + /* Cleanup */ + memset(pWep, 0, sizeof(MRVL_WEP_KEY)); + + /* Copy the key in the driver */ + memcpy(pWep->KeyMaterial, extra, dwrq->length); + + /* Set the length */ + if (dwrq->length > MIN_WEP_KEY_SIZE) { + pWep->KeyLength = MAX_WEP_KEY_SIZE; + } else { + if (dwrq->length > 0) { + pWep->KeyLength = MIN_WEP_KEY_SIZE; + } else { + /* Disable the key */ + pWep->KeyLength = 0; + } + } + pWep->KeyIndex = index; + + if (Adapter->SecInfo.WEPStatus != Wlan802_11WEPEnabled) { + /* + * The status is set as Key Absent + * so as to make sure we display the + * keys when iwlist ethX key is used + */ + Adapter->SecInfo.WEPStatus = Wlan802_11WEPKeyAbsent; + } + + PRINTM(INFO, "KeyIndex=%u KeyLength=%u\n", + pWep->KeyIndex, pWep->KeyLength); + HEXDUMP("WepKey", (u8 *) pWep->KeyMaterial, pWep->KeyLength); + } + } else { + /* + * No key provided so it is either enable key, + * on or off */ + if (dwrq->flags & IW_ENCODE_DISABLED) { + PRINTM(INFO, "*** iwconfig ethX key off ***\n"); + + Adapter->SecInfo.WEPStatus = Wlan802_11WEPDisabled; + if (Adapter->SecInfo.AuthenticationMode == + Wlan802_11AuthModeShared) + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeOpen; + } else { + /* iwconfig ethX key [n] + * iwconfig ethX key on + * Do we want to just set the transmit key index ? + */ + + if (index < 0 || index > 3) { + PRINTM(INFO, "*** iwconfig ethX key on ***\n"); + index = Adapter->CurrentWepKeyIndex; + } else { + PRINTM(INFO, "*** iwconfig ethX key [x=%d] ***\n", index); + Adapter->CurrentWepKeyIndex = index; + } + + /* Copy the required key as the current key */ + pWep = &Adapter->WepKey[index]; + + if (!pWep->KeyLength) { + PRINTM(INFO, "Key not set,so cannot enable it\n"); + return -EPERM; + } + + Adapter->SecInfo.WEPStatus = Wlan802_11WEPEnabled; + + HEXDUMP("KeyMaterial", (u8 *) pWep->KeyMaterial, pWep->KeyLength); + } + } + + if (pWep->KeyLength) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SET_WEP, + 0, HostCmd_OPTION_WAITFORRSP, + OID_802_11_ADD_WEP, NULL); + + if (ret) { + LEAVE(); + return ret; + } + } + + if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled) { + Adapter->CurrentPacketFilter |= HostCmd_ACT_MAC_WEP_ENABLE; + } else { + Adapter->CurrentPacketFilter &= ~HostCmd_ACT_MAC_WEP_ENABLE; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_MAC_CONTROL, + 0, HostCmd_OPTION_WAITFORRSP, + 0, &Adapter->CurrentPacketFilter); + + if (dwrq->flags & IW_ENCODE_RESTRICTED) { + /* iwconfig ethX restricted key [1] */ + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeShared; + PRINTM(INFO, "Auth mode restricted!\n"); + } else if (dwrq->flags & IW_ENCODE_OPEN) { + /* iwconfig ethX key [2] open */ + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeOpen; + PRINTM(INFO, "Auth mode open!\n"); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set RX Antenna + * + * @param priv A pointer to wlan_private structure + * @param Mode RF antenna mode + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +SetRxAntenna(wlan_private * priv, int Mode) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + + if (Mode != RF_ANTENNA_1 && Mode != RF_ANTENNA_2 + && Mode != RF_ANTENNA_AUTO) { + return -EINVAL; + } + + Adapter->RxAntennaMode = Mode; + + PRINTM(INFO, "SET RX Antenna Mode to 0x%04x\n", Adapter->RxAntennaMode); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RF_ANTENNA, + HostCmd_ACT_SET_RX, HostCmd_OPTION_WAITFORRSP, + 0, &Adapter->RxAntennaMode); + return ret; +} + +/** + * @brief Set TX Antenna + * + * @param priv A pointer to wlan_private structure + * @param Mode RF antenna mode + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +SetTxAntenna(wlan_private * priv, int Mode) +{ + int ret = 0; + wlan_adapter *Adapter = priv->adapter; + + if ((Mode != RF_ANTENNA_1) && (Mode != RF_ANTENNA_2) + && (Mode != RF_ANTENNA_AUTO)) { + return -EINVAL; + } + + Adapter->TxAntennaMode = Mode; + + PRINTM(INFO, "SET TX Antenna Mode to 0x%04x\n", Adapter->TxAntennaMode); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RF_ANTENNA, + HostCmd_ACT_SET_TX, HostCmd_OPTION_WAITFORRSP, + 0, &Adapter->TxAntennaMode); + + return ret; +} + +/** + * @brief Get RX Antenna + * + * @param priv A pointer to wlan_private structure + * @param buf A pointer to recieve antenna mode + * @return length of buf + */ +static int +GetRxAntenna(wlan_private * priv, char *buf) +{ + int ret = 0; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + // clear it, so we will know if the value + // returned below is correct or not. + Adapter->RxAntennaMode = 0; + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RF_ANTENNA, + HostCmd_ACT_GET_RX, HostCmd_OPTION_WAITFORRSP, + 0, NULL); + + if (ret) { + LEAVE(); + return ret; + } + + PRINTM(INFO, "Get Rx Antenna Mode:0x%04x\n", Adapter->RxAntennaMode); + + LEAVE(); + + return sprintf(buf, "0x%04x", Adapter->RxAntennaMode) + 1; +} + +/** + * @brief Get TX Antenna + * + * @param priv A pointer to wlan_private structure + * @param buf A pointer to recieve antenna mode + * @return length of buf + */ +static int +GetTxAntenna(wlan_private * priv, char *buf) +{ + int ret = 0; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + // clear it, so we will know if the value + // returned below is correct or not. + Adapter->TxAntennaMode = 0; + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RF_ANTENNA, + HostCmd_ACT_GET_TX, HostCmd_OPTION_WAITFORRSP, + 0, NULL); + + if (ret) { + LEAVE(); + return ret; + } + + PRINTM(INFO, "Get Tx Antenna Mode:0x%04x\n", Adapter->TxAntennaMode); + + LEAVE(); + + return sprintf(buf, "0x%04x", Adapter->TxAntennaMode) + 1; +} + +/** + * @brief Set Radio On/OFF + * + * @param priv A pointer to wlan_private structure + * @option Radio Option + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_radio_ioctl(wlan_private * priv, u8 option) +{ + int ret = 0; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (Adapter->RadioOn != option) { + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + PRINTM(MSG, "Cannot turn radio off in connected state.\n"); + LEAVE(); + return -EINVAL; + } + + PRINTM(INFO, "Switching %s the Radio\n", option ? "On" : "Off"); + Adapter->RadioOn = option; + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RADIO_CONTROL, + HostCmd_ACT_GEN_SET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + } + + LEAVE(); + return ret; +} + +#ifdef REASSOCIATION +/** + * @brief Set Auto Reassociation On + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +reassociation_on(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + Adapter->Reassoc_on = TRUE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set Auto Reassociation Off + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +reassociation_off(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (Adapter->ReassocTimerIsSet == TRUE) { + CancelTimer(&Adapter->MrvDrvTimer); + Adapter->ReassocTimerIsSet = FALSE; + } + + Adapter->Reassoc_on = FALSE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} +#endif /* REASSOCIATION */ + +/** + * @brief Set Region + * + * @param priv A pointer to wlan_private structure + * @param region_code region code + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_region(wlan_private * priv, u16 region_code) +{ + int i; + + ENTER(); + + for (i = 0; i < MRVDRV_MAX_REGION_CODE; i++) { + // use the region code to search for the index + if (region_code == RegionCodeToIndex[i]) { + priv->adapter->RegionCode = region_code; + break; + } + } + + // if it's unidentified region code + if (i >= MRVDRV_MAX_REGION_CODE) { + PRINTM(INFO, "Region Code not identified\n"); + LEAVE(); + return WLAN_STATUS_FAILURE; + } + + if (wlan_set_regiontable(priv, priv->adapter->RegionCode, 0)) { + LEAVE(); + return -EINVAL; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Copy Rates + * + * @param dest A pointer to Dest Buf + * @param src A pointer to Src Buf + * @param len The len of Src Buf + * @return Number of Rates copyed + */ +static inline int +CopyRates(u8 * dest, int pos, u8 * src, int len) +{ + int i; + + for (i = 0; i < len && src[i]; i++, pos++) { + if (pos >= sizeof(WLAN_802_11_RATES)) + break; + dest[pos] = src[i]; + } + + return pos; +} + +/** + * @brief Get active data rates + * + * @param Adapter A pointer to wlan_adapter structure + * @param rate The buf to return the active rates + * @return The number of Rates + */ +static int +get_active_data_rates(wlan_adapter * Adapter, WLAN_802_11_RATES rates) +{ + int k = 0; + + ENTER(); + + if (Adapter->MediaConnectStatus != WlanMediaStateConnected) { + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + //Infra. mode + PRINTM(INFO, "Infra\n"); + k = CopyRates(rates, k, SupportedRates, sizeof(SupportedRates)); + } else { + //ad-hoc mode + PRINTM(INFO, "Adhoc G\n"); + k = CopyRates(rates, k, AdhocRates_G, sizeof(AdhocRates_G)); + } + } else { + k = CopyRates(rates, 0, Adapter->CurBssParams.DataRates, + Adapter->CurBssParams.NumOfRates); + } + + LEAVE(); + + return k; +} + +/** + * @brief Get/Set Per packet TX Control flags + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_txcontrol(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data[3]; + int ret; + + ENTER(); + + ret = WLAN_STATUS_SUCCESS; + + switch (wrq->u.data.length) { + case 0: + /* + * Get the Global setting for TxCtrl + */ + if (copy_to_user(wrq->u.data.pointer, + &Adapter->PktTxCtrl, sizeof(u32))) { + PRINTM(INFO, "copy_to_user failed!\n"); + ret = -EFAULT; + } else { + wrq->u.data.length = 1; + } + break; + + case 1: + /* + * Set the Global setting for TxCtrl + */ + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + ret = -EFAULT; + } else { + Adapter->PktTxCtrl = data[0]; + PRINTM(INFO, "PktTxCtrl set: 0x%08x\n", Adapter->PktTxCtrl); + } + break; + + case 2: + /* + * Get the per User Priority setting for TxCtrl for the given UP + */ + if (copy_from_user(data, wrq->u.data.pointer, sizeof(int) * 2)) { + PRINTM(INFO, "Copy from user failed\n"); + ret = -EFAULT; + + } else if (data[1] >= NELEMENTS(Adapter->wmm.userPriPktTxCtrl)) { + /* Range check the UP input from user space */ + PRINTM(INFO, "User priority out of range\n"); + ret = -EINVAL; + + } else if (Adapter->wmm.userPriPktTxCtrl[data[1]]) { + data[2] = Adapter->wmm.userPriPktTxCtrl[data[1]]; + + /* User priority setting is valid, return it */ + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 3)) { + PRINTM(INFO, "copy_to_user failed!\n"); + ret = -EFAULT; + } else { + wrq->u.data.length = 3; + } + + } else { + /* Return the global setting since the UP set is zero */ + data[2] = Adapter->PktTxCtrl; + + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 3)) { + PRINTM(INFO, "copy_to_user failed!\n"); + ret = -EFAULT; + } else { + wrq->u.data.length = 3; + } + } + break; + + case 3: + /* + * Set the per User Priority setting for TxCtrl for the given UP + */ + + if (copy_from_user(data, wrq->u.data.pointer, sizeof(int) * 3)) { + PRINTM(INFO, "Copy from user failed\n"); + ret = -EFAULT; + } else if (data[1] >= NELEMENTS(Adapter->wmm.userPriPktTxCtrl)) { + PRINTM(INFO, "User priority out of range\n"); + ret = -EINVAL; + } else { + Adapter->wmm.userPriPktTxCtrl[data[1]] = data[2]; + + if (Adapter->wmm.userPriPktTxCtrl[data[1]] == 0) { + /* Return the global setting since the UP set is zero */ + data[2] = Adapter->PktTxCtrl; + } + + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 3)) { + PRINTM(INFO, "copy_to_user failed!\n"); + ret = -EFAULT; + } else { + wrq->u.data.length = 3; + } + } + break; + + default: + ret = -EINVAL; + break; + } + + LEAVE(); + return ret; +} + +/** + * @brief Enable/Disable atim uapsd null package generation + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_null_pkg_gen(wlan_private * priv, struct iwreq *wrq) +{ + int data; + wlan_adapter *Adapter = priv->adapter; + int *val; + + ENTER(); + + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + PRINTM(INFO, "Enable UAPSD NULL PKG: %s\n", + (data == CMD_ENABLED) ? "Enable" : "Disable"); + switch (data) { + case CMD_ENABLED: + Adapter->gen_null_pkg = TRUE; + break; + case CMD_DISABLED: + Adapter->gen_null_pkg = FALSE; + break; + default: + break; + } + + data = (Adapter->gen_null_pkg == TRUE) ? CMD_ENABLED : CMD_DISABLED; + val = (int *) wrq->u.name; + *val = data; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set NULL Package generation interval + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_null_pkt_interval(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data; + ENTER(); + + if ((int) wrq->u.data.length == 0) { + data = Adapter->NullPktInterval; + + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(MSG, "copy_to_user failed!\n"); + return -EFAULT; + } + } else { + if ((int) wrq->u.data.length > 1) { + PRINTM(MSG, "ioctl too many args!\n"); + return -EFAULT; + } + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + Adapter->NullPktInterval = data; + } + + wrq->u.data.length = 1; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set Adhoc awake period + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_adhoc_awake_period(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data; + ENTER(); + + if ((int) wrq->u.data.length == 0) { + data = Adapter->AdhocAwakePeriod; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(MSG, "copy_to_user failed!\n"); + return -EFAULT; + } + } else { + if ((int) wrq->u.data.length > 1) { + PRINTM(MSG, "ioctl too many args!\n"); + return -EFAULT; + } + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } +#define AWAKE_PERIOD_MIN 1 +#define AWAKE_PERIOD_MAX 31 +#define DISABLE_AWAKE_PERIOD 0xff + if ((((data & 0xff) >= AWAKE_PERIOD_MIN) && + ((data & 0xff) <= AWAKE_PERIOD_MAX)) || + ((data & 0xff) == DISABLE_AWAKE_PERIOD)) + Adapter->AdhocAwakePeriod = (u16) data; + else { + PRINTM(INFO, + "Invalid parameter, AdhocAwakePeriod not changed.\n"); + return -EINVAL; + + } + } + wrq->u.data.length = 1; + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set bcn missing timeout + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_bcn_miss_timeout(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data; + ENTER(); + + if ((int) wrq->u.data.length == 0) { + data = Adapter->BCNMissTimeOut; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(MSG, "copy_to_user failed!\n"); + return -EFAULT; + } + } else { + if ((int) wrq->u.data.length > 1) { + PRINTM(MSG, "ioctl too many args!\n"); + return -EFAULT; + } + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + if (((data >= 0) && (data <= 50)) || (data == 0xffff)) + Adapter->BCNMissTimeOut = (u16) data; + else { + PRINTM(INFO, + "Invalid parameter, BCN Missing timeout not changed.\n"); + return -EINVAL; + + } + } + + wrq->u.data.length = 1; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set adhoc g proctection + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_adhoc_g_protection(wlan_private * priv, struct iwreq *wrq) +{ + int data; + int *val; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); +#define ADHOC_G_PROTECTION_ON 1 +#define ADHOC_G_PROTECTION_OFF 0 + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + + switch (data) { + case CMD_DISABLED: + Adapter->CurrentPacketFilter &= + ~HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON; + break; + case CMD_ENABLED: + Adapter->CurrentPacketFilter |= HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON; + break; + + case CMD_GET: + if (Adapter-> + CurrentPacketFilter & HostCmd_ACT_MAC_ADHOC_G_PROTECTION_ON) + data = ADHOC_G_PROTECTION_ON; + else + data = ADHOC_G_PROTECTION_OFF; + break; + + default: + return -EINVAL; + } + + val = (int *) wrq->u.name; + *val = data; + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +#define USE_RTS_CTS 1 +#define USE_CTS_TO_SELF 0 +/** + * @brief GetSet RTS/CTS or CTS to self. + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_rts_cts_ctrl(wlan_private * priv, struct iwreq *wrq) +{ + int data; + wlan_adapter *Adapter = priv->adapter; + int ret = 0; + ENTER(); + + if ((int) wrq->u.data.length == 0) { + if (Adapter->CurrentPacketFilter & HostCmd_ACT_MAC_RTS_CTS_ENABLE) + data = USE_RTS_CTS; + else + data = USE_CTS_TO_SELF; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(MSG, "copy_to_user failed!\n"); + return -EFAULT; + } + } else { + if ((int) wrq->u.data.length > 1) { + PRINTM(MSG, "ioctl too many args!\n"); + return -EFAULT; + } + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + if (data == USE_RTS_CTS) { + Adapter->CurrentPacketFilter |= HostCmd_ACT_MAC_RTS_CTS_ENABLE; + } else { + Adapter->CurrentPacketFilter &= ~HostCmd_ACT_MAC_RTS_CTS_ENABLE; + } + PRINTM(INFO, "Adapter->CurrentPacketFilter=0x%x\n", + Adapter->CurrentPacketFilter); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_MAC_CONTROL, + 0, HostCmd_OPTION_WAITFORRSP, + 0, &Adapter->CurrentPacketFilter); + } + wrq->u.data.length = 1; + LEAVE(); + return ret; +} + +/** + * @brief Get/Set sdio mode + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_sdio_mode(wlan_private * priv, struct iwreq *wrq) +{ + int data; + int bus_width; + wlan_adapter *Adapter = priv->adapter; + ENTER(); + + if ((int) wrq->u.data.length == 0) { + data = 4; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(MSG, "copy_to_user failed!\n"); + return -EFAULT; + } + } else { + if ((int) wrq->u.data.length > 1) { + PRINTM(MSG, "ioctl too many args!\n"); + return -EFAULT; + } + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + if ((data != 1) && (data != 4)) + return -EFAULT; + bus_width = 4; + if (bus_width != data) + Adapter->sdiomode = (u8) data; + } + wrq->u.data.length = 1; + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set LDO config + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to wrq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_ldo_config(wlan_private * priv, struct iwreq *wrq) +{ + HostCmd_DS_802_11_LDO_CONFIG ldocfg; + int data = 0; + u16 action; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (wrq->u.data.length == 0) { + action = HostCmd_ACT_GEN_GET; + } else if (wrq->u.data.length > 1) { + PRINTM(MSG, "ioctl too many args!\n"); + ret = -EFAULT; + goto ldoexit; + } else { + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + ret = -EFAULT; + goto ldoexit; + } + if (data != LDO_INTERNAL && data != LDO_EXTERNAL) { + PRINTM(MSG, "Invalid parameter, LDO config not changed.\n"); + ret = -EFAULT; + goto ldoexit; + } + action = HostCmd_ACT_GEN_SET; + } + ldocfg.Action = action; + ldocfg.PMSource = (u16) data; + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_LDO_CONFIG, + action, HostCmd_OPTION_WAITFORRSP, + 0, (void *) &ldocfg); + + if (!ret && action == HostCmd_ACT_GEN_GET) { + data = (int) ldocfg.PMSource; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + ret = -EFAULT; + goto ldoexit; + } + wrq->u.data.length = 1; + } + + ldoexit: + LEAVE(); + return ret; +} + +#ifdef DEBUG_LEVEL1 +/** + * @brief Get/Set the bit mask of driver debug message control + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to wrq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_drv_dbg(wlan_private * priv, struct iwreq *wrq) +{ + int data[4]; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (wrq->u.data.length == 0) { + data[0] = drvdbg; + data[1] = ifdbg; + /* Return the current driver debug bit masks */ + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 2)) { + PRINTM(INFO, "Copy to user failed\n"); + ret = -EFAULT; + goto drvdbgexit; + } + wrq->u.data.length = 2; + } else if (wrq->u.data.length < 3) { + /* Get the driver debug bit masks */ + if (copy_from_user + (data, wrq->u.data.pointer, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + ret = -EFAULT; + goto drvdbgexit; + } + drvdbg = data[0]; + if (wrq->u.data.length == 2) + ifdbg = data[1]; + } else { + PRINTM(INFO, "Invalid parameter number\n"); + goto drvdbgexit; + } + + printk(KERN_ALERT "drvdbg = 0x%x\n", drvdbg); +#ifdef DEBUG_LEVEL2 + printk(KERN_ALERT "INFO (%08x) %s\n", DBG_INFO, + (drvdbg & DBG_INFO) ? "X" : ""); + printk(KERN_ALERT "WARN (%08x) %s\n", DBG_WARN, + (drvdbg & DBG_WARN) ? "X" : ""); + printk(KERN_ALERT "ENTRY (%08x) %s\n", DBG_ENTRY, + (drvdbg & DBG_ENTRY) ? "X" : ""); +#endif + printk(KERN_ALERT "FW_D (%08x) %s\n", DBG_FW_D, + (drvdbg & DBG_FW_D) ? "X" : ""); + printk(KERN_ALERT "CMD_D (%08x) %s\n", DBG_CMD_D, + (drvdbg & DBG_CMD_D) ? "X" : ""); + printk(KERN_ALERT "DAT_D (%08x) %s\n", DBG_DAT_D, + (drvdbg & DBG_DAT_D) ? "X" : ""); + + printk(KERN_ALERT "INTR (%08x) %s\n", DBG_INTR, + (drvdbg & DBG_INTR) ? "X" : ""); + printk(KERN_ALERT "EVENT (%08x) %s\n", DBG_EVENT, + (drvdbg & DBG_EVENT) ? "X" : ""); + printk(KERN_ALERT "CMND (%08x) %s\n", DBG_CMND, + (drvdbg & DBG_CMND) ? "X" : ""); + printk(KERN_ALERT "DATA (%08x) %s\n", DBG_DATA, + (drvdbg & DBG_DATA) ? "X" : ""); + printk(KERN_ALERT "ERROR (%08x) %s\n", DBG_ERROR, + (drvdbg & DBG_ERROR) ? "X" : ""); + printk(KERN_ALERT "FATAL (%08x) %s\n", DBG_FATAL, + (drvdbg & DBG_FATAL) ? "X" : ""); + printk(KERN_ALERT "MSG (%08x) %s\n", DBG_MSG, + (drvdbg & DBG_MSG) ? "X" : ""); + printk(KERN_ALERT "ifdbg = 0x%x\n", ifdbg); + printk(KERN_ALERT "IF_D (%08x) %s\n", DBG_IF_D, + (ifdbg & DBG_IF_D) ? "X" : ""); + + drvdbgexit: + LEAVE(); + return ret; +} +#endif + +/** + * @brief Commit handler: called after a bunch of SET operations + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_config_commit(struct net_device *dev, + struct iw_request_info *info, char *cwrq, char *extra) +{ + ENTER(); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get protocol name + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_name(struct net_device *dev, struct iw_request_info *info, + char *cwrq, char *extra) +{ + const char *cp; + char comm[6] = { "COMM-" }; + char mrvl[6] = { "MRVL-" }; + int cnt; + + ENTER(); + + strcpy(cwrq, mrvl); + + cp = strstr(driver_version, comm); + if (cp == driver_version) //skip leading "COMM-" + cp = driver_version + strlen(comm); + else + cp = driver_version; + + cnt = strlen(mrvl); + cwrq += cnt; + while (cnt < 16 && (*cp != '-')) { + *cwrq++ = toupper(*cp++); + cnt++; + } + *cwrq = '\0'; + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get frequency + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param fwrq A pointer to iw_freq structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_freq(struct net_device *dev, struct iw_request_info *info, + struct iw_freq *fwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + CHANNEL_FREQ_POWER *cfp; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + cfp = find_cfp_by_band_and_channel(Adapter, 0, + (u16) Adapter->CurBssParams. + BSSDescriptor.Channel); + + if (!cfp) { + if (Adapter->CurBssParams.BSSDescriptor.Channel) { + PRINTM(INFO, "Invalid channel=%d\n", + Adapter->CurBssParams.BSSDescriptor.Channel); + } + return -EINVAL; + } + + fwrq->m = (long) cfp->Freq * 100000; + fwrq->e = 1; + + PRINTM(INFO, "freq=%u\n", fwrq->m); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get current BSSID + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param awrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_wap(struct net_device *dev, struct iw_request_info *info, + struct sockaddr *awrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + memcpy(awrq->sa_data, + Adapter->CurBssParams.BSSDescriptor.MacAddress, ETH_ALEN); + } else { + memset(awrq->sa_data, 0, ETH_ALEN); + } + awrq->sa_family = ARPHRD_ETHER; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set Adapter Node Name + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_nick(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + /* + * Check the size of the string + */ + + if (dwrq->length > 16) { + return -E2BIG; + } + + memset(Adapter->nodeName, 0, sizeof(Adapter->nodeName)); + memcpy(Adapter->nodeName, extra, dwrq->length); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Adapter Node Name + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_nick(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + /* + * Get the Nick Name saved + */ + + strncpy(extra, Adapter->nodeName, 16); + + extra[16] = '\0'; + + /* + * If none, we may want to get the one that was set + */ + + /* + * Push it out ! + */ +#if WIRELESS_EXT > 20 + dwrq->length = strlen(extra); +#else + dwrq->length = strlen(extra) + 1; +#endif + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set RTS threshold + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_rts(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int rthr = vwrq->value; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (vwrq->disabled) { + Adapter->RTSThsd = rthr = MRVDRV_RTS_MAX_VALUE; + } else { + if (rthr < MRVDRV_RTS_MIN_VALUE || rthr > MRVDRV_RTS_MAX_VALUE) + return -EINVAL; + Adapter->RTSThsd = rthr; + } + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_SET, HostCmd_OPTION_WAITFORRSP, + OID_802_11_RTS_THRESHOLD, &rthr); + + LEAVE(); + return ret; +} + +/** + * @brief Get RTS threshold + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_rts(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + Adapter->RTSThsd = 0; + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_GET, HostCmd_OPTION_WAITFORRSP, + OID_802_11_RTS_THRESHOLD, NULL); + if (ret) { + LEAVE(); + return ret; + } + + vwrq->value = Adapter->RTSThsd; + vwrq->disabled = ((vwrq->value < MRVDRV_RTS_MIN_VALUE) + || (vwrq->value > MRVDRV_RTS_MAX_VALUE)); + vwrq->fixed = 1; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set Fragment threshold + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_frag(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + int fthr = vwrq->value; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (vwrq->disabled) { + Adapter->FragThsd = fthr = MRVDRV_FRAG_MAX_VALUE; + } else { + if (fthr < MRVDRV_FRAG_MIN_VALUE || fthr > MRVDRV_FRAG_MAX_VALUE) + return -EINVAL; + Adapter->FragThsd = fthr; + } + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_SET, HostCmd_OPTION_WAITFORRSP, + OID_802_11_FRAGMENTATION_THRESHOLD, &fthr); + LEAVE(); + return ret; +} + +/** + * @brief Get Fragment threshold + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_frag(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + Adapter->FragThsd = 0; + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SNMP_MIB, HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, + OID_802_11_FRAGMENTATION_THRESHOLD, NULL); + if (ret) { + LEAVE(); + return ret; + } + + vwrq->value = Adapter->FragThsd; + vwrq->disabled = ((vwrq->value < MRVDRV_FRAG_MIN_VALUE) + || (vwrq->value > MRVDRV_FRAG_MAX_VALUE)); + vwrq->fixed = 1; + + LEAVE(); + return ret; +} + +/** + * @brief Get Wlan Mode + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_mode(struct net_device *dev, + struct iw_request_info *info, u32 * uwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *adapter = priv->adapter; + + ENTER(); + + switch (adapter->InfrastructureMode) { + case Wlan802_11IBSS: + *uwrq = IW_MODE_ADHOC; + break; + + case Wlan802_11Infrastructure: + *uwrq = IW_MODE_INFRA; + break; + + default: + case Wlan802_11AutoUnknown: + *uwrq = IW_MODE_AUTO; + break; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Encryption key + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_encode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, u8 * extra) +{ + + wlan_private *priv = dev->priv; + wlan_adapter *adapter = priv->adapter; + int index = (dwrq->flags & IW_ENCODE_INDEX); + + ENTER(); + + PRINTM(INFO, "flags=0x%x index=%d length=%d CurrentWepKeyIndex=%d\n", + dwrq->flags, index, dwrq->length, adapter->CurrentWepKeyIndex); + if (index < 0 || index > 4) { + PRINTM(INFO, "Key index #%d out of range.\n", index); + LEAVE(); + return -EINVAL; + } + if (adapter->CurrentWepKeyIndex >= MRVL_NUM_WEP_KEY) + adapter->CurrentWepKeyIndex = 0; + dwrq->flags = 0; + + /* + * Check encryption mode + */ + + switch (adapter->SecInfo.AuthenticationMode) { + case Wlan802_11AuthModeOpen: + dwrq->flags = IW_ENCODE_OPEN; + break; + + case Wlan802_11AuthModeShared: + case Wlan802_11AuthModeNetworkEAP: + dwrq->flags = IW_ENCODE_RESTRICTED; + break; + default: + dwrq->flags = IW_ENCODE_DISABLED | IW_ENCODE_OPEN; + break; + } + + if ((adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled) + || (adapter->SecInfo.WEPStatus == Wlan802_11WEPKeyAbsent) + || adapter->SecInfo.WPAEnabled || adapter->SecInfo.WPA2Enabled) { + dwrq->flags &= ~IW_ENCODE_DISABLED; + } else { + dwrq->flags |= IW_ENCODE_DISABLED; + } + + memset(extra, 0, 16); + + if (!index) { + /* Handle current key request */ + if ((adapter->WepKey[adapter->CurrentWepKeyIndex].KeyLength) && + (adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled)) { + index = adapter->WepKey[adapter->CurrentWepKeyIndex].KeyIndex; + memcpy(extra, adapter->WepKey[index].KeyMaterial, + adapter->WepKey[index].KeyLength); + dwrq->length = adapter->WepKey[index].KeyLength; + /* return current key */ + dwrq->flags |= (index + 1); + /* return WEP enabled */ + dwrq->flags &= ~IW_ENCODE_DISABLED; + } else if ((adapter->SecInfo.WPAEnabled) + || (adapter->SecInfo.WPA2Enabled) + ) { + /* return WPA enabled */ + dwrq->flags &= ~IW_ENCODE_DISABLED; + } else { + dwrq->flags |= IW_ENCODE_DISABLED; + } + } else { + /* Handle specific key requests */ + index--; + if (adapter->WepKey[index].KeyLength) { + memcpy(extra, adapter->WepKey[index].KeyMaterial, + adapter->WepKey[index].KeyLength); + dwrq->length = adapter->WepKey[index].KeyLength; + /* return current key */ + dwrq->flags |= (index + 1); + /* return WEP enabled */ + dwrq->flags &= ~IW_ENCODE_DISABLED; + } else if ((adapter->SecInfo.WPAEnabled) + || (adapter->SecInfo.WPA2Enabled) + ) { + /* return WPA enabled */ + dwrq->flags &= ~IW_ENCODE_DISABLED; + } else { + dwrq->flags |= IW_ENCODE_DISABLED; + } + } + + dwrq->flags |= IW_ENCODE_NOKEY; + + PRINTM(INFO, "Key:%02x:%02x:%02x:%02x:%02x:%02x KeyLen=%d\n", + extra[0], extra[1], extra[2], + extra[3], extra[4], extra[5], dwrq->length); + + PRINTM(INFO, "Return flags=0x%x\n", dwrq->flags); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get TX Power + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_txpow(struct net_device *dev, + struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RF_TX_POWER, + HostCmd_ACT_GEN_GET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + LEAVE(); + return ret; + } + + PRINTM(INFO, "TXPOWER GET %d dbm.\n", Adapter->TxPowerLevel); + vwrq->value = Adapter->TxPowerLevel; + vwrq->fixed = 1; + if (Adapter->RadioOn) { + vwrq->disabled = 0; + vwrq->flags = IW_TXPOW_DBM; + } else { + vwrq->disabled = 1; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set TX Retry Count + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_retry(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *adapter = priv->adapter; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (vwrq->flags == IW_RETRY_LIMIT) { + /* The MAC has a 4-bit Total_Tx_Count register + Total_Tx_Count = 1 + Tx_Retry_Count */ +#define TX_RETRY_MIN 0 +#define TX_RETRY_MAX 14 + if (vwrq->value < TX_RETRY_MIN || vwrq->value > TX_RETRY_MAX) + return -EINVAL; + + /* Set Tx retry count */ + adapter->TxRetryCount = vwrq->value + 1; + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, + OID_802_11_TX_RETRYCOUNT, NULL); + + if (ret) { + LEAVE(); + return ret; + } + } else { + return -EOPNOTSUPP; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get TX Retry Count + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_retry(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + Adapter->TxRetryCount = 0; + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SNMP_MIB, HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, + OID_802_11_TX_RETRYCOUNT, NULL); + if (ret) { + LEAVE(); + return ret; + } + vwrq->disabled = 0; + if (!vwrq->flags) { + vwrq->flags = IW_RETRY_LIMIT; + /* Get Tx retry count */ + vwrq->value = Adapter->TxRetryCount - 1; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Sort Channels + * + * @param freq A pointer to iw_freq structure + * @param num number of Channels + * @return NA + */ +static inline void +sort_channels(struct iw_freq *freq, int num) +{ + int i, j; + struct iw_freq temp; + + for (i = 0; i < num; i++) + for (j = i + 1; j < num; j++) + if (freq[i].i > freq[j].i) { + temp.i = freq[i].i; + temp.m = freq[i].m; + + freq[i].i = freq[j].i; + freq[i].m = freq[j].m; + + freq[j].i = temp.i; + freq[j].m = temp.m; + } +} + +/* data rate listing + MULTI_BANDS: + abg a b b/g + Infra G(12) A(8) B(4) G(12) + Adhoc A+B(12) A(8) B(4) B(4) + + non-MULTI_BANDS: + b b/g + Infra B(4) G(12) + Adhoc B(4) B(4) + */ +/** + * @brief Get Range Info + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_range(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + int i, j; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + struct iw_range *range = (struct iw_range *) extra; + CHANNEL_FREQ_POWER *cfp; + WLAN_802_11_RATES rates; + + ENTER(); + + dwrq->length = sizeof(struct iw_range); + memset(range, 0, sizeof(struct iw_range)); + + range->min_nwid = 0; + range->max_nwid = 0; + + memset(rates, 0, sizeof(rates)); + range->num_bitrates = get_active_data_rates(Adapter, rates); + if (range->num_bitrates > sizeof(rates)) + range->num_bitrates = sizeof(rates); + + for (i = 0; i < MIN(range->num_bitrates, IW_MAX_BITRATES) && rates[i]; + i++) { + range->bitrate[i] = (rates[i] & 0x7f) * 500000; + } + range->num_bitrates = i; + PRINTM(INFO, "IW_MAX_BITRATES=%d num_bitrates=%d\n", IW_MAX_BITRATES, + range->num_bitrates); + + range->num_frequency = 0; + if (wlan_get_state_11d(priv) == ENABLE_11D && + Adapter->MediaConnectStatus == WlanMediaStateConnected) { + u8 chan_no; + u8 band; + + parsed_region_chan_11d_t *parsed_region_chan = + &Adapter->parsed_region_chan; + + band = parsed_region_chan->band; + PRINTM(INFO, "band=%d NoOfChan=%d\n", band, + parsed_region_chan->NoOfChan); + + for (i = 0; (range->num_frequency < IW_MAX_FREQUENCIES) + && (i < parsed_region_chan->NoOfChan); i++) { + chan_no = parsed_region_chan->chanPwr[i].chan; + PRINTM(INFO, "chan_no=%d\n", chan_no); + range->freq[range->num_frequency].i = (long) chan_no; + range->freq[range->num_frequency].m = + (long) chan_2_freq(chan_no, band) * 100000; + range->freq[range->num_frequency].e = 1; + range->num_frequency++; + } + } else { + for (j = 0; (range->num_frequency < IW_MAX_FREQUENCIES) + && (j < sizeof(Adapter->region_channel) + / sizeof(Adapter->region_channel[0])); j++) { + cfp = Adapter->region_channel[j].CFP; + for (i = 0; (range->num_frequency < IW_MAX_FREQUENCIES) + && Adapter->region_channel[j].Valid + && cfp && (i < Adapter->region_channel[j].NrCFP); i++) { + range->freq[range->num_frequency].i = (long) cfp->Channel; + range->freq[range->num_frequency].m = + (long) cfp->Freq * 100000; + range->freq[range->num_frequency].e = 1; + cfp++; + range->num_frequency++; + } + } + } + + PRINTM(INFO, "IW_MAX_FREQUENCIES=%d num_frequency=%d\n", + IW_MAX_FREQUENCIES, range->num_frequency); + + range->num_channels = range->num_frequency; + + sort_channels(&range->freq[0], range->num_frequency); + + /* + * Set an indication of the max TCP throughput in bit/s that we can + * expect using this interface + */ + if (i > 2) + range->throughput = 5000 * 1000; + else + range->throughput = 1500 * 1000; + + range->min_rts = MRVDRV_RTS_MIN_VALUE; + range->max_rts = MRVDRV_RTS_MAX_VALUE; + range->min_frag = MRVDRV_FRAG_MIN_VALUE; + range->max_frag = MRVDRV_FRAG_MAX_VALUE; + + range->encoding_size[0] = 5; + range->encoding_size[1] = 13; + range->num_encoding_sizes = 2; + range->max_encoding_tokens = 4; + +#define IW_POWER_PERIOD_MIN 1000000 /* 1 sec */ +#define IW_POWER_PERIOD_MAX 120000000 /* 2 min */ +#define IW_POWER_TIMEOUT_MIN 1000 /* 1 ms */ +#define IW_POWER_TIMEOUT_MAX 1000000 /* 1 sec */ + + /* Power Management duration & timeout */ + range->min_pmp = IW_POWER_PERIOD_MIN; + range->max_pmp = IW_POWER_PERIOD_MAX; + range->min_pmt = IW_POWER_TIMEOUT_MIN; + range->max_pmt = IW_POWER_TIMEOUT_MAX; + range->pmp_flags = IW_POWER_PERIOD; + range->pmt_flags = IW_POWER_TIMEOUT; + range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_ALL_R; + + /* + * Minimum version we recommend + */ + range->we_version_source = 15; + + /* + * Version we are compiled with + */ + range->we_version_compiled = WIRELESS_EXT; + + range->retry_capa = IW_RETRY_LIMIT; + range->retry_flags = IW_RETRY_LIMIT | IW_RETRY_MAX; + + range->min_retry = TX_RETRY_MIN; + range->max_retry = TX_RETRY_MAX; + + /* + * Set the qual, level and noise range values + */ + /* + * need to put the right values here + */ +#define IW_MAX_QUAL_PERCENT 100 +#define IW_AVG_QUAL_PERCENT 70 + range->max_qual.qual = IW_MAX_QUAL_PERCENT; + range->max_qual.level = 0; + range->max_qual.noise = 0; + + range->avg_qual.qual = IW_AVG_QUAL_PERCENT; + range->avg_qual.level = 0; + range->avg_qual.noise = 0; + + range->sensitivity = 0; + /* + * Setup the supported power level ranges + */ + memset(range->txpower, 0, sizeof(range->txpower)); + range->txpower[0] = Adapter->MinTxPowerLevel; + range->txpower[1] = Adapter->MaxTxPowerLevel; + range->num_txpower = 2; + range->txpower_capa = IW_TXPOW_DBM | IW_TXPOW_RANGE; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set power management + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_set_power(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + /* PS is currently supported only in Infrastructure Mode + * Remove this check if it is to be supported in IBSS mode also + */ + + if (vwrq->disabled) { + Adapter->PSMode = Wlan802_11PowerModeCAM; + if (Adapter->PSState != PS_STATE_FULL_POWER) { + PSWakeup(priv, HostCmd_OPTION_WAITFORRSP); + } + + return 0; + } + + if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_TIMEOUT) { + PRINTM(INFO, "Setting power timeout command is not supported\n"); + return -EINVAL; + } else if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_PERIOD) { + PRINTM(INFO, "Setting power period command is not supported\n"); + return -EINVAL; + } + + if (Adapter->PSMode != Wlan802_11PowerModeCAM) { + return WLAN_STATUS_SUCCESS; + } + + Adapter->PSMode = Wlan802_11PowerModeMAX_PSP; + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + PSSleep(priv, HostCmd_OPTION_WAITFORRSP); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get power management + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_power(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int mode; + + ENTER(); + + mode = Adapter->PSMode; + + if ((vwrq->disabled = (mode == Wlan802_11PowerModeCAM)) + || Adapter->MediaConnectStatus == WlanMediaStateDisconnected) { + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + vwrq->value = 0; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set sensitivity threshold + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_sens(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + ENTER(); + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get sensitivity threshold + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_FAILURE + */ +static int +wlan_get_sens(struct net_device *dev, + struct iw_request_info *info, struct iw_param *vwrq, + char *extra) +{ + ENTER(); + LEAVE(); + return WLAN_STATUS_FAILURE; +} + +/** + * @brief Append/Reset IE buffer. + * + * Pass an opaque block of data, expected to be IEEE IEs, to the driver + * for eventual passthrough to the firmware in an associate/join + * (and potentially start) command. This function is the main body + * for both wlan_set_gen_ie_ioctl and wlan_set_gen_ie + * + * Data is appended to an existing buffer and then wrapped in a passthrough + * TLV in the command API to the firmware. The firmware treats the data + * as a transparent passthrough to the transmitted management frame. + * + * @param Adapter A pointer to wlan_private structure + * @param ie_data_ptr A pointer to iwreq structure + * @param ie_len Length of the IE or IE block passed in ie_data_ptr + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_gen_ie_helper(wlan_private * priv, u8 * ie_data_ptr, u16 ie_len) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + IEEEtypes_VendorHeader_t *pVendorIe; + const u8 wpa_oui[] = { 0x00, 0x50, 0xf2, 0x01 }; + const u8 wps_oui[] = { 0x00, 0x50, 0xf2, 0x04 }; + /* If the passed length is zero, reset the buffer */ + if (ie_len == 0) { + Adapter->genIeBufferLen = 0; + + } else if (ie_data_ptr == NULL) { + /* NULL check */ + ret = -EINVAL; + } else { + + pVendorIe = (IEEEtypes_VendorHeader_t *) ie_data_ptr; + + /* Test to see if it is a WPA IE, if not, then it is a gen IE */ + if ((pVendorIe->ElementId == RSN_IE) + || ((pVendorIe->ElementId == WPA_IE) + && (pVendorIe->OuiType == wpa_oui[3]) + && (memcmp(pVendorIe->Oui, wpa_oui, sizeof(pVendorIe->Oui)) == + 0))) { + + /* IE is a WPA/WPA2 IE so call set_wpa function */ + ret = wlan_set_wpa_ie_helper(priv, ie_data_ptr, ie_len); + } else if ((pVendorIe->ElementId == WPS_IE) + && (memcmp(pVendorIe->Oui, wps_oui, sizeof(pVendorIe->Oui)) + == 0) + && (pVendorIe->OuiType == wps_oui[3])) { + /* + * Discard first two byte (Element ID and Length) + * because they are not needed in the case of setting WPS_IE + */ + if (pVendorIe->Len > 4) { + memcpy((u8 *) & Adapter->wps.wpsIe, ie_data_ptr, ie_len); + HEXDUMP("wpsIe", + (u8 *) & Adapter->wps.wpsIe, + Adapter->wps.wpsIe.VendHdr.Len + 2); + + } else { + /* Only wps oui exist, reset driver wps buffer */ + memset((u8 *) & Adapter->wps.wpsIe, + 0x00, sizeof(Adapter->wps.wpsIe)); + PRINTM(INFO, "wpsIe cleared\n"); + } + } else { + /* + * Verify that the passed length is not larger than the available + * space remaining in the buffer + */ + if (ie_len < (sizeof(Adapter->genIeBuffer) + - Adapter->genIeBufferLen)) { + + /* Append the passed data to the end of the genIeBuffer */ + if (copy_from_user((Adapter->genIeBuffer + + Adapter->genIeBufferLen), + ie_data_ptr, ie_len)) { + PRINTM(INFO, "Copy from user failed\n"); + ret = -EFAULT; + + } else { + /* Increment the stored buffer length by the size passed */ + Adapter->genIeBufferLen += ie_len; + } + + } else { + /* Passed data does not fit in the remaining buffer space */ + ret = WLAN_STATUS_FAILURE; + } + } + } + + /* Return WLAN_STATUS_SUCCESS, or < 0 for error case */ + return ret; +} + +/** + * @brief Get IE buffer from driver + * + * Used to pass an opaque block of data, expected to be IEEE IEs, + * back to the application. Currently the data block passed + * back to the application is the saved association response retrieved + * from the firmware. + * + * @param priv A pointer to wlan_private structure + * @param ie_data_ptr A pointer to the IE or IE block + * @param ie_len_ptr In/Out parameter pointer for the buffer length passed + * in ie_data_ptr and the resulting data length copied + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_gen_ie_helper(wlan_private * priv, + u8 * ie_data_ptr, u16 * ie_len_ptr) +{ + wlan_adapter *Adapter = priv->adapter; + IEEEtypes_AssocRsp_t *pAssocRsp; + int copySize; + + pAssocRsp = (IEEEtypes_AssocRsp_t *) Adapter->assocRspBuffer; + + /* + * Set the amount to copy back to the application as the minimum of the + * available IE data or the buffer provided by the application + */ + copySize = (Adapter->assocRspSize - sizeof(pAssocRsp->Capability) - + -sizeof(pAssocRsp->StatusCode) - sizeof(pAssocRsp->AId)); + copySize = MIN(copySize, *ie_len_ptr); + + /* Copy the IEEE TLVs in the assoc response back to the application */ + if (copy_to_user(ie_data_ptr, (u8 *) pAssocRsp->IEBuffer, copySize)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + /* Returned copy length */ + *ie_len_ptr = copySize; + + /* No error on return */ + return WLAN_STATUS_SUCCESS; +} + +#if (WIRELESS_EXT >= 18) +/** + * @brief Set IE + * + * Calls main function set_gen_ie_fuct that adds the inputted IE + * to the genie buffer + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_gen_ie(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + return wlan_set_gen_ie_helper(dev->priv, dwrq->pointer, dwrq->length); +} + +/** + * @brief Get IE + * + * Calls main function get_gen_ie_fuct that retrieves expected IEEE IEs + * and places then in the iw_point structure + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_gen_ie(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + return wlan_get_gen_ie_helper(dev->priv, dwrq->pointer, &dwrq->length); +} + +/** + * @brief Set authentication mode + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_setauthalg(wlan_private * priv, int alg) +{ + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + PRINTM(INFO, "auth alg is %#x\n", alg); + + switch (alg) { + case IW_AUTH_ALG_SHARED_KEY: + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeShared; + break; + case IW_AUTH_ALG_LEAP: + //clear WPA IE + wlan_set_wpa_ie_helper(priv, NULL, 0); + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeNetworkEAP; + break; + case IW_AUTH_ALG_OPEN_SYSTEM: + default: + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeOpen; + break; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief set authentication mode params + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_auth(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + switch (vwrq->flags & IW_AUTH_INDEX) { + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + if (vwrq->value & IW_AUTH_CIPHER_NONE) + priv->adapter->SecInfo.EncryptionMode = CIPHER_NONE; + else if (vwrq->value & IW_AUTH_CIPHER_WEP40) + priv->adapter->SecInfo.EncryptionMode = CIPHER_WEP40; + else if (vwrq->value & IW_AUTH_CIPHER_TKIP) + priv->adapter->SecInfo.EncryptionMode = CIPHER_TKIP; + else if (vwrq->value & IW_AUTH_CIPHER_CCMP) + priv->adapter->SecInfo.EncryptionMode = CIPHER_CCMP; + else if (vwrq->value & IW_AUTH_CIPHER_WEP104) + priv->adapter->SecInfo.EncryptionMode = CIPHER_WEP104; + break; + case IW_AUTH_80211_AUTH_ALG: + wlan_setauthalg(priv, vwrq->value); + break; + case IW_AUTH_WPA_ENABLED: + if (vwrq->value == FALSE) + wlan_set_wpa_ie_helper(priv, NULL, 0); + break; + case IW_AUTH_WPA_VERSION: + case IW_AUTH_KEY_MGMT: + case IW_AUTH_TKIP_COUNTERMEASURES: + case IW_AUTH_DROP_UNENCRYPTED: + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + case IW_AUTH_ROAMING_CONTROL: + case IW_AUTH_PRIVACY_INVOKED: + default: + break; + } + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief get authentication mode params + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_auth(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + ENTER(); + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + switch (vwrq->flags & IW_AUTH_INDEX) { + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + if (priv->adapter->SecInfo.EncryptionMode == CIPHER_NONE) + vwrq->value = IW_AUTH_CIPHER_NONE; + else if (priv->adapter->SecInfo.EncryptionMode == CIPHER_WEP40) + vwrq->value = IW_AUTH_CIPHER_WEP40; + else if (priv->adapter->SecInfo.EncryptionMode == CIPHER_TKIP) + vwrq->value = IW_AUTH_CIPHER_TKIP; + else if (priv->adapter->SecInfo.EncryptionMode == CIPHER_CCMP) + vwrq->value = IW_AUTH_CIPHER_CCMP; + else if (priv->adapter->SecInfo.EncryptionMode == CIPHER_WEP104) + vwrq->value = IW_AUTH_CIPHER_WEP104; + break; + case IW_AUTH_80211_AUTH_ALG: + if (Adapter->SecInfo.AuthenticationMode == Wlan802_11AuthModeShared) + vwrq->value = IW_AUTH_ALG_SHARED_KEY; + else if (Adapter->SecInfo.AuthenticationMode == + Wlan802_11AuthModeNetworkEAP) + vwrq->value = IW_AUTH_ALG_LEAP; + else + vwrq->value = IW_AUTH_ALG_OPEN_SYSTEM; + break; + case IW_AUTH_WPA_ENABLED: + if (Adapter->Wpa_ie_len > 0) + vwrq->value = TRUE; + else + vwrq->value = FALSE; + break; + case IW_AUTH_WPA_VERSION: + case IW_AUTH_KEY_MGMT: + case IW_AUTH_TKIP_COUNTERMEASURES: + case IW_AUTH_DROP_UNENCRYPTED: + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + case IW_AUTH_ROAMING_CONTROL: + case IW_AUTH_PRIVACY_INVOKED: + default: + ret = -EOPNOTSUPP; + break; + } + LEAVE(); + return ret; +} + +/** + * @brief Request MLME operation + * + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_mlme(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct iw_mlme *mlme = (struct iw_mlme *) extra; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + if ((mlme->cmd == IW_MLME_DEAUTH) || (mlme->cmd == IW_MLME_DISASSOC)) { + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure && + Adapter->MediaConnectStatus == WlanMediaStateConnected) { + SendDeauthentication(priv); + } else if (Adapter->InfrastructureMode == Wlan802_11IBSS && + Adapter->MediaConnectStatus == WlanMediaStateConnected) { + StopAdhocNetwork(priv); + } + } + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Extended version of encoding configuration + * + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_encode_ext(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + struct iw_encode_ext *ext = (struct iw_encode_ext *) extra; + wlan_private *priv = dev->priv; + WLAN_802_11_KEY *pkey; + int keyindex; + u8 *pKeyMaterial = NULL; + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + keyindex = dwrq->flags & IW_ENCODE_INDEX; + if (keyindex > 4) + return -EINVAL; + if (ext->key_len > (dwrq->length - sizeof(struct iw_encode_ext))) + return -EINVAL; + pKeyMaterial = (u8 *) (ext + 1); + //Disable Key + if ((dwrq->flags & IW_ENCODE_DISABLED) && (ext->key_len == 0)) { + dwrq->length = 0; + wlan_set_encode_nonwpa(dev, info, dwrq, extra); + return WLAN_STATUS_SUCCESS; + } + //Set WEP key + if (ext->key_len <= MAX_WEP_KEY_SIZE) { + dwrq->length = ext->key_len; + wlan_set_encode_nonwpa(dev, info, dwrq, pKeyMaterial); + if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { + dwrq->length = 0; + wlan_set_encode_nonwpa(dev, info, dwrq, extra); + } + } else { + pkey = kmalloc(sizeof(WLAN_802_11_KEY) + ext->key_len, GFP_KERNEL); + if (!pkey) { + PRINTM(INFO, "allocate key buffer failed!\n"); + return -ENOMEM; + } + memset(pkey, 0, sizeof(WLAN_802_11_KEY) + ext->key_len); + memcpy((u8 *) pkey->BSSID, (u8 *) ext->addr.sa_data, ETH_ALEN); + pkey->KeyLength = ext->key_len; + memcpy(pkey->KeyMaterial, pKeyMaterial, ext->key_len); + pkey->KeyIndex = keyindex - 1; + if (pkey->KeyIndex == 0) + pkey->KeyIndex = 0x40000000; + if (ext->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) + memcpy((u8 *) & pkey->KeyRSC, ext->rx_seq, + IW_ENCODE_SEQ_MAX_SIZE); + pkey->Length = sizeof(WLAN_802_11_KEY) + ext->key_len; + wlan_set_encode_wpa(dev, info, dwrq, (u8 *) pkey); + kfree(pkey); + } + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Extended version of encoding configuration + * + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param dwrq A pointer to iw_point structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_encode_ext(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + return -EOPNOTSUPP; +} +#endif /* #if (WIRELESS_EXT >= 18) */ + +/** + * @brief Append/Reset IE buffer. + * + * Pass an opaque block of data, expected to be IEEE IEs, to the driver + * for eventual passthrough to the firmware in an associate/join + * (and potentially start) command. + * + * Data is appended to an existing buffer and then wrapped in a passthrough + * TLV in the command API to the firmware. The firmware treats the data + * as a transparent passthrough to the transmitted management frame. + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_gen_ie_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + return wlan_set_gen_ie_helper(priv, + wrq->u.data.pointer, wrq->u.data.length); +} + +/** + * @brief Get IE buffer from driver + * + * Used to pass an opaque block of data, expected to be IEEE IEs, + * back to the application. Currently the data block passed + * back to the application is the saved association response retrieved + * from the firmware. + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_gen_ie_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + return wlan_get_gen_ie_helper(priv, + wrq->u.data.pointer, &wrq->u.data.length); +} + +/* + * iwconfig settable callbacks + */ +static const iw_handler wlan_handler[] = { + (iw_handler) wlan_config_commit, /* SIOCSIWCOMMIT */ + (iw_handler) wlan_get_name, /* SIOCGIWNAME */ + (iw_handler) NULL, /* SIOCSIWNWID */ + (iw_handler) NULL, /* SIOCGIWNWID */ + (iw_handler) wlan_set_freq, /* SIOCSIWFREQ */ + (iw_handler) wlan_get_freq, /* SIOCGIWFREQ */ + (iw_handler) wlan_set_mode, /* SIOCSIWMODE */ + (iw_handler) wlan_get_mode, /* SIOCGIWMODE */ + (iw_handler) wlan_set_sens, /* SIOCSIWSENS */ + (iw_handler) wlan_get_sens, /* SIOCGIWSENS */ + (iw_handler) NULL, /* SIOCSIWRANGE */ + (iw_handler) wlan_get_range, /* SIOCGIWRANGE */ + (iw_handler) NULL, /* SIOCSIWPRIV */ + (iw_handler) NULL, /* SIOCGIWPRIV */ + (iw_handler) NULL, /* SIOCSIWSTATS */ + (iw_handler) NULL, /* SIOCGIWSTATS */ +#if WIRELESS_EXT > 15 + iw_handler_set_spy, /* SIOCSIWSPY */ + iw_handler_get_spy, /* SIOCGIWSPY */ + iw_handler_set_thrspy, /* SIOCSIWTHRSPY */ + iw_handler_get_thrspy, /* SIOCGIWTHRSPY */ +#else /* WIRELESS_EXT > 15 */ +#ifdef WIRELESS_SPY + (iw_handler) wlan_set_spy, /* SIOCSIWSPY */ + (iw_handler) wlan_get_spy, /* SIOCGIWSPY */ +#else /* WIRELESS_SPY */ + (iw_handler) NULL, /* SIOCSIWSPY */ + (iw_handler) NULL, /* SIOCGIWSPY */ +#endif /* WIRELESS_SPY */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ +#endif /* WIRELESS_EXT > 15 */ + (iw_handler) wlan_set_wap, /* SIOCSIWAP */ + (iw_handler) wlan_get_wap, /* SIOCGIWAP */ +#if WIRELESS_EXT >= 18 + (iw_handler) wlan_set_mlme, /* SIOCSIWMLME */ +#else + (iw_handler) NULL, /* -- hole -- */ +#endif + //(iw_handler) wlan_get_aplist, /* SIOCGIWAPLIST */ + NULL, /* SIOCGIWAPLIST */ +#if WIRELESS_EXT > 13 + (iw_handler) wlan_set_scan, /* SIOCSIWSCAN */ + (iw_handler) wlan_get_scan, /* SIOCGIWSCAN */ +#else /* WIRELESS_EXT > 13 */ + (iw_handler) NULL, /* SIOCSIWSCAN */ + (iw_handler) NULL, /* SIOCGIWSCAN */ +#endif /* WIRELESS_EXT > 13 */ + (iw_handler) wlan_set_essid, /* SIOCSIWESSID */ + (iw_handler) wlan_get_essid, /* SIOCGIWESSID */ + (iw_handler) wlan_set_nick, /* SIOCSIWNICKN */ + (iw_handler) wlan_get_nick, /* SIOCGIWNICKN */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) wlan_set_rate, /* SIOCSIWRATE */ + (iw_handler) wlan_get_rate, /* SIOCGIWRATE */ + (iw_handler) wlan_set_rts, /* SIOCSIWRTS */ + (iw_handler) wlan_get_rts, /* SIOCGIWRTS */ + (iw_handler) wlan_set_frag, /* SIOCSIWFRAG */ + (iw_handler) wlan_get_frag, /* SIOCGIWFRAG */ + (iw_handler) wlan_set_txpow, /* SIOCSIWTXPOW */ + (iw_handler) wlan_get_txpow, /* SIOCGIWTXPOW */ + (iw_handler) wlan_set_retry, /* SIOCSIWRETRY */ + (iw_handler) wlan_get_retry, /* SIOCGIWRETRY */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) wlan_get_encode, /* SIOCGIWENCODE */ + (iw_handler) wlan_set_power, /* SIOCSIWPOWER */ + (iw_handler) wlan_get_power, /* SIOCGIWPOWER */ +#if (WIRELESS_EXT >= 18) + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) NULL, /* -- hole -- */ + (iw_handler) wlan_set_gen_ie, /* SIOCSIWGENIE */ + (iw_handler) wlan_get_gen_ie, /* SIOCGIWGENIE */ + (iw_handler) wlan_set_auth, /* SIOCSIWAUTH */ + (iw_handler) wlan_get_auth, /* SIOCGIWAUTH */ + (iw_handler) wlan_set_encode_ext, /* SIOCSIWENCODEEXT */ + (iw_handler) wlan_get_encode_ext, /* SIOCGIWENCODEEXT */ +#endif /* WIRELESSS_EXT >= 18 */ +}; + +/* + * iwpriv settable callbacks + */ + +static const iw_handler wlan_private_handler[] = { + NULL, /* SIOCIWFIRSTPRIV */ +}; + +static const struct iw_priv_args wlan_private_args[] = { + /* + * { cmd, set_args, get_args, name } + */ + { + WLANEXTSCAN, + IW_PRIV_TYPE_INT, + IW_PRIV_TYPE_CHAR | 2, + "extscan"}, + { + WLANHOSTCMD, + IW_PRIV_TYPE_BYTE | 2047, + IW_PRIV_TYPE_BYTE | 2047, + "hostcmd"}, + { + WLANARPFILTER, + IW_PRIV_TYPE_BYTE | 2047, + IW_PRIV_TYPE_BYTE | 2047, + "arpfilter"}, + { + WLANREGRDWR, + IW_PRIV_TYPE_CHAR | 256, + IW_PRIV_TYPE_CHAR | 256, + "regrdwr"}, + { + WLANCMD52RDWR, + IW_PRIV_TYPE_BYTE | 7, + IW_PRIV_TYPE_BYTE | 7, + "sdcmd52rw"}, + { + WLANCMD53RDWR, + IW_PRIV_TYPE_CHAR | 32, + IW_PRIV_TYPE_CHAR | 32, + "sdcmd53rw"}, + { + WLAN_SETCONF_GETCONF, + IW_PRIV_TYPE_BYTE | MAX_SETGET_CONF_SIZE, + IW_PRIV_TYPE_BYTE | MAX_SETGET_CONF_SIZE, + "setgetconf"}, + { + WLANCISDUMP, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_BYTE | 512, + "getcis"}, + { + WLANSCAN_TYPE, + IW_PRIV_TYPE_CHAR | 8, + IW_PRIV_TYPE_CHAR | 8, + "scantype"}, + { + WLAN_SETINT_GETINT, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + ""}, + { + WLANNF, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getNF"}, + { + WLANRSSI, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getRSSI"}, + { + WLANBGSCAN, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "bgscan"}, + { + WLANENABLE11D, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "enable11d"}, + { + WLANADHOCGRATE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "adhocgrate"}, + { + WLANSDIOCLOCK, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "sdioclock"}, + { + WLANWMM_ENABLE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "wmm"}, + { + WLANNULLGEN, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "uapsdnullgen"}, + { + WLANADHOCCSET, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "setcoalescing"}, + { + WLAN_ADHOC_G_PROT, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "adhocgprot"}, + + { + WLAN_SETONEINT_GETONEINT, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + ""}, + { + WLAN_WMM_QOSINFO, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "wmm_qosinfo"}, + { + WLAN_LISTENINTRVL, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "lolisteninter"}, + { + WLAN_FW_WAKEUP_METHOD, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "fwwakeupmethod"}, + { + WLAN_NULLPKTINTERVAL, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "psnullinterval"}, + { + WLAN_BCN_MISS_TIMEOUT, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "bcnmisto"}, + { + WLAN_ADHOC_AWAKE_PERIOD, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "adhocawakepd"}, + { + WLAN_LDO, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "ldocfg"}, + { + WLAN_SDIO_MODE, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "sdiomode"}, + { + WLAN_RTS_CTS_CTRL, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "rtsctsctrl"}, + { + WLAN_AUTODEEPSLEEP, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "autodeepsleep"}, + { + WLAN_WAKEUP_MT, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_INT | 1, + "wakeupmt"}, + /* Using iwpriv sub-command feature */ + { + WLAN_SETONEINT_GETNONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + ""}, + { + WLAN_SUBCMD_SETRXANTENNA, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setrxant"}, + { + WLAN_SUBCMD_SETTXANTENNA, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "settxant"}, + { + WLANSETAUTHALG, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "authalgs", + }, + { + WLANSETENCRYPTIONMODE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "encryptionmode", + }, + { + WLANSETREGION, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setregioncode"}, + { + WLAN_SET_LISTEN_INTERVAL, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setlisteninter"}, + { + WLAN_SET_MULTIPLE_DTIM, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setmultipledtim"}, + { + WLANSETBCNAVG, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setbcnavg"}, + { + WLANSETDATAAVG, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "setdataavg"}, + { + WLANASSOCIATE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + IW_PRIV_TYPE_NONE, + "associate"}, + { + WLAN_SETNONE_GETONEINT, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + ""}, + { + WLANGETREGION, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getregioncode"}, + { + WLAN_GET_LISTEN_INTERVAL, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getlisteninter"}, + { + WLAN_GET_MULTIPLE_DTIM, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getmultipledtim"}, + { + WLAN_GET_TX_RATE, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "gettxrate"}, + { + WLANGETBCNAVG, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getbcnavg"}, + { + WLANGETDATAAVG, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getdataavg"}, + { + WLANGETDTIM, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "getdtim"}, + { + WLAN_SETNONE_GETTWELVE_CHAR, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_CHAR | 12, + ""}, + { + WLAN_SUBCMD_GETRXANTENNA, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_CHAR | 12, + "getrxant"}, + { + WLAN_SUBCMD_GETTXANTENNA, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_CHAR | 12, + "gettxant"}, + { + WLAN_GET_TSF, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_CHAR | 12, + "gettsf"}, + { + WLAN_WPS_SESSION, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_CHAR | 12, + "wpssession"}, + { + WLANDEEPSLEEP, + IW_PRIV_TYPE_CHAR | 1, + IW_PRIV_TYPE_CHAR | 6, + "deepsleep"}, + { + WLANHOSTSLEEPCFG, + IW_PRIV_TYPE_CHAR | 31, + IW_PRIV_TYPE_NONE, + "hostsleepcfg"}, + { + WLAN_SETNONE_GETNONE, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + ""}, + { + WLANDEAUTH, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "deauth"}, + { + WLANADHOCSTOP, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "adhocstop"}, + { + WLANRADIOON, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "radioon"}, + { + WLANRADIOOFF, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "radiooff"}, + { + WLANREMOVEADHOCAES, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "rmaeskey"}, +#ifdef REASSOCIATION + { + WLANREASSOCIATIONAUTO, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "reasso-on"}, + { + WLANREASSOCIATIONUSER, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "reasso-off"}, +#endif /* REASSOCIATION */ + { + WLANWLANIDLEON, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "wlanidle-on"}, + { + WLANWLANIDLEOFF, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_NONE, + "wlanidle-off"}, + { + WLAN_SET64CHAR_GET64CHAR, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + ""}, + { + WLANSLEEPPARAMS, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "sleepparams"}, + + { + WLAN_BCA_TIMESHARE, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "bca-ts"}, + { + WLANSCAN_MODE, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "scanmode"}, + { + WLAN_GET_ADHOC_STATUS, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "getadhocstatus"}, + { + WLAN_SET_GEN_IE, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "setgenie"}, + { + WLAN_GET_GEN_IE, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "getgenie"}, + { + WLAN_WMM_QUEUE_STATUS, + IW_PRIV_TYPE_CHAR | 64, + IW_PRIV_TYPE_CHAR | 64, + "qstatus"}, + { + WLAN_SETWORDCHAR_GETNONE, + IW_PRIV_TYPE_CHAR | 32, + IW_PRIV_TYPE_NONE, + ""}, + { + WLANSETADHOCAES, + IW_PRIV_TYPE_CHAR | 32, + IW_PRIV_TYPE_NONE, + "setaeskey"}, + { + WLAN_SETONEINT_GETWORDCHAR, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_CHAR | 128, + ""}, + { + WLANGETADHOCAES, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_CHAR | 128, + "getaeskey"}, + { + WLANVERSION, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_CHAR | 128, + "version"}, + { + WLANVEREXT, + IW_PRIV_TYPE_INT | 1, + IW_PRIV_TYPE_CHAR | 128, + "verext"}, + { + WLANSETWPAIE, + IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | 24, + IW_PRIV_TYPE_NONE, + "setwpaie"}, + { + WLANGETLOG, + IW_PRIV_TYPE_NONE, + IW_PRIV_TYPE_CHAR | GETLOG_BUFSIZE, + "getlog"}, + { + WLAN_SET_GET_SIXTEEN_INT, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + ""}, + { + WLAN_TPCCFG, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "tpccfg"}, + { + WLAN_SCANPROBES, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "scanprobes"}, + { + WLAN_LED_GPIO_CTRL, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "ledgpio"}, + { + WLAN_SLEEP_PERIOD, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "sleeppd"}, + { + WLAN_ADAPT_RATESET, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "rateadapt"}, + + { + WLAN_INACTIVITY_TIMEOUT, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "inactivityto"}, + { + WLANSNR, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "getSNR"}, + { + WLAN_GET_RATE, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "getrate"}, + { + WLAN_GET_RXINFO, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "getrxinfo"}, + { + WLAN_SET_ATIM_WINDOW, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "atimwindow"}, + { + WLAN_BEACON_INTERVAL, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "bcninterval"}, + { + WLAN_SDIO_PULL_CTRL, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "sdiopullctrl"}, + { + WLAN_SCAN_TIME, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "scantime"}, + { + WLAN_DATA_SUBSCRIBE_EVENT, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "dataevtcfg"}, + { + WLAN_TXCONTROL, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "txcontrol"}, + { + WLANHSCFG, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "hscfg"}, + { + WLANHSSETPARA, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "hssetpara"}, +#ifdef DEBUG_LEVEL1 + { + WLAN_DRV_DBG, + IW_PRIV_TYPE_INT | 16, + IW_PRIV_TYPE_INT | 16, + "drvdbg"}, +#endif + { + WLAN_SET_GET_2K, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + ""}, + { + WLAN_SET_USER_SCAN, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "setuserscan"}, + { + WLAN_GET_SCAN_TABLE, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "getscantable"}, + { + WLAN_SET_MRVL_TLV, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "setmrvltlv"}, + { + WLAN_GET_ASSOC_RSP, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "getassocrsp"}, + { + WLAN_ADDTS_REQ, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "addts"}, + { + WLAN_DELTS_REQ, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "delts"}, + { + WLAN_QUEUE_CONFIG, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "qconfig"}, + { + WLAN_QUEUE_STATS, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "qstats"}, + { + WLAN_TX_PKT_STATS, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "txpktstats"}, + { + WLAN_GET_CFP_TABLE, + IW_PRIV_TYPE_BYTE | 2000, + IW_PRIV_TYPE_BYTE | 2000, + "getcfptable"}, +}; + +struct iw_handler_def wlan_handler_def = { + num_standard:sizeof(wlan_handler) / sizeof(iw_handler), + num_private:sizeof(wlan_private_handler) / sizeof(iw_handler), + num_private_args:sizeof(wlan_private_args) / + sizeof(struct iw_priv_args), + standard:(iw_handler *) wlan_handler, + private:(iw_handler *) wlan_private_handler, + private_args:(struct iw_priv_args *) wlan_private_args, +}; + +/** + * @brief get the channel frequency power info with specific channel + * + * @param band it can be BAND_A, BAND_G or BAND_B + * @param channel the channel for looking + * @param region_channel A pointer to REGION_CHANNEL structure + * @return A pointer to CHANNEL_FREQ_POWER structure or NULL if not find. + */ + +CHANNEL_FREQ_POWER * +get_cfp_by_band_and_channel(u8 band, u16 channel, + REGION_CHANNEL * region_channnel) +{ + REGION_CHANNEL *rc; + CHANNEL_FREQ_POWER *cfp = NULL; + int i, j; + + for (j = 0; !cfp && (j < MAX_REGION_CHANNEL_NUM); j++) { + rc = ®ion_channnel[j]; + + if (!rc->Valid || !rc->CFP) + continue; + if (rc->Band != band) + continue; + if (channel == FIRST_VALID_CHANNEL) + cfp = &rc->CFP[0]; + else { + for (i = 0; i < rc->NrCFP; i++) { + if (rc->CFP[i].Channel == channel) { + cfp = &rc->CFP[i]; + break; + } + } + } + } + + if (!cfp && channel) + PRINTM(INFO, "get_cfp_by_band_and_channel(): cannot find " + "cfp by band %d & channel %d\n", band, channel); + + LEAVE(); + return cfp; +} + +/** + * @brief wlan hostcmd ioctl handler + * + * @param dev A pointer to net_device structure + * @param req A pointer to ifreq structure + * @param cmd command + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_hostcmd_ioctl(struct net_device *dev, struct ifreq *req, int cmd) +{ + u8 *tempResponseBuffer; + CmdCtrlNode *pCmdNode; + HostCmd_DS_GEN *pCmdPtr; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + u16 wait_option = 0; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + if ((wrq->u.data.pointer == NULL) || (wrq->u.data.length < S_DS_GEN)) { + PRINTM(INFO, + "wlan_hostcmd_ioctl() corrupt data: pointer=%p, length=%d\n", + wrq->u.data.pointer, wrq->u.data.length); + return -EFAULT; + } + + /* + * Get a free command control node + */ + if (!(pCmdNode = GetFreeCmdCtrlNode(priv))) { + PRINTM(INFO, "Failed GetFreeCmdCtrlNode\n"); + return -ENOMEM; + } + + if (! + (tempResponseBuffer = + kmalloc(MRVDRV_SIZE_OF_CMD_BUFFER, GFP_KERNEL))) { + PRINTM(INFO, "ERROR: Failed to allocate response buffer!\n"); + CleanupAndInsertCmd(priv, pCmdNode); + return -ENOMEM; + } + + wait_option |= HostCmd_OPTION_WAITFORRSP; + + SetCmdCtrlNode(priv, pCmdNode, 0, wait_option, NULL); + init_waitqueue_head(&pCmdNode->cmdwait_q); + + pCmdPtr = (HostCmd_DS_GEN *) pCmdNode->BufVirtualAddr; + + /* + * Copy the whole command into the command buffer + */ + if (copy_from_user(pCmdPtr, wrq->u.data.pointer, wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + kfree(tempResponseBuffer); + CleanupAndInsertCmd(priv, pCmdNode); + return -EFAULT; + } + + if (pCmdPtr->Size < S_DS_GEN) { + PRINTM(INFO, "wlan_hostcmd_ioctl() invalid cmd size: Size=%d\n", + pCmdPtr->Size); + kfree(tempResponseBuffer); + CleanupAndInsertCmd(priv, pCmdNode); + return -EFAULT; + } + + pCmdNode->pdata_buf = tempResponseBuffer; + pCmdNode->CmdFlags |= CMD_F_HOSTCMD; + + pCmdPtr->Result = 0; + + PRINTM(INFO, "HOSTCMD Command: 0x%04x Size: %d\n", + pCmdPtr->Command, pCmdPtr->Size); + HEXDUMP("Command Data", (u8 *) (pCmdPtr), MIN(32, pCmdPtr->Size)); + PRINTM(INFO, "Copying data from : (user)0x%p -> 0x%p(driver)\n", + req->ifr_data, pCmdPtr); + + pCmdNode->CmdWaitQWoken = FALSE; + pCmdPtr->Command = wlan_cpu_to_le16(pCmdPtr->Command); + pCmdPtr->Size = wlan_cpu_to_le16(pCmdPtr->Size); + QueueCmd(Adapter, pCmdNode, TRUE); + wake_up_interruptible(&priv->MainThread.waitQ); + + if (wait_option & HostCmd_OPTION_WAITFORRSP) { + /* Sleep until response is generated by FW */ + wait_event_interruptible(pCmdNode->cmdwait_q, + pCmdNode->CmdWaitQWoken); + } + + /* Copy the response back to user space */ + pCmdPtr = (HostCmd_DS_GEN *) tempResponseBuffer; + + if (copy_to_user(wrq->u.data.pointer, tempResponseBuffer, pCmdPtr->Size)) + PRINTM(INFO, "ERROR: copy_to_user failed!\n"); + wrq->u.data.length = pCmdPtr->Size; + kfree(tempResponseBuffer); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief wlan arpfilter ioctl handler + * + * @param dev A pointer to net_device structure + * @param req A pointer to ifreq structure + * @param cmd command + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_arpfilter_ioctl(struct net_device *dev, struct ifreq *req, int cmd) +{ + wlan_private *priv = dev->priv; + struct iwreq *wrq = (struct iwreq *) req; + wlan_adapter *Adapter = priv->adapter; + MrvlIEtypesHeader_t hdr; + + ENTER(); + + if ((wrq->u.data.pointer == NULL) + || (wrq->u.data.length < sizeof(MrvlIEtypesHeader_t)) + || (wrq->u.data.length > sizeof(Adapter->ArpFilter))) { + PRINTM(INFO, + "wlan_arpfilter_ioctl() corrupt data: pointer=%p, length=%d\n", + wrq->u.data.pointer, wrq->u.data.length); + return -EFAULT; + } + + if (copy_from_user(&hdr, wrq->u.data.pointer, sizeof(hdr))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + if (hdr.Len == 0) { + Adapter->ArpFilterSize = 0; + memset(Adapter->ArpFilter, 0, sizeof(Adapter->ArpFilter)); + } else { + Adapter->ArpFilterSize = wrq->u.data.length; + + PRINTM(INFO, "Copying data from : (user)0x%p -> 0x%p(driver)\n", + wrq->u.data.pointer, Adapter->ArpFilter); + if (copy_from_user(Adapter->ArpFilter, wrq->u.data.pointer, + Adapter->ArpFilterSize)) { + Adapter->ArpFilterSize = 0; + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + HEXDUMP("ArpFilter", Adapter->ArpFilter, Adapter->ArpFilterSize); + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Rx Info + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success + */ +static int +wlan_get_rxinfo(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data[2]; + ENTER(); + data[0] = Adapter->SNR[TYPE_RXPD][TYPE_NOAVG]; + data[1] = Adapter->RxPDRate; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 2)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 2; + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get SNR + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_snr(wlan_private * priv, struct iwreq *wrq) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + int data[4]; + + ENTER(); + + memset(data, 0, sizeof(data)); + if (wrq->u.data.length) { + if (copy_from_user + (data, wrq->u.data.pointer, + MIN(wrq->u.data.length, 4) * sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + } + if ((wrq->u.data.length == 0) || (data[0] == 0) || (data[0] == 1)) { + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RSSI, + 0, HostCmd_OPTION_WAITFORRSP, + 0, NULL); + + if (ret) { + LEAVE(); + return ret; + } + } + } + + if (wrq->u.data.length == 0) { + data[0] = Adapter->SNR[TYPE_BEACON][TYPE_NOAVG]; + data[1] = Adapter->SNR[TYPE_BEACON][TYPE_AVG]; + if ((jiffies - Adapter->RxPDAge) > HZ) //data expired after 1 second + data[2] = 0; + else + data[2] = Adapter->SNR[TYPE_RXPD][TYPE_NOAVG]; + data[3] = Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 4)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 4; + } else if (data[0] == 0) { + data[0] = Adapter->SNR[TYPE_BEACON][TYPE_NOAVG]; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + } else if (data[0] == 1) { + data[0] = Adapter->SNR[TYPE_BEACON][TYPE_AVG]; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + } else if (data[0] == 2) { + if ((jiffies - Adapter->RxPDAge) > HZ) //data expired after 1 second + data[0] = 0; + else + data[0] = Adapter->SNR[TYPE_RXPD][TYPE_NOAVG]; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + } else if (data[0] == 3) { + data[0] = Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + } else { + return -ENOTSUPP; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set SDIO PULL CTRL + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_sdio_pull_ctrl(wlan_private * priv, struct iwreq *wrq) +{ + int data[2]; + HostCmd_DS_SDIO_PULL_CTRL sdio_pull_ctrl; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + memset(&sdio_pull_ctrl, 0, sizeof(sdio_pull_ctrl)); + if (wrq->u.data.length > 0) { + if (copy_from_user(data, wrq->u.data.pointer, sizeof(data))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + PRINTM(INFO, "WLAN SET SDIO PULL CTRL: %d %d\n", data[0], data[1]); + sdio_pull_ctrl.Action = HostCmd_ACT_GEN_SET; + sdio_pull_ctrl.PullUp = data[0]; + sdio_pull_ctrl.PullDown = data[1]; + } else { + sdio_pull_ctrl.Action = HostCmd_ACT_GEN_GET; + } + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_SDIO_PULL_CTRL, + 0, HostCmd_OPTION_WAITFORRSP, + 0, (void *) &sdio_pull_ctrl); + data[0] = sdio_pull_ctrl.PullUp; + data[1] = sdio_pull_ctrl.PullDown; + wrq->u.data.length = 2; + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set scan time + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_scan_time(wlan_private * priv, struct iwreq *wrq) +{ + int data[3] = { 0, 0, 0 }; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wrq->u.data.length > 0 && wrq->u.data.length <= 3) { + if (copy_from_user + (data, wrq->u.data.pointer, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, + "WLAN SET Scan Time: Specific %d, Active %d, Passive %d\n", + data[0], data[1], data[2]); + if (data[0]) { + if (data[0] > MRVDRV_MAX_ACTIVE_SCAN_CHAN_TIME) { + PRINTM(MSG, + "Invalid parameter, max specific scan time is %d ms\n", + MRVDRV_MAX_ACTIVE_SCAN_CHAN_TIME); + return -EINVAL; + } + Adapter->SpecificScanTime = data[0]; + } + if (data[1]) { + if (data[1] > MRVDRV_MAX_ACTIVE_SCAN_CHAN_TIME) { + PRINTM(MSG, + "Invalid parameter, max active scan time is %d ms\n", + MRVDRV_MAX_ACTIVE_SCAN_CHAN_TIME); + return -EINVAL; + } + Adapter->ActiveScanTime = data[1]; + } + if (data[2]) { + if (data[2] > MRVDRV_MAX_PASSIVE_SCAN_CHAN_TIME) { + PRINTM(MSG, + "Invalid parameter, max passive scan time is %d ms\n", + MRVDRV_MAX_PASSIVE_SCAN_CHAN_TIME); + return -EINVAL; + } + Adapter->PassiveScanTime = data[2]; + } + } + + data[0] = Adapter->SpecificScanTime; + data[1] = Adapter->ActiveScanTime; + data[2] = Adapter->PassiveScanTime; + wrq->u.data.length = 3; + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set Adhoc beacon Interval + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_beacon_interval(wlan_private * priv, struct iwreq *wrq) +{ + int data[2]; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wrq->u.data.length > 0) { + if (copy_from_user(data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, "WLAN SET BEACON INTERVAL: %d\n", data[0]); + if ((data[0] > MRVDRV_MAX_BEACON_INTERVAL) || + (data[0] < MRVDRV_MIN_BEACON_INTERVAL)) + return -ENOTSUPP; + Adapter->BeaconPeriod = data[0]; + } + data[0] = Adapter->BeaconPeriod; + wrq->u.data.length = 1; + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + data[1] = Adapter->CurBssParams.BSSDescriptor.BeaconPeriod; + wrq->u.data.length = 2; + } + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set Adhoc ATIM Window + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_ATIM_Window(wlan_private * priv, struct iwreq *wrq) +{ + int data[2]; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wrq->u.data.length > 0) { + if (copy_from_user(data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, "WLAN SET ATIM WINDOW: %d\n", data[0]); + Adapter->AtimWindow = data[0]; + Adapter->AtimWindow = MIN(Adapter->AtimWindow, 50); + } + data[0] = Adapter->AtimWindow; + wrq->u.data.length = 1; + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + data[1] = Adapter->CurBssParams.BSSDescriptor.ATIMWindow; + wrq->u.data.length = 2; + } + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set data subscribe event + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_data_subscribe_event(wlan_private * priv, struct iwreq *wrq) +{ + int data[9]; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + memset(data, 0, sizeof(data)); + if (wrq->u.data.length > 9) + return -EFAULT; + if (wrq->u.data.length > 0) { + if (copy_from_user + (data, wrq->u.data.pointer, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + memset(&Adapter->subevent, 0, sizeof(Adapter->subevent)); + Adapter->subevent.EventsBitmap = (u16) data[0]; + Adapter->subevent.Rssi_low.value = (u8) data[1]; + Adapter->subevent.Rssi_low.Freq = (u8) data[2]; + Adapter->subevent.Snr_low.value = (u8) data[3]; + Adapter->subevent.Snr_low.Freq = (u8) data[4]; + Adapter->subevent.Rssi_high.value = (u8) data[5]; + Adapter->subevent.Rssi_high.Freq = (u8) data[6]; + Adapter->subevent.Snr_high.value = (u8) data[7]; + Adapter->subevent.Snr_high.Freq = (u8) data[8]; + } else { + data[0] = (int) Adapter->subevent.EventsBitmap; + data[1] = (int) Adapter->subevent.Rssi_low.value; + data[2] = (int) Adapter->subevent.Rssi_low.Freq; + data[3] = (int) Adapter->subevent.Snr_low.value; + data[4] = (int) Adapter->subevent.Snr_low.Freq; + data[5] = (int) Adapter->subevent.Rssi_high.value; + data[6] = (int) Adapter->subevent.Rssi_high.Freq; + data[7] = (int) Adapter->subevent.Snr_high.value; + data[8] = (int) Adapter->subevent.Snr_high.Freq; + } + wrq->u.data.length = 9; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(data))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get RSSI + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_rssi(wlan_private * priv, struct iwreq *wrq) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + int temp; + int data = 0; + int *val; + + ENTER(); + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + if ((data == 0) || (data == 1)) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RSSI, + 0, HostCmd_OPTION_WAITFORRSP, 0, NULL); + if (ret) { + LEAVE(); + return ret; + } + } + + switch (data) { + case 0: + + temp = CAL_RSSI(Adapter->SNR[TYPE_BEACON][TYPE_NOAVG], + Adapter->NF[TYPE_BEACON][TYPE_NOAVG]); + break; + case 1: + temp = CAL_RSSI(Adapter->SNR[TYPE_BEACON][TYPE_AVG], + Adapter->NF[TYPE_BEACON][TYPE_AVG]); + break; + case 2: + if ((jiffies - Adapter->RxPDAge) > HZ) //data expired after 1 second + temp = 0; + else + temp = CAL_RSSI(Adapter->SNR[TYPE_RXPD][TYPE_NOAVG], + Adapter->NF[TYPE_RXPD][TYPE_NOAVG]); + break; + case 3: + temp = CAL_RSSI(Adapter->SNR[TYPE_RXPD][TYPE_AVG] / AVG_SCALE, + Adapter->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE); + break; + default: + return -ENOTSUPP; + } + val = (int *) wrq->u.name; + *val = temp; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get NF + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_nf(wlan_private * priv, struct iwreq *wrq) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + int temp; + int data = 0; + int *val; + + ENTER(); + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + if ((data == 0) || (data == 1)) { + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RSSI, + 0, HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + LEAVE(); + return ret; + } + } + + switch (data) { + case 0: + temp = Adapter->NF[TYPE_BEACON][TYPE_NOAVG]; + break; + case 1: + temp = Adapter->NF[TYPE_BEACON][TYPE_AVG]; + break; + case 2: + if ((jiffies - Adapter->RxPDAge) > HZ) //data expired after 1 second + temp = 0; + else + temp = Adapter->NF[TYPE_RXPD][TYPE_NOAVG]; + break; + case 3: + temp = Adapter->NF[TYPE_RXPD][TYPE_AVG] / AVG_SCALE; + break; + default: + return -ENOTSUPP; + } + + temp = CAL_NF(temp); + + PRINTM(INFO, "***temp = %d\n", temp); + val = (int *) wrq->u.name; + *val = temp; + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Remove AES key + * + * @param priv A pointer to wlan_private structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_remove_aes(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + WLAN_802_11_KEY key; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (Adapter->InfrastructureMode != Wlan802_11IBSS || + Adapter->MediaConnectStatus == WlanMediaStateConnected) + return -EOPNOTSUPP; + + Adapter->AdhocAESEnabled = FALSE; + + memset(&key, 0, sizeof(WLAN_802_11_KEY)); + PRINTM(INFO, "WPA2: DISABLE AES_KEY\n"); + key.KeyLength = WPA_AES_KEY_LEN; + key.KeyIndex = 0x40000000; + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, + !(KEY_INFO_ENABLED), &key); + + LEAVE(); + + return ret; +} + +/** + * @brief Get Support Rates + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_getrate_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + WLAN_802_11_RATES rates; + int rate[sizeof(rates)]; + int i; + + ENTER(); + + memset(rates, 0, sizeof(rates)); + memset(rate, 0, sizeof(rate)); + wrq->u.data.length = get_active_data_rates(Adapter, rates); + if (wrq->u.data.length > sizeof(rates)) + wrq->u.data.length = sizeof(rates); + + for (i = 0; i < wrq->u.data.length; i++) { + rates[i] &= ~0x80; + rate[i] = rates[i]; + } + + if (copy_to_user + (wrq->u.data.pointer, rate, wrq->u.data.length * sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get TxRate + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_txrate_ioctl(wlan_private * priv, struct ifreq *req) +{ + wlan_adapter *Adapter = priv->adapter; + int *pdata; + struct iwreq *wrq = (struct iwreq *) req; + int ret = WLAN_STATUS_SUCCESS; + ENTER(); + Adapter->TxRate = 0; + PRINTM(INFO, "wlan_get_txrate_ioctl\n"); + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_TX_RATE_QUERY, + HostCmd_ACT_GET, HostCmd_OPTION_WAITFORRSP, + 0, NULL); + if (ret) { + LEAVE(); + return ret; + } + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->TxRate; + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get DTIM + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_dtim_ioctl(wlan_private * priv, struct ifreq *req) +{ + wlan_adapter *Adapter = priv->adapter; + struct iwreq *wrq = (struct iwreq *) req; + int *pdata; + int ret = WLAN_STATUS_FAILURE; + + ENTER(); + + /* The DTIM value is valid only in connected state */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + Adapter->Dtim = 0; + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SNMP_MIB, + HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, + OID_802_11_DTIM, NULL); + if (!ret) { + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->Dtim; + } + } + + LEAVE(); + return ret; +} + +/** + * @brief Get Adhoc Status + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_get_adhoc_status_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + char status[64]; + wlan_adapter *Adapter = priv->adapter; + + memset(status, 0, sizeof(status)); + + switch (Adapter->InfrastructureMode) { + case Wlan802_11IBSS: + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + if (Adapter->AdhocCreate == TRUE) + strcpy(status, "AdhocStarted"); + else + strcpy(status, "AdhocJoined"); + } else { + strcpy(status, "AdhocIdle"); + } + break; + case Wlan802_11Infrastructure: + strcpy(status, "InfraMode"); + break; + default: + strcpy(status, "AutoUnknownMode"); + break; + } + + PRINTM(INFO, "Status = %s\n", status); + wrq->u.data.length = strlen(status) + 1; + + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, &status, wrq->u.data.length)) + return -EFAULT; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Driver Version + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_version_ioctl(wlan_private * priv, struct ifreq *req) +{ + int len; + char buf[128]; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + get_version(priv->adapter, buf, sizeof(buf) - 1); + + len = strlen(buf); + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, buf, len)) { + PRINTM(INFO, "CopyToUser failed\n"); + return -EFAULT; + } + wrq->u.data.length = len; + } + + PRINTM(INFO, "wlan version: %s\n", buf); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Driver and FW version + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_verext_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + HostCmd_DS_VERSION_EXT versionExtCmd; + int len; + + ENTER(); + + memset(&versionExtCmd, 0x00, sizeof(versionExtCmd)); + + if (wrq->u.data.flags == 0) { + //from iwpriv subcmd + versionExtCmd.versionStrSel = + *((int *) (wrq->u.name + SUBCMD_OFFSET)); + } else { + if (copy_from_user(&versionExtCmd.versionStrSel, + wrq->u.data.pointer, + sizeof(versionExtCmd.versionStrSel))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + } + + PrepareAndSendCommand(priv, + HostCmd_CMD_VERSION_EXT, 0, + HostCmd_OPTION_WAITFORRSP, 0, &versionExtCmd); + + len = strlen(versionExtCmd.versionStr) + 1; + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, versionExtCmd.versionStr, len)) { + PRINTM(INFO, "CopyToUser failed\n"); + return -EFAULT; + } + wrq->u.data.length = len; + } + + PRINTM(INFO, "Version: %s\n", versionExtCmd.versionStr); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Read/Write adapter registers + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_regrdwr_ioctl(wlan_private * priv, struct ifreq *req) +{ + wlan_ioctl_regrdwr regrdwr; + wlan_offset_value offval; + u8 *pRdeeprom; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (copy_from_user(®rdwr, req->ifr_data, sizeof(regrdwr))) { + PRINTM(INFO, + "copy of regrdwr for wlan_regrdwr_ioctl from user failed \n"); + LEAVE(); + return -EFAULT; + } + + if (regrdwr.WhichReg == REG_EEPROM) { + PRINTM(INFO, "Inside RDEEPROM\n"); + pRdeeprom = + (char *) kmalloc((regrdwr.NOB + sizeof(regrdwr)), GFP_KERNEL); + if (!pRdeeprom) { + PRINTM(INFO, "allocate memory for EEPROM read failed\n"); + return -ENOMEM; + } + memcpy(pRdeeprom, ®rdwr, sizeof(regrdwr)); + PRINTM(INFO, "Action: %d, Offset: %x, NOB: %02x\n", + regrdwr.Action, regrdwr.Offset, regrdwr.NOB); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_EEPROM_ACCESS, + regrdwr.Action, HostCmd_OPTION_WAITFORRSP, + 0, pRdeeprom); + + /* + * Return the result back to the user + */ + if (!ret && regrdwr.Action == HostCmd_ACT_GEN_READ) { + if (copy_to_user + (req->ifr_data, pRdeeprom, sizeof(regrdwr) + regrdwr.NOB)) { + PRINTM(INFO, + "copy of regrdwr for wlan_regrdwr_ioctl to user failed \n"); + ret = -EFAULT; + } + } + + kfree(pRdeeprom); + + LEAVE(); + return ret; + } + + offval.offset = regrdwr.Offset; + offval.value = (regrdwr.Action) ? regrdwr.Value : 0x00; + + PRINTM(INFO, "RegAccess: %02x Action:%d " + "Offset: %04x Value: %04x\n", + regrdwr.WhichReg, regrdwr.Action, offval.offset, offval.value); + + /* + * regrdwr.WhichReg should contain the command that + * corresponds to which register access is to be + * performed HostCmd_CMD_MAC_REG_ACCESS 0x0019 + * HostCmd_CMD_BBP_REG_ACCESS 0x001a + * HostCmd_CMD_RF_REG_ACCESS 0x001b + */ + if (regrdwr.WhichReg == REG_MAC || + regrdwr.WhichReg == REG_BBP || regrdwr.WhichReg == REG_RF) { + ret = PrepareAndSendCommand(priv, regrdwr.WhichReg, + regrdwr.Action, HostCmd_OPTION_WAITFORRSP, + 0, &offval); + } else + ret = -EINVAL; + + /* + * Return the result back to the user + */ + if (!ret && regrdwr.Action == HostCmd_ACT_GEN_READ) { + regrdwr.Value = offval.value; + if (copy_to_user(req->ifr_data, ®rdwr, sizeof(regrdwr))) { + PRINTM(INFO, + "copy of regrdwr for wlan_regrdwr_ioctl to user failed \n"); + ret = -EFAULT; + } + } + + LEAVE(); + return ret; +} + +/** + * @brief Cmd52 read/write register + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_cmd52rdwr_ioctl(wlan_private * priv, struct ifreq *req) +{ + u8 buf[7]; + u8 rw, func, dat = 0xff; + u32 reg; + + ENTER(); + + if (copy_from_user(buf, req->ifr_data, sizeof(buf))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + rw = buf[0]; + func = buf[1]; + reg = buf[5]; + reg = (reg << 8) + buf[4]; + reg = (reg << 8) + buf[3]; + reg = (reg << 8) + buf[2]; + + if (rw != 0) + dat = buf[6]; + + PRINTM(INFO, "rw=%d func=%d reg=0x%08X dat=0x%02X\n", rw, func, reg, dat); + + if (rw == 0) { + if (sbi_read_ioreg(priv, func, reg, &dat) < 0) { + PRINTM(INFO, "sdio_read_ioreg: reading register 0x%X failed\n", + reg); + dat = 0xff; + } + } else { + if (sbi_write_ioreg(priv, func, reg, dat) < 0) { + PRINTM(INFO, "sdio_read_ioreg: writing register 0x%X failed\n", + reg); + dat = 0xff; + } + } + if (copy_to_user(req->ifr_data, &dat, sizeof(dat))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Cmd53 read/write register + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_cmd53rdwr_ioctl(wlan_private * priv, struct ifreq *req) +{ + return -EINVAL; +} + +/** + * @brief Convert ascii string to Hex integer + * + * @param d A pointer to integer buf + * @param s A pointer to ascii string + * @param dlen the length o fascii string + * @return number of integer + */ +static int +ascii2hex(u8 * d, char *s, u32 dlen) +{ + int i; + u8 n; + + memset(d, 0x00, dlen); + + for (i = 0; i < dlen * 2; i++) { + if ((s[i] >= 48) && (s[i] <= 57)) + n = s[i] - 48; + else if ((s[i] >= 65) && (s[i] <= 70)) + n = s[i] - 55; + else if ((s[i] >= 97) && (s[i] <= 102)) + n = s[i] - 87; + else + break; + if ((i % 2) == 0) + n = n * 16; + d[i / 2] += n; + } + + return i; +} + +/** + * @brief Set adhoc aes key + * + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_setadhocaes_ioctl(wlan_private * priv, struct ifreq *req) +{ + u8 key_ascii[32]; + u8 key_hex[16]; + int ret = 0; + struct iwreq *wrq = (struct iwreq *) req; + wlan_adapter *Adapter = priv->adapter; + + WLAN_802_11_KEY key; + + ENTER(); + + if (Adapter->InfrastructureMode != Wlan802_11IBSS) + return -EOPNOTSUPP; + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) + return -EOPNOTSUPP; + + if (copy_from_user(key_ascii, wrq->u.data.pointer, sizeof(key_ascii))) { + PRINTM(INFO, "wlan_setadhocaes_ioctl copy from user failed \n"); + LEAVE(); + return -EFAULT; + } + + Adapter->AdhocAESEnabled = TRUE; + ascii2hex(key_hex, key_ascii, sizeof(key_hex)); + + HEXDUMP("wlan_setadhocaes_ioctl", key_hex, sizeof(key_hex)); + + PRINTM(INFO, "WPA2: ENABLE AES_KEY\n"); + key.KeyLength = WPA_AES_KEY_LEN; + key.KeyIndex = 0x40000000; + memcpy(key.KeyMaterial, key_hex, key.KeyLength); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, + KEY_INFO_ENABLED, &key); + + LEAVE(); + return ret; +} + +/** + * @brief Get adhoc aes key + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_getadhocaes_ioctl(wlan_private * priv, struct ifreq *req) +{ + u8 *tmp; + u8 key_ascii[33]; + u8 key_hex[16]; + int i, ret = 0; + struct iwreq *wrq = (struct iwreq *) req; + wlan_adapter *Adapter = priv->adapter; + WLAN_802_11_KEY key; + + ENTER(); + + memset(key_hex, 0x00, sizeof(key_hex)); + + PRINTM(INFO, "WPA2: ENABLE AES_KEY\n"); + key.KeyLength = WPA_AES_KEY_LEN; + key.KeyIndex = 0x40000000; + memcpy(key.KeyMaterial, key_hex, key.KeyLength); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_KEY_MATERIAL, + HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, + KEY_INFO_ENABLED, &key); + + if (ret) { + LEAVE(); + return ret; + } + + memcpy(key_hex, Adapter->aeskey.KeyParamSet.Key, sizeof(key_hex)); + + HEXDUMP("wlan_getadhocaes_ioctl", key_hex, sizeof(key_hex)); + + wrq->u.data.length = sizeof(key_ascii) + 1; + + memset(key_ascii, 0x00, sizeof(key_ascii)); + tmp = key_ascii; + + for (i = 0; i < sizeof(key_hex); i++) + tmp += sprintf(tmp, "%02x", key_hex[i]); + + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, &key_ascii, sizeof(key_ascii))) { + PRINTM(INFO, "copy_to_user failed\n"); + return -EFAULT; + } + } + + LEAVE(); + return ret; +} + +/** + * @brief Set multiple dtim + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_multiple_dtim_ioctl(wlan_private * priv, struct ifreq *req) +{ + struct iwreq *wrq = (struct iwreq *) req; + u32 mdtim; + int idata; + int ret = -EINVAL; + + ENTER(); + + idata = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + mdtim = (u32) idata; + if (((mdtim >= MRVDRV_MIN_MULTIPLE_DTIM) && + (mdtim <= MRVDRV_MAX_MULTIPLE_DTIM)) + || (mdtim == MRVDRV_IGNORE_MULTIPLE_DTIM)) { + priv->adapter->MultipleDtim = mdtim; + ret = WLAN_STATUS_SUCCESS; + } + if (ret) + PRINTM(INFO, "Invalid parameter, MultipleDtim not changed.\n"); + + LEAVE(); + return ret; +} + +/** + * @brief Set authentication mode + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_setauthalg_ioctl(wlan_private * priv, struct ifreq *req) +{ + int alg; + struct iwreq *wrq = (struct iwreq *) req; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wrq->u.data.flags == 0) { + //from iwpriv subcmd + alg = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + } else { + //from wpa_supplicant subcmd + if (copy_from_user(&alg, wrq->u.data.pointer, sizeof(alg))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + } + + PRINTM(INFO, "auth alg is %#x\n", alg); + + switch (alg) { + case AUTH_ALG_SHARED_KEY: + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeShared; + break; + case AUTH_ALG_NETWORK_EAP: + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeNetworkEAP; + break; + case AUTH_ALG_OPEN_SYSTEM: + default: + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeOpen; + break; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set Encryption mode + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_setencryptionmode_ioctl(wlan_private * priv, struct ifreq *req) +{ + int mode; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + if (wrq->u.data.flags == 0) { + //from iwpriv subcmd + mode = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + } else { + //from wpa_supplicant subcmd + if (copy_from_user(&mode, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + } + PRINTM(INFO, "encryption mode is %#x\n", mode); + priv->adapter->SecInfo.EncryptionMode = mode; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Rx antenna + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_subcmd_getrxantenna_ioctl(wlan_private * priv, struct ifreq *req) +{ + int len; + char buf[8]; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + PRINTM(INFO, "WLAN_SUBCMD_GETRXANTENNA\n"); + len = GetRxAntenna(priv, buf); + + wrq->u.data.length = len; + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, &buf, len)) { + PRINTM(INFO, "CopyToUser failed\n"); + return -EFAULT; + } + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get Tx antenna + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_subcmd_gettxantenna_ioctl(wlan_private * priv, struct ifreq *req) +{ + int len; + char buf[8]; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + PRINTM(INFO, "WLAN_SUBCMD_GETTXANTENNA\n"); + len = GetTxAntenna(priv, buf); + + wrq->u.data.length = len; + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, &buf, len)) { + PRINTM(INFO, "CopyToUser failed\n"); + return -EFAULT; + } + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get the MAC TSF value from the firmware + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure containing buffer + * space to store a TSF value retrieved from the firmware + * + * @return 0 if successful; IOCTL error code otherwise + */ +static int +wlan_get_tsf_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + u64 tsfVal = 0; + int ret; + + ENTER(); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_GET_TSF, + 0, HostCmd_OPTION_WAITFORRSP, 0, &tsfVal); + + PRINTM(INFO, "IOCTL: Get TSF = 0x%016llx\n", tsfVal); + + if (ret != WLAN_STATUS_SUCCESS) { + PRINTM(INFO, "IOCTL: Get TSF; Command exec failed\n"); + ret = -EFAULT; + } else { + if (copy_to_user(wrq->u.data.pointer, + &tsfVal, + MIN(wrq->u.data.length, sizeof(tsfVal))) != 0) { + + PRINTM(INFO, "IOCTL: Get TSF; Copy to user failed\n"); + ret = -EFAULT; + } else { + ret = 0; + } + } + + LEAVE(); + + return ret; +} + +/** + * @brief Control WPS Session Enable/Disable + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_do_wps_session_ioctl(wlan_private * priv, struct iwreq *req) +{ + wlan_adapter *Adapter = priv->adapter; + char buf[8]; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + PRINTM(INFO, "WLAN_WPS_SESSION\n"); + + memset(buf, 0, sizeof(buf)); + if (copy_from_user(buf, wrq->u.data.pointer, + MIN(sizeof(buf) - 1, wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + if (buf[0] == 1) + Adapter->wps.SessionEnable = TRUE; + else + Adapter->wps.SessionEnable = FALSE; + + PRINTM(INFO, "Adapter->wps.SessionEnable = %d\n", + Adapter->wps.SessionEnable); + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set DeepSleep mode + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_deepsleep_ioctl(wlan_private * priv, struct ifreq *req) +{ + int ret = WLAN_STATUS_SUCCESS; + char status[128]; + struct iwreq *wrq = (struct iwreq *) req; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + PRINTM(MSG, "Cannot enter Deep Sleep mode in connected state.\n"); + return -EINVAL; + } + + if (*(char *) req->ifr_data == '0') { + PRINTM(INFO, "Exit Deep Sleep Mode.\n"); + sprintf(status, "setting to off "); + SetDeepSleep(priv, FALSE); + } else if (*(char *) req->ifr_data == '1') { + PRINTM(INFO, "Enter Deep Sleep Mode.\n"); + sprintf(status, "setting to on "); + SetDeepSleep(priv, TRUE); + } else if (*(char *) req->ifr_data == '2') { + PRINTM(INFO, "Get Deep Sleep Mode.\n"); + if (Adapter->IsDeepSleep == TRUE) { + sprintf(status, "on "); + } else { + sprintf(status, "off "); + } + } else { + PRINTM(INFO, "unknown option = %d\n", *(u8 *) req->ifr_data); + return -EINVAL; + } + + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, &status, strlen(status))) + return -EFAULT; + wrq->u.data.length = strlen(status); + } + + LEAVE(); + return ret; +} + +/** + * @brief Config hostsleep parameter + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_do_hostsleepcfg_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + char buf[32]; + int ret = WLAN_STATUS_SUCCESS; + int gpio, gap; + + memset(buf, 0, sizeof(buf)); + if (copy_from_user(buf, wrq->u.data.pointer, + MIN(sizeof(buf) - 1, wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + buf[sizeof(buf) - 1] = 0; + + if (sscanf(buf, "%x %x %x", &Adapter->HSCfg.conditions, &gpio, &gap) != 3) { + PRINTM(MSG, "Invalid parameters\n"); + return -EINVAL; + } + + if (Adapter->HSCfg.conditions != HOST_SLEEP_CFG_CANCEL) { + Adapter->HSCfg.gpio = (u8) gpio; + Adapter->HSCfg.gap = (u8) gap; + } + + PRINTM(INFO, + "hostsleepcfg: cond=%#x gpio=%#x gap=%#x PSState=%d HS_Activated=%d\n", + Adapter->HSCfg.conditions, Adapter->HSCfg.gpio, Adapter->HSCfg.gap, + Adapter->PSState, Adapter->HS_Activated); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_HOST_SLEEP_CFG, + 0, HostCmd_OPTION_WAITFORRSP, 0, + &Adapter->HSCfg); + + return ret; +} + +/** + * @brief Config Host Sleep parameters + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_hscfg_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data[3] = { -1, 0xff, 0xff }; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (wrq->u.data.length >= 1 && wrq->u.data.length <= 3) { + if (copy_from_user + (data, wrq->u.data.pointer, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + PRINTM(INFO, + "wlan_hscfg_ioctl: data[0]=%#08x, data[1]=%#02x, data[2]=%#02x\n", + data[0], data[1], data[2]); + } else { + PRINTM(MSG, "Invalid Argument\n"); + return -EINVAL; + } + + Adapter->HSCfg.conditions = data[0]; + if (Adapter->HSCfg.conditions != HOST_SLEEP_CFG_CANCEL) { + if (wrq->u.data.length == 2) { + Adapter->HSCfg.gpio = (u8) data[1]; + } else if (wrq->u.data.length == 3) { + Adapter->HSCfg.gpio = (u8) data[1]; + Adapter->HSCfg.gap = (u8) data[2]; + } + } + + PRINTM(INFO, + "hscfg: cond=%#x gpio=%#x gap=%#x PSState=%d HS_Activated=%d\n", + Adapter->HSCfg.conditions, Adapter->HSCfg.gpio, Adapter->HSCfg.gap, + Adapter->PSState, Adapter->HS_Activated); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_HOST_SLEEP_CFG, + 0, HostCmd_OPTION_WAITFORRSP, 0, + &Adapter->HSCfg); + + data[0] = Adapter->HSCfg.conditions; + data[1] = Adapter->HSCfg.gpio; + data[2] = Adapter->HSCfg.gap; + wrq->u.data.length = 3; + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + LEAVE(); + return ret; +} + +/** + * @brief Set Host Sleep parameters + * + * @param priv A pointer to wlan_private structure + * @param wreq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_hssetpara_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data[3] = { -1, 0xff, 0xff }; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (wrq->u.data.length >= 1 && wrq->u.data.length <= 3) { + if (copy_from_user + (data, wrq->u.data.pointer, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + PRINTM(INFO, + "wlan_hssetpara_ioctl: data[0]=%#08x, data[1]=%#02x, data[2]=%#02x\n", + data[0], data[1], data[2]); + } + + Adapter->HSCfg.conditions = data[0]; + if (Adapter->HSCfg.conditions != HOST_SLEEP_CFG_CANCEL) { + if (wrq->u.data.length == 2) { + Adapter->HSCfg.gpio = (u8) data[1]; + } else if (wrq->u.data.length == 3) { + Adapter->HSCfg.gpio = (u8) data[1]; + Adapter->HSCfg.gap = (u8) data[2]; + } + } + + PRINTM(INFO, + "hssetpara: cond=%#x gpio=%#x gap=%#x PSState=%d HS_Activated=%d\n", + Adapter->HSCfg.conditions, Adapter->HSCfg.gpio, Adapter->HSCfg.gap, + Adapter->PSState, Adapter->HS_Activated); + + data[0] = Adapter->HSCfg.conditions; + data[1] = Adapter->HSCfg.gpio; + data[2] = Adapter->HSCfg.gap; + wrq->u.data.length = 3; + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + LEAVE(); + return ret; +} + +/** + * @brief Get/Set Cal data ext + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_do_caldata_ext_ioctl(wlan_private * priv, struct ifreq *req) +{ + HostCmd_DS_802_11_CAL_DATA_EXT *pCalData = NULL; + int ret = WLAN_STATUS_SUCCESS; + u16 action; + + ENTER(); + + if (!(pCalData = kmalloc(MAX_SETGET_CONF_CMD_LEN, GFP_KERNEL))) { + PRINTM(INFO, "Allocate memory failed\n"); + ret = -ENOMEM; + goto calexit; + } + memset(pCalData, 0, MAX_SETGET_CONF_CMD_LEN); + + if (copy_from_user(pCalData, req->ifr_data + SKIP_CMDNUM, + MAX_SETGET_CONF_CMD_LEN)) { + PRINTM(INFO, "Copy from user failed\n"); + kfree(pCalData); + ret = -EFAULT; + goto calexit; + } + + action = (pCalData->Action == HostCmd_ACT_GEN_SET) ? + HostCmd_ACT_GEN_SET : HostCmd_ACT_GEN_GET; + + HEXDUMP("Cal data ext", (u8 *) pCalData, MAX_SETGET_CONF_CMD_LEN); + + PRINTM(INFO, "CalData Action = 0x%0X\n", action); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_CAL_DATA_EXT, + action, + HostCmd_OPTION_WAITFORRSP, 0, pCalData); + + if (!ret && action == HostCmd_ACT_GEN_GET) { + if (copy_to_user(req->ifr_data + SKIP_CMDNUM, pCalData, + MAX_SETGET_CONF_CMD_LEN)) { + PRINTM(INFO, "Copy to user failed\n"); + ret = -EFAULT; + } + } + + kfree(pCalData); + calexit: + LEAVE(); + return ret; +} + +/** + * @brief Get/Set sleep period + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_sleep_period(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + int data; + wlan_adapter *Adapter = priv->adapter; + HostCmd_DS_802_11_SLEEP_PERIOD sleeppd; + + ENTER(); + + if (wrq->u.data.length > 1) + return -ENOTSUPP; + + memset(&sleeppd, 0, sizeof(sleeppd)); + memset(&Adapter->sleep_period, 0, sizeof(SleepPeriod)); + + if (wrq->u.data.length == 0) { + sleeppd.Action = HostCmd_ACT_GEN_GET; + } else { + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + /* sleep period is 0 or 10~60 in milliseconds */ +#define MIN_SLEEP_PERIOD 10 +#define MAX_SLEEP_PERIOD 60 +#define SLEEP_PERIOD_RESERVED_FF 0xFF + if ((data <= MAX_SLEEP_PERIOD && data >= MIN_SLEEP_PERIOD) || + (data == 0) + || (data == SLEEP_PERIOD_RESERVED_FF) /* for UPSD certification tests */ + ) { + sleeppd.Action = HostCmd_ACT_GEN_SET; + sleeppd.Period = data; + } else + return -EINVAL; + } + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SLEEP_PERIOD, + 0, HostCmd_OPTION_WAITFORRSP, + 0, (void *) &sleeppd); + + data = (int) Adapter->sleep_period.period; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + + LEAVE(); + return ret; +} + +/** + * @brief Get/Set adapt rate + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_adapt_rateset(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + wlan_adapter *Adapter = priv->adapter; + int data[4]; + int rateindex; + + ENTER(); + memset(data, 0, sizeof(data)); + if (!wrq->u.data.length) { + PRINTM(INFO, "Get ADAPT RATE SET\n"); + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RATE_ADAPT_RATESET, + HostCmd_ACT_GEN_GET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + data[0] = Adapter->HWRateDropMode; + data[2] = Adapter->Threshold; + data[3] = Adapter->FinalRate; + wrq->u.data.length = 4; + data[1] = Adapter->RateBitmap; + if (copy_to_user + (wrq->u.data.pointer, data, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + } else { + PRINTM(INFO, "Set ADAPT RATE SET\n"); + if (wrq->u.data.length > 4) + return -EINVAL; + if (copy_from_user + (data, wrq->u.data.pointer, sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + if (data[0] > HW_SINGLE_RATE_DROP) + return -EINVAL; + Adapter->HWRateDropMode = data[0]; + Adapter->Threshold = data[2]; + Adapter->FinalRate = data[3]; + Adapter->RateBitmap = data[1]; + Adapter->Is_DataRate_Auto = Is_Rate_Auto(priv); + if (Adapter->Is_DataRate_Auto) + Adapter->DataRate = 0; + else { + rateindex = GetRateIndex(priv); + Adapter->DataRate = index_to_data_rate(rateindex); + } + PRINTM(INFO, "RateBitmap=%x,IsRateAuto=%d,DataRate=%d\n", + Adapter->RateBitmap, Adapter->Is_DataRate_Auto, + Adapter->DataRate); + ret = + PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RATE_ADAPT_RATESET, + HostCmd_ACT_GEN_SET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + } + + LEAVE(); + return ret; +} + +/** + * @brief Get/Set inactivity timeout + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_inactivity_timeout(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + int data = 0; + u16 timeout = 0; + + ENTER(); + if (wrq->u.data.length > 1) + return -ENOTSUPP; + + if (wrq->u.data.length == 0) { + /* Get */ + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_INACTIVITY_TIMEOUT, + HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, 0, &timeout); + data = timeout; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + } else { + /* Set */ + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + timeout = data; + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_INACTIVITY_TIMEOUT, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, 0, &timeout); + } + + wrq->u.data.length = 1; + + LEAVE(); + return ret; +} + +/** + * @brief Get LOG + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_do_getlog_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + char *buf = NULL; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + PRINTM(INFO, " GET STATS\n"); + + if (!(buf = kmalloc(GETLOG_BUFSIZE, GFP_KERNEL))) { + PRINTM(INFO, "kmalloc failed!\n"); + return -ENOMEM; + } + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_GET_LOG, + 0, HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (!ret && wrq->u.data.pointer) { + sprintf(buf, "\n" + "mcasttxframe %u\n" + "failed %u\n" + "retry %u\n" + "multiretry %u\n" + "framedup %u\n" + "rtssuccess %u\n" + "rtsfailure %u\n" + "ackfailure %u\n" + "rxfrag %u\n" + "mcastrxframe %u\n" + "fcserror %u\n" + "txframe %u\n", + Adapter->LogMsg.mcasttxframe, + Adapter->LogMsg.failed, + Adapter->LogMsg.retry, + Adapter->LogMsg.multiretry, + Adapter->LogMsg.framedup, + Adapter->LogMsg.rtssuccess, + Adapter->LogMsg.rtsfailure, + Adapter->LogMsg.ackfailure, + Adapter->LogMsg.rxfrag, + Adapter->LogMsg.mcastrxframe, + Adapter->LogMsg.fcserror, Adapter->LogMsg.txframe); + + wrq->u.data.length = strlen(buf) + 1; + if (copy_to_user(wrq->u.data.pointer, buf, wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + ret = -EFAULT; + } + } + + kfree(buf); + LEAVE(); + return ret; +} + +/** + * @brief config sleep parameters + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_sleep_params_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + wlan_adapter *Adapter = priv->adapter; + wlan_ioctl_sleep_params_config sp; + + ENTER(); + + memset(&sp, 0, sizeof(sp)); + + if (!wrq->u.data.pointer) + return -EFAULT; + if (copy_from_user(&sp, wrq->u.data.pointer, + MIN(sizeof(sp), wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + memcpy(&Adapter->sp, &sp.Error, sizeof(SleepParams)); + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_SLEEP_PARAMS, + sp.Action, HostCmd_OPTION_WAITFORRSP, + 0, NULL); + + if (!ret && !sp.Action) { + memcpy(&sp.Error, &Adapter->sp, sizeof(SleepParams)); + if (copy_to_user(wrq->u.data.pointer, &sp, sizeof(sp))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = sizeof(sp); + } + + LEAVE(); + return ret; +} + +/** + * @brief Read the CIS Table + * @param priv A pointer to wlan_private structure + * @param req A pointer to ifreq structure + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_do_getcis_ioctl(wlan_private * priv, struct ifreq *req) +{ + int ret = WLAN_STATUS_SUCCESS; + struct iwreq *wrq = (struct iwreq *) req; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wrq->u.data.pointer) { + if (copy_to_user(wrq->u.data.pointer, Adapter->CisInfoBuf, + Adapter->CisInfoLen)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = Adapter->CisInfoLen; + } + + LEAVE(); + return ret; +} + +/** + * @brief Set BCA timeshare + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_bca_timeshare_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + int ret; + wlan_adapter *Adapter = priv->adapter; + wlan_ioctl_bca_timeshare_config bca_ts; + + ENTER(); + + memset(&bca_ts, 0, sizeof(HostCmd_DS_802_11_BCA_TIMESHARE)); + + if (!wrq->u.data.pointer) + return -EFAULT; + if (copy_from_user(&bca_ts, wrq->u.data.pointer, + MIN(sizeof(bca_ts), wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, "TrafficType=%x TimeShareInterva=%x BTTime=%x\n", + bca_ts.TrafficType, bca_ts.TimeShareInterval, bca_ts.BTTime); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_BCA_CONFIG_TIMESHARE, + bca_ts.Action, HostCmd_OPTION_WAITFORRSP, + 0, &bca_ts); + + if (!ret && !bca_ts.Action) { + if (copy_to_user(wrq->u.data.pointer, &Adapter->bca_ts, + sizeof(bca_ts))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = sizeof(HostCmd_DS_802_11_BCA_TIMESHARE); + } + + LEAVE(); + return ret; +} + +/** + * @brief Set scan type + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_scan_type_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + u8 buf[12]; + u8 *option[] = { "active", "passive", "get", }; + int i, max_options = (sizeof(option) / sizeof(option[0])); + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (wlan_get_state_11d(priv) == ENABLE_11D) { + PRINTM(INFO, "11D: Cannot set scantype when 11D enabled\n"); + return -EFAULT; + } + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, wrq->u.data.pointer, MIN(sizeof(buf), + wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, "Scan Type Option = %s\n", buf); + + buf[sizeof(buf) - 1] = '\0'; + + for (i = 0; i < max_options; i++) { + if (!strcmp(buf, option[i])) + break; + } + + switch (i) { + case 0: + Adapter->ScanType = HostCmd_SCAN_TYPE_ACTIVE; + + break; + case 1: + Adapter->ScanType = HostCmd_SCAN_TYPE_PASSIVE; + break; + case 2: + wrq->u.data.length = strlen(option[Adapter->ScanType]) + 1; + + if (copy_to_user(wrq->u.data.pointer, + option[Adapter->ScanType], wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + ret = -EFAULT; + } + + break; + default: + PRINTM(INFO, "Invalid Scan Type Ioctl Option\n"); + ret = -EINVAL; + break; + } + + LEAVE(); + return ret; +} + +/** + * @brief Set scan mode + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_scan_mode_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + u8 buf[12]; + u8 *option[] = { "bss", "ibss", "any", "get" }; + int i, max_options = (sizeof(option) / sizeof(option[0])); + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, wrq->u.data.pointer, MIN(sizeof(buf), + wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, "Scan Mode Option = %s\n", buf); + + buf[sizeof(buf) - 1] = '\0'; + + for (i = 0; i < max_options; i++) { + if (!strcmp(buf, option[i])) + break; + } + + switch (i) { + + case 0: + Adapter->ScanMode = HostCmd_BSS_TYPE_BSS; + break; + case 1: + Adapter->ScanMode = HostCmd_BSS_TYPE_IBSS; + break; + case 2: + Adapter->ScanMode = HostCmd_BSS_TYPE_ANY; + break; + case 3: + + wrq->u.data.length = strlen(option[Adapter->ScanMode - 1]) + 1; + + PRINTM(INFO, "Get Scan Mode Option = %s\n", + option[Adapter->ScanMode - 1]); + + PRINTM(INFO, "Scan Mode Length %d\n", wrq->u.data.length); + + if (copy_to_user(wrq->u.data.pointer, + option[Adapter->ScanMode - 1], wrq->u.data.length)) { + PRINTM(INFO, "Copy to user failed\n"); + ret = -EFAULT; + } + PRINTM(INFO, "GET Scan Type Option after copy = %s\n", + (char *) wrq->u.data.pointer); + + break; + + default: + PRINTM(INFO, "Invalid Scan Mode Ioctl Option\n"); + ret = -EINVAL; + break; + } + + LEAVE(); + return ret; +} + +/** + * @brief Get/Set Adhoc G Rate + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_do_set_grate_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data, data1; + int *val; + + ENTER(); + + data1 = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + switch (data1) { + case 0: + Adapter->adhoc_grate_enabled = FALSE; + break; + case 1: + Adapter->adhoc_grate_enabled = TRUE; + break; + case 2: + break; + default: + return -EINVAL; + } + data = Adapter->adhoc_grate_enabled; + val = (int *) wrq->u.name; + *val = data; + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get/Set Firmware wakeup method + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_cmd_fw_wakeup_method(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + u16 action; + u16 method; + int ret; + int data; + + ENTER(); + + if (wrq->u.data.length == 0 || !wrq->u.data.pointer) { + action = HostCmd_ACT_GET; + method = Adapter->fwWakeupMethod; + } else { + action = HostCmd_ACT_SET; + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + switch (data) { + case 0: + method = WAKEUP_FW_UNCHANGED; + break; + case 1: + method = WAKEUP_FW_THRU_INTERFACE; + break; + case 2: + method = WAKEUP_FW_THRU_GPIO; + break; + default: + return -EINVAL; + } + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_FW_WAKE_METHOD, action, + HostCmd_OPTION_WAITFORRSP, 0, &method); + + if (action == HostCmd_ACT_GET) { + method = Adapter->fwWakeupMethod; + if (copy_to_user(wrq->u.data.pointer, &method, sizeof(method))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + } + + LEAVE(); + return ret; +} + +/** + * @brief Get/Set Auto Deep Sleep mode + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to user data + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +static int +wlan_auto_deep_sleep(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + int data; + + ENTER(); + + if (wrq->u.data.length > 0 && wrq->u.data.pointer) { + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + switch (data) { + case 0: + if (Adapter->IsAutoDeepSleepEnabled) { + Adapter->IsAutoDeepSleepEnabled = FALSE; + /* Try to exit DS if auto DS disabled */ + SetDeepSleep(priv, FALSE); + } + break; + case 1: + if (!Adapter->IsAutoDeepSleepEnabled) { + Adapter->IsAutoDeepSleepEnabled = TRUE; + /* Wakeup main thread to enter DS if auto DS enabled */ + wake_up_interruptible(&priv->MainThread.waitQ); + } + break; + default: + return -EINVAL; + } + } + + data = Adapter->IsAutoDeepSleepEnabled; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(data))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get the CFP table based on the region code + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_get_cfp_table_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + pwlan_ioctl_cfp_table ioctl_cfp; + CHANNEL_FREQ_POWER *cfp; + int cfp_no; + int regioncode; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (wrq->u.data.length == 0 || !wrq->u.data.pointer) { + ret = -EINVAL; + goto cfpexit; + } + + ioctl_cfp = (pwlan_ioctl_cfp_table) wrq->u.data.pointer; + + if (copy_from_user(®ioncode, &ioctl_cfp->region, sizeof(int))) { + PRINTM(INFO, "Get CFP table: copy from user failed\n"); + ret = -EFAULT; + goto cfpexit; + } + + if (!regioncode) + regioncode = Adapter->RegionCode; + + cfp = + wlan_get_region_cfp_table((u8) regioncode, BAND_G | BAND_B, &cfp_no); + + if (cfp == NULL) { + PRINTM(MSG, "No related CFP table found, region code = 0x%x\n", + regioncode); + ret = -EFAULT; + goto cfpexit; + } + + if (copy_to_user(&ioctl_cfp->cfp_no, &cfp_no, sizeof(int))) { + PRINTM(INFO, "Get CFP table: copy to user failed\n"); + ret = -EFAULT; + goto cfpexit; + } + + if (copy_to_user + (ioctl_cfp->cfp, cfp, sizeof(CHANNEL_FREQ_POWER) * cfp_no)) { + PRINTM(INFO, "Get CFP table: copy to user failed\n"); + ret = -EFAULT; + goto cfpexit; + } + + wrq->u.data.length = + sizeof(int) * 2 + sizeof(CHANNEL_FREQ_POWER) * cfp_no; + + cfpexit: + LEAVE(); + return ret; +} + +/** + * @brief Retrieve transmit packet statistics from the firmware + * + * @param priv A pointer to wlan_private structure + * @param wrq A pointer to iwreq structure + * + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_tx_pkt_stats_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + HostCmd_DS_TX_PKT_STATS txPktStats; + int ret; + + ENTER(); + + if (wrq->u.data.length == 0 || !wrq->u.data.pointer) { + LEAVE(); + return -EINVAL; + } + + if (wrq->u.data.length < sizeof(txPktStats)) { + LEAVE(); + return -E2BIG; + } + + memset(&txPktStats, 0x00, sizeof(txPktStats)); + + if ((ret = PrepareAndSendCommand(priv, + HostCmd_CMD_TX_PKT_STATS, 0, + HostCmd_OPTION_WAITFORRSP, + 0, &txPktStats))) { + LEAVE(); + return ret; + } + + if (copy_to_user(wrq->u.data.pointer, + (u8 *) & txPktStats, sizeof(txPktStats))) { + PRINTM(INFO, "TxPktStats: copy to user failed\n"); + return -EFAULT; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/******************************************************** + Global Functions +********************************************************/ +/** + * @brief ioctl function - entry point + * + * @param dev A pointer to net_device structure + * @param req A pointer to ifreq structure + * @param cmd command + * @return WLAN_STATUS_SUCCESS--success, otherwise fail + */ +int +wlan_do_ioctl(struct net_device *dev, struct ifreq *req, int cmd) +{ + int subcmd = 0; + int idata = 0; + int *pdata; + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + struct iwreq *wrq = (struct iwreq *) req; + + ENTER(); + + if (Adapter->bHostSleepConfigured) { + BOOLEAN cmd_allowed = FALSE; + int count = sizeof(Commands_Allowed_In_HostSleep) + / sizeof(Commands_Allowed_In_HostSleep[0]); + + if (cmd == WLANHOSTSLEEPCFG) { + char buf[32]; + u32 cond; + + memset(buf, 0, sizeof(buf)); + if (copy_from_user(buf, wrq->u.data.pointer, + MIN(sizeof(buf) - 1, wrq->u.data.length))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + buf[sizeof(buf) - 1] = 0; + sscanf(buf, "%x ", &cond); + if (cond == HOST_SLEEP_CFG_CANCEL) { + cmd_allowed = TRUE; + if (Adapter->IsDeepSleep) { + SetDeepSleep(priv, FALSE); + } + } + } else if (cmd == WLAN_SET_GET_SIXTEEN_INT && + ((int) wrq->u.data.flags == WLANHSCFG)) { + u32 cond; + if (copy_from_user(&cond, wrq->u.data.pointer, sizeof(cond))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + if (cond == HOST_SLEEP_CFG_CANCEL) { + cmd_allowed = TRUE; + if (Adapter->IsDeepSleep) { + SetDeepSleep(priv, FALSE); + } + } + } else + if (Is_Command_Allowed_In_Sleep + (req, cmd, Commands_Allowed_In_HostSleep, count)) { + cmd_allowed = TRUE; + } + if (!cmd_allowed) { + PRINTM(MSG, "%s IOCTLS called when WLAN access is blocked\n", + __FUNCTION__); + return -EBUSY; + } + } + + if (!Adapter->IsAutoDeepSleepEnabled) { + if (Adapter->IsDeepSleep) { + int count = sizeof(Commands_Allowed_In_DeepSleep) + / sizeof(Commands_Allowed_In_DeepSleep[0]); + + if (!Is_Command_Allowed_In_Sleep + (req, cmd, Commands_Allowed_In_DeepSleep, count)) { + PRINTM(MSG, + "():%s IOCTLS called when station is" + " in DeepSleep\n", __FUNCTION__); + return -EBUSY; + } + } + } else if (cmd == WLANDEEPSLEEP) { + PRINTM(MSG, + "DeepSleep command is not allowed in AutoDeepSleep mode\n"); + return -EBUSY; + } + + PRINTM(INFO, "wlan_do_ioctl: ioctl cmd = 0x%x\n", cmd); + switch (cmd) { + case WLANEXTSCAN: + ret = wlan_extscan_ioctl(priv, req); + break; + case WLANHOSTCMD: + ret = wlan_hostcmd_ioctl(dev, req, cmd); + break; + case WLANARPFILTER: + ret = wlan_arpfilter_ioctl(dev, req, cmd); + break; + + case WLANCISDUMP: /* Read CIS Table */ + ret = wlan_do_getcis_ioctl(priv, req); + break; + + case WLANSCAN_TYPE: + PRINTM(INFO, "Scan Type Ioctl\n"); + ret = wlan_scan_type_ioctl(priv, wrq); + break; + +#ifdef MFG_CMD_SUPPORT + case WLANMANFCMD: + PRINTM(INFO, "Entering the Manufacturing ioctl SIOCCFMFG\n"); + ret = wlan_mfg_command(priv, wrq); + + PRINTM(INFO, "Manufacturing Ioctl %s\n", + (ret) ? "failed" : "success"); + break; +#endif + + case WLANREGRDWR: /* Register read write command */ + ret = wlan_regrdwr_ioctl(priv, req); + break; + + case WLANCMD52RDWR: /* CMD52 read/write command */ + ret = wlan_cmd52rdwr_ioctl(priv, req); + break; + + case WLANCMD53RDWR: /* CMD53 read/write command */ + ret = wlan_cmd53rdwr_ioctl(priv, req); + break; + + case SIOCSIWENCODE: /* set encoding token & mode for WPA */ + ret = wlan_set_encode(dev, NULL, &(wrq->u.data), wrq->u.data.pointer); + break; + case WLAN_SETNONE_GETNONE: /* set WPA mode on/off ioctl #20 */ + switch (wrq->u.data.flags) { + case WLANDEAUTH: + PRINTM(INFO, "Deauth\n"); + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure && + Adapter->MediaConnectStatus == WlanMediaStateConnected) { + SendDeauthentication(priv); + } else if (Adapter->InfrastructureMode == Wlan802_11IBSS && + Adapter->MediaConnectStatus == + WlanMediaStateConnected) { + StopAdhocNetwork(priv); + } + break; + + case WLANADHOCSTOP: + PRINTM(INFO, "Adhoc stop\n"); + ret = wlan_do_adhocstop_ioctl(priv); + break; + + case WLANRADIOON: + wlan_radio_ioctl(priv, RADIO_ON); + break; + + case WLANRADIOOFF: + ret = wlan_radio_ioctl(priv, RADIO_OFF); + break; + case WLANREMOVEADHOCAES: + ret = wlan_remove_aes(priv); + break; +#ifdef REASSOCIATION + case WLANREASSOCIATIONAUTO: + reassociation_on(priv); + break; + case WLANREASSOCIATIONUSER: + reassociation_off(priv); + break; +#endif /* REASSOCIATION */ + case WLANWLANIDLEON: + wlanidle_on(priv); + break; + case WLANWLANIDLEOFF: + wlanidle_off(priv); + break; + } /* End of switch */ + break; + + case WLAN_SETWORDCHAR_GETNONE: + switch (wrq->u.data.flags) { + case WLANSETADHOCAES: + ret = wlan_setadhocaes_ioctl(priv, req); + break; + } + break; + + case WLAN_SETONEINT_GETWORDCHAR: + switch (wrq->u.data.flags) { + case WLANGETADHOCAES: + ret = wlan_getadhocaes_ioctl(priv, req); + break; + case WLANVERSION: /* Get driver version */ + ret = wlan_version_ioctl(priv, req); + break; + case WLANVEREXT: + ret = wlan_verext_ioctl(priv, wrq); + break; + } + break; + + case WLANSETWPAIE: + ret = wlan_set_wpa_ie_ioctl(priv, req); + break; + case WLAN_SETINT_GETINT: + /* The first 4 bytes of req->ifr_data is sub-ioctl number + * after 4 bytes sits the payload. + */ + subcmd = (int) req->ifr_data; //from iwpriv subcmd + switch (subcmd) { + case WLANNF: + ret = wlan_get_nf(priv, wrq); + break; + case WLANRSSI: + ret = wlan_get_rssi(priv, wrq); + break; + case WLANBGSCAN: + { + int data, data1; + int *val; + data1 = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + switch (data1) { + case CMD_DISABLED: + PRINTM(INFO, "Background scan is set to disable\n"); + ret = wlan_bg_scan_enable(priv, FALSE); + val = (int *) wrq->u.name; + *val = data1; + break; + case CMD_ENABLED: + PRINTM(INFO, "Background scan is set to enable\n"); + ret = wlan_bg_scan_enable(priv, TRUE); + val = (int *) wrq->u.name; + *val = data1; + break; + case CMD_GET: + data = (Adapter->bgScanConfig->Enable == TRUE) ? + CMD_ENABLED : CMD_DISABLED; + val = (int *) wrq->u.name; + *val = data; + break; + default: + ret = -EINVAL; + PRINTM(INFO, "Background scan: wrong parameter\n"); + break; + } + } + break; + case WLANENABLE11D: + ret = wlan_cmd_enable_11d(priv, wrq); + break; + case WLANADHOCGRATE: + ret = wlan_do_set_grate_ioctl(priv, wrq); + break; + case WLANSDIOCLOCK: + { + int data; + int *val; + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + switch (data) { + case CMD_DISABLED: + PRINTM(INFO, "SDIO clock is turned off\n"); + ret = sbi_set_bus_clock(priv, FALSE); + break; + case CMD_ENABLED: + PRINTM(INFO, "SDIO clock is turned on\n"); + ret = sbi_set_bus_clock(priv, TRUE); + break; + case CMD_GET: /* need an API in sdio.c to get STRPCL */ + default: + ret = -EINVAL; + PRINTM(INFO, "sdioclock: wrong parameter\n"); + break; + } + val = (int *) wrq->u.name; + *val = data; + } + break; + case WLANWMM_ENABLE: + ret = wlan_wmm_enable_ioctl(priv, wrq); + break; + case WLANNULLGEN: + ret = wlan_null_pkg_gen(priv, wrq); + /* enable/disable null pkg generation */ + break; + case WLANADHOCCSET: + ret = wlan_set_coalescing_ioctl(priv, wrq); + break; + case WLAN_ADHOC_G_PROT: + ret = wlan_adhoc_g_protection(priv, wrq); + break; + + } + break; + + case WLAN_SETONEINT_GETONEINT: + switch (wrq->u.data.flags) { + + case WLAN_WMM_QOSINFO: + { + int data; + if (wrq->u.data.length == 1) { + if (copy_from_user + (&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + Adapter->wmm.qosinfo = (u8) data; + } else { + data = (int) Adapter->wmm.qosinfo; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + wrq->u.data.length = 1; + } + } + break; + case WLAN_LISTENINTRVL: + if (!wrq->u.data.length) { + int data; + PRINTM(INFO, "Get LocalListenInterval Value\n"); +#define GET_ONE_INT 1 + data = Adapter->LocalListenInterval; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + wrq->u.data.length = GET_ONE_INT; + } else { + int data; + if (copy_from_user(&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + PRINTM(INFO, "Set LocalListenInterval = %d\n", data); +#define MAX_U16_VAL 65535 + if (data > MAX_U16_VAL) { + PRINTM(INFO, "Exceeds U16 value\n"); + return -EINVAL; + } + Adapter->LocalListenInterval = data; + } + break; + case WLAN_FW_WAKEUP_METHOD: + ret = wlan_cmd_fw_wakeup_method(priv, wrq); + break; + case WLAN_NULLPKTINTERVAL: + ret = wlan_null_pkt_interval(priv, wrq); + break; + case WLAN_BCN_MISS_TIMEOUT: + ret = wlan_bcn_miss_timeout(priv, wrq); + break; + case WLAN_ADHOC_AWAKE_PERIOD: + ret = wlan_adhoc_awake_period(priv, wrq); + break; + case WLAN_LDO: + ret = wlan_ldo_config(priv, wrq); + break; + case WLAN_SDIO_MODE: + ret = wlan_sdio_mode(priv, wrq); + break; + case WLAN_RTS_CTS_CTRL: + ret = wlan_rts_cts_ctrl(priv, wrq); + break; + case WLAN_AUTODEEPSLEEP: + ret = wlan_auto_deep_sleep(priv, wrq); + break; + case WLAN_WAKEUP_MT: + if (wrq->u.data.length > 0) + Adapter->IntCounter++; + wake_up_interruptible(&priv->MainThread.waitQ); + break; + default: + ret = -EOPNOTSUPP; + break; + } + break; + + case WLAN_SETONEINT_GETNONE: + /* The first 4 bytes of req->ifr_data is sub-ioctl number + * after 4 bytes sits the payload. + */ + subcmd = wrq->u.data.flags; //from wpa_supplicant subcmd + + if (!subcmd) + subcmd = (int) req->ifr_data; //from iwpriv subcmd + + idata = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + + switch (subcmd) { + case WLAN_SUBCMD_SETRXANTENNA: /* SETRXANTENNA */ + ret = SetRxAntenna(priv, idata); + break; + case WLAN_SUBCMD_SETTXANTENNA: /* SETTXANTENNA */ + ret = SetTxAntenna(priv, idata); + break; + + case WLANSETBCNAVG: + if (idata == 0) + Adapter->bcn_avg_factor = DEFAULT_BCN_AVG_FACTOR; + else if (idata > MAX_BCN_AVG_FACTOR || idata < MIN_BCN_AVG_FACTOR) { + PRINTM(MSG, "The value '%u' is out of the range (0-%u).\n", + idata, MAX_BCN_AVG_FACTOR); + return -EINVAL; + } else + Adapter->bcn_avg_factor = idata; + break; + case WLANSETDATAAVG: + if (idata == 0) + Adapter->data_avg_factor = DEFAULT_DATA_AVG_FACTOR; + else if (idata > MAX_DATA_AVG_FACTOR + || idata < MIN_DATA_AVG_FACTOR) { + PRINTM(MSG, "The value '%u' is out of the range (0-%u).\n", + idata, MAX_DATA_AVG_FACTOR); + return -EINVAL; + } else + Adapter->data_avg_factor = idata; + memset(Adapter->rawSNR, 0x00, sizeof(Adapter->rawSNR)); + memset(Adapter->rawNF, 0x00, sizeof(Adapter->rawNF)); + Adapter->nextSNRNF = 0; + Adapter->numSNRNF = 0; + break; + case WLANASSOCIATE: + ret = wlan_associate_to_table_idx(priv, idata); + break; + + case WLANSETREGION: + ret = wlan_set_region(priv, (u16) idata); + break; + + case WLAN_SET_LISTEN_INTERVAL: + Adapter->ListenInterval = (u16) idata; + break; + + case WLAN_SET_MULTIPLE_DTIM: + ret = wlan_set_multiple_dtim_ioctl(priv, req); + break; + + case WLANSETAUTHALG: + ret = wlan_setauthalg_ioctl(priv, req); + break; + + case WLANSETENCRYPTIONMODE: + ret = wlan_setencryptionmode_ioctl(priv, req); + break; + + default: + ret = -EOPNOTSUPP; + break; + } + + break; + + case WLAN_SETNONE_GETTWELVE_CHAR: /* Get Antenna settings */ + /* + * We've not used IW_PRIV_TYPE_FIXED so sub-ioctl number is + * in flags of iwreq structure, otherwise it will be in + * mode member of iwreq structure. + */ + switch ((int) wrq->u.data.flags) { + case WLAN_SUBCMD_GETRXANTENNA: /* Get Rx Antenna */ + ret = wlan_subcmd_getrxantenna_ioctl(priv, req); + break; + + case WLAN_SUBCMD_GETTXANTENNA: /* Get Tx Antenna */ + ret = wlan_subcmd_gettxantenna_ioctl(priv, req); + break; + + case WLAN_GET_TSF: + ret = wlan_get_tsf_ioctl(priv, wrq); + break; + + case WLAN_WPS_SESSION: + ret = wlan_do_wps_session_ioctl(priv, wrq); + break; + } + break; + + case WLANDEEPSLEEP: + ret = wlan_deepsleep_ioctl(priv, req); + break; + + case WLANHOSTSLEEPCFG: + ret = wlan_do_hostsleepcfg_ioctl(priv, wrq); + break; + + case WLAN_SET64CHAR_GET64CHAR: + switch ((int) wrq->u.data.flags) { + + case WLANSLEEPPARAMS: + ret = wlan_sleep_params_ioctl(priv, wrq); + break; + + case WLAN_BCA_TIMESHARE: + ret = wlan_bca_timeshare_ioctl(priv, wrq); + break; + case WLANSCAN_MODE: + PRINTM(INFO, "Scan Mode Ioctl\n"); + ret = wlan_scan_mode_ioctl(priv, wrq); + break; + + case WLAN_GET_ADHOC_STATUS: + ret = wlan_get_adhoc_status_ioctl(priv, wrq); + break; + case WLAN_SET_GEN_IE: + ret = wlan_set_gen_ie_ioctl(priv, wrq); + break; + case WLAN_GET_GEN_IE: + ret = wlan_get_gen_ie_ioctl(priv, wrq); + break; + case WLAN_WMM_QUEUE_STATUS: + ret = wlan_wmm_queue_status_ioctl(priv, wrq); + break; + } + break; + + case WLAN_SETCONF_GETCONF: + PRINTM(INFO, "The WLAN_SETCONF_GETCONF=0x%x is %d\n", + WLAN_SETCONF_GETCONF, *(u8 *) req->ifr_data); + switch (*(u8 *) req->ifr_data) { + case CAL_DATA_EXT_CONFIG: + ret = wlan_do_caldata_ext_ioctl(priv, req); + break; + case BG_SCAN_CONFIG: + ret = wlan_do_bg_scan_config_ioctl(priv, req); + break; + } + break; + + case WLAN_SETNONE_GETONEINT: + switch ((int) req->ifr_data) { + case WLANGETBCNAVG: + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->bcn_avg_factor; + break; + + case WLANGETDATAAVG: + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->data_avg_factor; + break; + + case WLANGETREGION: + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->RegionCode; + break; + + case WLAN_GET_LISTEN_INTERVAL: + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->ListenInterval; + break; + + case WLAN_GET_MULTIPLE_DTIM: + pdata = (int *) wrq->u.name; + *pdata = (int) Adapter->MultipleDtim; + break; + case WLAN_GET_TX_RATE: + ret = wlan_get_txrate_ioctl(priv, req); + break; + case WLANGETDTIM: + ret = wlan_get_dtim_ioctl(priv, req); + break; + default: + ret = -EOPNOTSUPP; + + } + + break; + + case WLANGETLOG: + ret = wlan_do_getlog_ioctl(priv, wrq); + break; + + case WLAN_SET_GET_SIXTEEN_INT: + switch ((int) wrq->u.data.flags) { + case WLAN_TPCCFG: + { + int data[5]; + HostCmd_DS_802_11_TPC_CFG cfg; + memset(&cfg, 0, sizeof(cfg)); + if ((wrq->u.data.length > 1) && (wrq->u.data.length != 5)) + return WLAN_STATUS_FAILURE; + + if (wrq->u.data.length == 0) { + cfg.Action = HostCmd_ACT_GEN_GET; + } else { + if (copy_from_user(data, + wrq->u.data.pointer, + sizeof(int) * 5)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + cfg.Action = HostCmd_ACT_GEN_SET; + cfg.Enable = data[0]; + cfg.UseSNR = data[1]; +#define TPC_DATA_NO_CHANG 0x7f + if (wrq->u.data.length == 1) { + cfg.P0 = TPC_DATA_NO_CHANG; + cfg.P1 = TPC_DATA_NO_CHANG; + cfg.P2 = TPC_DATA_NO_CHANG; + } else { + cfg.P0 = data[2]; + cfg.P1 = data[3]; + cfg.P2 = data[4]; + } + } + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_TPC_CFG, 0, + HostCmd_OPTION_WAITFORRSP, 0, + (void *) &cfg); + + data[0] = cfg.Enable; + data[1] = cfg.UseSNR; + data[2] = cfg.P0; + data[3] = cfg.P1; + data[4] = cfg.P2; + if (copy_to_user(wrq->u.data.pointer, data, sizeof(int) * 5)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + wrq->u.data.length = 5; + } + break; + + case WLAN_SCANPROBES: + { + int data; + if (wrq->u.data.length > 0) { + if (copy_from_user + (&data, wrq->u.data.pointer, sizeof(int))) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + Adapter->ScanProbes = data; + } else { + data = Adapter->ScanProbes; + if (copy_to_user(wrq->u.data.pointer, &data, sizeof(int))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + } + wrq->u.data.length = 1; + } + break; + case WLAN_LED_GPIO_CTRL: + { + int i; + int data[MAX_LEDS * 2]; + HostCmd_DS_802_11_LED_CTRL ctrl; + MrvlIEtypes_LedGpio_t *gpio; + + gpio = (MrvlIEtypes_LedGpio_t *) & ctrl.LedGpio; + + if ((wrq->u.data.length > MAX_LEDS * 2) || + (wrq->u.data.length % 2) != 0) { + PRINTM(MSG, "invalid ledgpio parameters\n"); + return -EINVAL; + } + + memset(&ctrl, 0, sizeof(ctrl)); + if (wrq->u.data.length == 0) { + ctrl.Action = HostCmd_ACT_GEN_GET; + } else { + if (copy_from_user(data, wrq->u.data.pointer, + sizeof(int) * wrq->u.data.length)) { + PRINTM(INFO, "Copy from user failed\n"); + return -EFAULT; + } + + ctrl.Action = HostCmd_ACT_GEN_SET; + ctrl.LedNums = 0; + gpio->Header.Type = TLV_TYPE_LED_GPIO; + gpio->Header.Len = wrq->u.data.length; + for (i = 0; i < wrq->u.data.length; i += 2) { + gpio->LedGpio[i / 2].LedNum = data[i]; + gpio->LedGpio[i / 2].GpioNum = data[i + 1]; + } + } + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_LED_CONTROL, 0, + HostCmd_OPTION_WAITFORRSP, + 0, (void *) &ctrl); + + for (i = 0; i < gpio->Header.Len; i += 2) { + data[i] = gpio->LedGpio[i / 2].LedNum; + data[i + 1] = gpio->LedGpio[i / 2].GpioNum; + } + if (copy_to_user(wrq->u.data.pointer, data, + sizeof(int) * gpio->Header.Len)) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + wrq->u.data.length = gpio->Header.Len; + } + break; + case WLAN_SLEEP_PERIOD: + ret = wlan_sleep_period(priv, wrq); + break; + case WLAN_ADAPT_RATESET: + ret = wlan_adapt_rateset(priv, wrq); + break; + case WLAN_INACTIVITY_TIMEOUT: + ret = wlan_inactivity_timeout(priv, wrq); + break; + case WLANSNR: + ret = wlan_get_snr(priv, wrq); + break; + case WLAN_GET_RATE: + ret = wlan_getrate_ioctl(priv, wrq); + break; + case WLAN_GET_RXINFO: + ret = wlan_get_rxinfo(priv, wrq); + break; + case WLAN_SET_ATIM_WINDOW: + ret = wlan_ATIM_Window(priv, wrq); + break; + case WLAN_BEACON_INTERVAL: + ret = wlan_beacon_interval(priv, wrq); + break; + case WLAN_SDIO_PULL_CTRL: + ret = wlan_sdio_pull_ctrl(priv, wrq); + break; + case WLAN_SCAN_TIME: + ret = wlan_scan_time(priv, wrq); + break; + case WLAN_DATA_SUBSCRIBE_EVENT: + ret = wlan_data_subscribe_event(priv, wrq); + break; + case WLAN_TXCONTROL: + ret = wlan_txcontrol(priv, wrq); + break; + case WLANHSCFG: + ret = wlan_hscfg_ioctl(priv, wrq); + break; + case WLANHSSETPARA: + ret = wlan_hssetpara_ioctl(priv, wrq); + break; +#ifdef DEBUG_LEVEL1 + case WLAN_DRV_DBG: + ret = wlan_drv_dbg(priv, wrq); + break; +#endif + } + break; + + case WLAN_SET_GET_2K: + switch ((int) wrq->u.data.flags) { + case WLAN_SET_USER_SCAN: + ret = wlan_set_user_scan_ioctl(priv, wrq); + break; + case WLAN_GET_SCAN_TABLE: + ret = wlan_get_scan_table_ioctl(priv, wrq); + break; + case WLAN_SET_MRVL_TLV: + ret = wlan_set_mrvl_tlv_ioctl(priv, wrq); + break; + case WLAN_GET_ASSOC_RSP: + ret = wlan_get_assoc_rsp_ioctl(priv, wrq); + break; + case WLAN_ADDTS_REQ: + ret = wlan_wmm_addts_req_ioctl(priv, wrq); + break; + case WLAN_DELTS_REQ: + ret = wlan_wmm_delts_req_ioctl(priv, wrq); + break; + case WLAN_QUEUE_CONFIG: + ret = wlan_wmm_queue_config_ioctl(priv, wrq); + break; + case WLAN_QUEUE_STATS: + ret = wlan_wmm_queue_stats_ioctl(priv, wrq); + break; + case WLAN_TX_PKT_STATS: + ret = wlan_tx_pkt_stats_ioctl(priv, wrq); + break; + case WLAN_GET_CFP_TABLE: + ret = wlan_get_cfp_table_ioctl(priv, wrq); + break; + default: + ret = -EOPNOTSUPP; + } + break; + + default: + ret = -EINVAL; + break; + } + LEAVE(); + return ret; +} + +/** + * @brief Get wireless statistics + * + * NOTE: If PrepareAndSendCommand() with wait option is issued + * in this function, a kernel dump (scheduling while atomic) + * issue may happen on some versions of kernels. + * + * @param dev A pointer to net_device structure + * @return A pointer to iw_statistics buf + */ +struct iw_statistics * +wlan_get_wireless_stats(struct net_device *dev) +{ + enum { + POOR = 30, + FAIR = 60, + GOOD = 80, + VERY_GOOD = 90, + EXCELLENT = 95, + PERFECT = 100 + }; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + u8 rssi; + u32 rssi_qual; + u32 tx_qual; + u32 quality = 0; + u32 tx_retries; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return NULL; + } + + priv->wstats.status = Adapter->InfrastructureMode; + priv->wstats.discard.retries = priv->stats.tx_errors; + + priv->wstats.qual.level = + CAL_RSSI(Adapter->SNR[TYPE_BEACON][TYPE_NOAVG], + Adapter->NF[TYPE_BEACON][TYPE_NOAVG]); + priv->wstats.qual.noise = CAL_NF(Adapter->NF[TYPE_BEACON][TYPE_NOAVG]); + if (Adapter->NF[TYPE_BEACON][TYPE_NOAVG] == 0 + && Adapter->MediaConnectStatus == WlanMediaStateConnected) + priv->wstats.qual.noise = MRVDRV_NF_DEFAULT_SCAN_VALUE; + else + priv->wstats.qual.noise = + CAL_NF(Adapter->NF[TYPE_BEACON][TYPE_NOAVG]); + + rssi = priv->wstats.qual.level - priv->wstats.qual.noise; + if (rssi < 15) + rssi_qual = rssi * POOR / 10; + else if (rssi < 20) + rssi_qual = (rssi - 15) * (FAIR - POOR) / 5 + POOR; + else if (rssi < 30) + rssi_qual = (rssi - 20) * (GOOD - FAIR) / 5 + FAIR; + else if (rssi < 40) + rssi_qual = (rssi - 30) * (VERY_GOOD - GOOD) / 10 + GOOD; + else + rssi_qual = (rssi - 40) * (PERFECT - VERY_GOOD) / 10 + VERY_GOOD; + quality = rssi_qual; + + tx_retries = Adapter->LogMsg.retry; + + if (tx_retries > 75) + tx_qual = (90 - tx_retries) * POOR / 15; + else if (tx_retries > 70) + tx_qual = (75 - tx_retries) * (FAIR - POOR) / 5 + POOR; + else if (tx_retries > 65) + tx_qual = (70 - tx_retries) * (GOOD - FAIR) / 5 + FAIR; + else if (tx_retries > 50) + tx_qual = (65 - tx_retries) * (VERY_GOOD - GOOD) / 15 + GOOD; + else + tx_qual = (50 - tx_retries) * (PERFECT - VERY_GOOD) / 50 + VERY_GOOD; + quality = min(quality, tx_qual); + + priv->wstats.qual.qual = min_t(u8, quality, 100); + priv->wstats.qual.updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM; + + PRINTM(INFO, "Signal Level = %#x\n", priv->wstats.qual.level); + PRINTM(INFO, "Noise = %#x\n", priv->wstats.qual.noise); + + /* send RSSI command to get beacon RSSI/NF, valid only if associated */ + PrepareAndSendCommand(priv, HostCmd_CMD_802_11_RSSI, 0, 0, 0, NULL); + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_GET_LOG, 0, + 0, 0, NULL); + + if (!ret) { + priv->wstats.discard.code = 0; + priv->wstats.discard.fragment = Adapter->LogMsg.fcserror; + priv->wstats.discard.retries = Adapter->LogMsg.retry; + priv->wstats.discard.misc = Adapter->LogMsg.ackfailure; + } + + return &priv->wstats; +} + +static int +wlan_set_coalescing_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + int data; + int *val; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + + switch (data) { + case CMD_DISABLED: + case CMD_ENABLED: + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_IBSS_COALESCING_STATUS, + HostCmd_ACT_SET, + HostCmd_OPTION_WAITFORRSP, 0, &data); + if (ret) { + LEAVE(); + return ret; + } + break; + + case CMD_GET: + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_IBSS_COALESCING_STATUS, + HostCmd_ACT_GET, + HostCmd_OPTION_WAITFORRSP, 0, &data); + if (ret) { + LEAVE(); + return ret; + } + break; + + default: + return -EINVAL; + } + + val = (int *) wrq->u.name; + *val = data; + + LEAVE(); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set frequency + * + * @param priv A pointer to wlan_private structure + * @param info A pointer to iw_request_info structure + * @param fwrq A pointer to iw_freq structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS--success, otherwise--fail + */ +int +wlan_set_freq(struct net_device *dev, struct iw_request_info *info, + struct iw_freq *fwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int rc = -EINPROGRESS; /* Call commit handler */ + CHANNEL_FREQ_POWER *cfp; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + if (Adapter->InfrastructureMode != Wlan802_11IBSS) + return -EOPNOTSUPP; + + /* + * If setting by frequency, convert to a channel + */ + if (fwrq->e == 1) { + + long f = fwrq->m / 100000; + int c = 0; + + cfp = find_cfp_by_band_and_freq(Adapter, 0, f); + if (!cfp) { + PRINTM(INFO, "Invalid freq=%ld\n", f); + return -EINVAL; + } + + c = (int) cfp->Channel; + + if (c < 0) + return -EINVAL; + + fwrq->e = 0; + fwrq->m = c; + } + + /* + * Setting by channel number + */ + if (fwrq->m > 1000 || fwrq->e > 0) { + rc = -EOPNOTSUPP; + } else { + int channel = fwrq->m; + + cfp = find_cfp_by_band_and_channel(Adapter, 0, (u16) channel); + if (!cfp) { + rc = -EINVAL; + } else { + rc = ChangeAdhocChannel(priv, channel); + /* If station is WEP enabled, send the + * command to set WEP in firmware + */ + if (Adapter->SecInfo.WEPStatus == Wlan802_11WEPEnabled) { + PRINTM(INFO, "set_freq: WEP Enabled\n"); + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SET_WEP, + 0, HostCmd_OPTION_WAITFORRSP, + OID_802_11_ADD_WEP, NULL); + + if (ret) { + LEAVE(); + return ret; + } + Adapter->CurrentPacketFilter |= HostCmd_ACT_MAC_WEP_ENABLE; + + PrepareAndSendCommand(priv, + HostCmd_CMD_MAC_CONTROL, + 0, HostCmd_OPTION_WAITFORRSP, + 0, &Adapter->CurrentPacketFilter); + } + } + } + + LEAVE(); + return rc; +} + +/** + * @brief Set Deep Sleep + * + * @param adapter A pointer to wlan_private structure + * @param bDeepSleep TRUE--enalbe deepsleep, FALSE--disable deepsleep + * @return WLAN_STATUS_SUCCESS-success, otherwise fail + */ + +int +SetDeepSleep(wlan_private * priv, BOOLEAN bDeepSleep) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_adapter *Adapter = priv->adapter; + + ENTER(); + + if (bDeepSleep == TRUE) { + if (Adapter->IsDeepSleep != TRUE) { + PRINTM(INFO, "Deep Sleep: sleep\n"); + + // note: the command could be queued and executed later + // if there is command in prigressing. + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_DEEP_SLEEP, 0, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + + if (ret) { + LEAVE(); + return ret; + } + wmm_stop_queue(priv); + os_stop_queue(priv); + os_carrier_off(priv); + } + } else { + if (Adapter->IsDeepSleep == TRUE) { + PRINTM(CMND, "Deep Sleep: wakeup\n"); + + if (Adapter->IntCounterSaved) { + Adapter->IntCounter = Adapter->IntCounterSaved; + Adapter->IntCounterSaved = 0; + } + + if (sbi_exit_deep_sleep(priv)) + PRINTM(ERROR, "Deep Sleep : wakeup failed\n"); + + if (Adapter->IsDeepSleep == TRUE) { + + if (os_wait_interruptible_timeout(Adapter->ds_awake_q, + !Adapter->IsDeepSleep, + MRVDRV_DEEP_SLEEP_EXIT_TIMEOUT) + == 0) { + PRINTM(MSG, "ds_awake_q: timer expired\n"); + } + } + + if (Adapter->IntCounter) + wake_up_interruptible(&priv->MainThread.waitQ); + } + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief use index to get the data rate + * + * @param index The index of data rate + * @return data rate or 0 + */ +u32 +index_to_data_rate(u8 index) +{ + if (index >= sizeof(WlanDataRates)) + index = 0; + + return WlanDataRates[index]; +} + +/** + * @brief use rate to get the index + * + * @param rate data rate + * @return index or 0 + */ +u8 +data_rate_to_index(u32 rate) +{ + u8 *ptr; + + if (rate) + if ((ptr = wlan_memchr(WlanDataRates, (u8) rate, + sizeof(WlanDataRates)))) + return (ptr - WlanDataRates); + + return 0; +} + +/** + * @brief set data rate + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ + +int +wlan_set_rate(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + u32 data_rate; + int ret = WLAN_STATUS_SUCCESS; + WLAN_802_11_RATES rates; + u8 *rate; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + PRINTM(INFO, "Vwrq->value = %d\n", vwrq->value); + + if (vwrq->value == -1) { + Adapter->DataRate = 0; + Adapter->RateBitmap = 0; + memset(rates, 0, sizeof(rates)); + get_active_data_rates(Adapter, rates); + rate = rates; + while (*rate) { + Adapter->RateBitmap |= 1 << (data_rate_to_index(*rate & 0x7f)); + rate++; + } + Adapter->Is_DataRate_Auto = TRUE; + } else { + if ((vwrq->value % 500000)) { + return -EINVAL; + } + + data_rate = vwrq->value / 500000; + + memset(rates, 0, sizeof(rates)); + get_active_data_rates(Adapter, rates); + rate = rates; + while (*rate) { + PRINTM(INFO, "Rate=0x%X Wanted=0x%X\n", *rate, data_rate); + if ((*rate & 0x7f) == (data_rate & 0x7f)) + break; + rate++; + } + if (!*rate) { + PRINTM(MSG, "The fixed data rate 0x%X is out " + "of range.\n", data_rate); + return -EINVAL; + } + + Adapter->DataRate = data_rate; + Adapter->RateBitmap = 1 << (data_rate_to_index(Adapter->DataRate)); + Adapter->Is_DataRate_Auto = FALSE; + } + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RATE_ADAPT_RATESET, + HostCmd_ACT_GEN_SET, + HostCmd_OPTION_WAITFORRSP, 0, NULL); + + LEAVE(); + return ret; +} + +/** + * @brief get data rate + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_get_rate(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (Adapter->Is_DataRate_Auto) + vwrq->fixed = 0; + else + vwrq->fixed = 1; + + Adapter->TxRate = 0; + + ret = PrepareAndSendCommand(priv, HostCmd_CMD_802_11_TX_RATE_QUERY, + HostCmd_ACT_GET, HostCmd_OPTION_WAITFORRSP, + 0, NULL); + if (ret) { + LEAVE(); + return ret; + } + vwrq->value = index_to_data_rate(Adapter->TxRate) * 500000; + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief set wireless mode + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_set_mode(struct net_device *dev, + struct iw_request_info *info, u32 * uwrq, char *extra) +{ + int ret = WLAN_STATUS_SUCCESS; + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + + WLAN_802_11_NETWORK_INFRASTRUCTURE WantedMode; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + switch (*uwrq) { + case IW_MODE_ADHOC: + PRINTM(INFO, "Wanted Mode is ad-hoc: current DataRate=%#x\n", + Adapter->DataRate); + WantedMode = Wlan802_11IBSS; + break; + + case IW_MODE_INFRA: + PRINTM(INFO, "Wanted Mode is Infrastructure\n"); + WantedMode = Wlan802_11Infrastructure; + break; + + case IW_MODE_AUTO: + PRINTM(INFO, "Wanted Mode is Auto\n"); + WantedMode = Wlan802_11AutoUnknown; + break; + + default: + PRINTM(INFO, "Wanted Mode is Unknown: 0x%x\n", *uwrq); + return -EINVAL; + } + + if (Adapter->InfrastructureMode == WantedMode || + WantedMode == Wlan802_11AutoUnknown) { + PRINTM(INFO, "Already set to required mode! No change!\n"); + + Adapter->InfrastructureMode = WantedMode; + + LEAVE(); + return WLAN_STATUS_SUCCESS; + } + + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + if (Adapter->PSState != PS_STATE_FULL_POWER) { + PSWakeup(priv, HostCmd_OPTION_WAITFORRSP); + } + Adapter->PSMode = Wlan802_11PowerModeCAM; + } + + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + if (Adapter->InfrastructureMode == Wlan802_11Infrastructure) { + ret = SendDeauthentication(priv); + if (ret) { + LEAVE(); + return ret; + } + } else if (Adapter->InfrastructureMode == Wlan802_11IBSS) { + /* If current mode is Adhoc, clean stale information */ + ret = StopAdhocNetwork(priv); + + if (ret) { + LEAVE(); + return ret; + } + } + } + + Adapter->SecInfo.AuthenticationMode = Wlan802_11AuthModeOpen; + + Adapter->InfrastructureMode = WantedMode; + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_SNMP_MIB, + 0, HostCmd_OPTION_WAITFORRSP, + OID_802_11_INFRASTRUCTURE_MODE, NULL); + + if (ret) { + LEAVE(); + return ret; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Set Encryption key + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +static int +wlan_set_encode(struct net_device *dev, + struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + + WLAN_802_11_KEY *pKey = NULL; + int retval = -EINVAL; + + ENTER(); + + if (dwrq->length > MAX_WEP_KEY_SIZE) { + pKey = (WLAN_802_11_KEY *) extra; + if (pKey->KeyLength <= MAX_WEP_KEY_SIZE) { + //dynamic WEP + dwrq->length = pKey->KeyLength; + dwrq->flags = pKey->KeyIndex + 1; + retval = wlan_set_encode_nonwpa(dev, info, dwrq, + pKey->KeyMaterial); + } else { + //WPA + retval = wlan_set_encode_wpa(dev, info, dwrq, extra); + } + } else { + //static WEP + PRINTM(INFO, "Setting WEP\n"); + retval = wlan_set_encode_nonwpa(dev, info, dwrq, extra); + } + + return retval; +} + +/** + * @brief set tx power + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_set_txpow(struct net_device *dev, struct iw_request_info *info, + struct iw_param *vwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int ret = WLAN_STATUS_SUCCESS; + + u16 dbm; + + ENTER(); + + if (!Is_Command_Allowed(priv)) { + PRINTM(MSG, "%s: not allowed\n", __FUNCTION__); + return -EBUSY; + } + + if (vwrq->disabled) { + wlan_radio_ioctl(priv, RADIO_OFF); + return WLAN_STATUS_SUCCESS; + } + + wlan_radio_ioctl(priv, RADIO_ON); + +#if WIRELESS_EXT > 14 + if ((vwrq->flags & IW_TXPOW_TYPE) == IW_TXPOW_MWATT) { + dbm = (u16) mw_to_dbm(vwrq->value); + } else +#endif + dbm = (u16) vwrq->value; + + if ((dbm < Adapter->MinTxPowerLevel) || (dbm > Adapter->MaxTxPowerLevel)) { + PRINTM(MSG, + "The set txpower value %d dBm is out of range (%d dBm-%d dBm)!\n", + dbm, Adapter->MinTxPowerLevel, Adapter->MaxTxPowerLevel); + LEAVE(); + return -EINVAL; + } + + /* auto tx power control */ + + if (vwrq->fixed == 0) + dbm = 0xffff; + + PRINTM(INFO, "<1>TXPOWER SET %d dbm.\n", dbm); + + ret = PrepareAndSendCommand(priv, + HostCmd_CMD_802_11_RF_TX_POWER, + HostCmd_ACT_GEN_SET, + HostCmd_OPTION_WAITFORRSP, 0, (void *) &dbm); + + LEAVE(); + return ret; +} + +/** + * @brief Get current essid + * + * @param dev A pointer to net_device structure + * @param info A pointer to iw_request_info structure + * @param vwrq A pointer to iw_param structure + * @param extra A pointer to extra data buf + * @return WLAN_STATUS_SUCCESS --success, otherwise fail + */ +int +wlan_get_essid(struct net_device *dev, struct iw_request_info *info, + struct iw_point *dwrq, char *extra) +{ + wlan_private *priv = dev->priv; + wlan_adapter *Adapter = priv->adapter; + int tblIdx = -1; + BSSDescriptor_t *pBSSDesc; + + ENTER(); + + pBSSDesc = &Adapter->CurBssParams.BSSDescriptor; + + /* + * Get the current SSID + */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + + tblIdx = FindSSIDInList(Adapter, + &pBSSDesc->Ssid, + pBSSDesc->MacAddress, + Adapter->InfrastructureMode); + + memcpy(extra, &pBSSDesc->Ssid.Ssid, pBSSDesc->Ssid.SsidLength); + extra[pBSSDesc->Ssid.SsidLength] = '\0'; + + } else { + memset(extra, 0, 32); + extra[pBSSDesc->Ssid.SsidLength] = '\0'; + } + + /* To make the driver backward compatible with WPA supplicant v0.2.4 */ + if (dwrq->length == 32) { + dwrq->length = MIN(pBSSDesc->Ssid.SsidLength, IW_ESSID_MAX_SIZE); + } else { +#if WIRELESS_EXT > 20 + dwrq->length = pBSSDesc->Ssid.SsidLength; +#else + dwrq->length = pBSSDesc->Ssid.SsidLength + 1; +#endif + } + + /* If the current network is in the table, return the table index */ + if (tblIdx >= 0) { + dwrq->flags = (tblIdx + 1) & IW_ENCODE_INDEX; + } else { + dwrq->flags = 1; + } + + LEAVE(); + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Get version + * + * @param adapter A pointer to wlan_adapter structure + * @param version A pointer to version buffer + * @param maxlen max length of version buffer + * @return NA + */ +void +get_version(wlan_adapter * adapter, char *version, int maxlen) +{ + union + { + u32 l; + u8 c[4]; + } ver; + char fwver[32]; + + ver.l = adapter->FWReleaseNumber; + if (ver.c[3] == 0) + sprintf(fwver, "%u.%u.%u", ver.c[2], ver.c[1], ver.c[0]); + else + sprintf(fwver, "%u.%u.%u.p%u", + ver.c[2], ver.c[1], ver.c[0], ver.c[3]); + + snprintf(version, maxlen, driver_version, fwver); +} diff --git a/drivers/net/wireless/marvell8686/wlan_wext.h b/drivers/net/wireless/marvell8686/wlan_wext.h new file mode 100644 index 0000000..d1d40d1 --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_wext.h @@ -0,0 +1,406 @@ +/** @file wlan_wext.h + * @brief This file contains definition for IOCTL call. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2007 + */ +/******************************************************** +Change log: + 10/11/05: Add Doxygen format comments + 12/19/05: Correct a typo in structure _wlan_ioctl_wmm_tspec + 01/11/06: Conditionalize new scan/join ioctls + 04/10/06: Add hostcmd generic API + 04/18/06: Remove old Subscrive Event and add new Subscribe Event + implementation through generic hostcmd API + 06/08/06: Add definitions of custom events + 08/29/06: Add ledgpio private command +********************************************************/ + +#ifndef _WLAN_WEXT_H_ +#define _WLAN_WEXT_H_ + +#include "wlan_types.h" + +#define SUBCMD_OFFSET 4 +/** PRIVATE CMD ID */ +#define WLANIOCTL 0x8BE0 + +#define WLANSETWPAIE (WLANIOCTL + 0) +#define WLANCISDUMP (WLANIOCTL + 1) +#ifdef MFG_CMD_SUPPORT +#define WLANMANFCMD (WLANIOCTL + 2) +#endif +#define WLANREGRDWR (WLANIOCTL + 3) +#define MAX_EEPROM_DATA 256 +#define WLANHOSTCMD (WLANIOCTL + 4) + +#define WLANHOSTSLEEPCFG (WLANIOCTL + 5) +#define WLANARPFILTER (WLANIOCTL + 6) + +#define WLAN_SETINT_GETINT (WLANIOCTL + 7) +#define WLANNF 1 +#define WLANRSSI 2 +#define WLANBGSCAN 4 +#define WLANENABLE11D 5 +#define WLANADHOCGRATE 6 +#define WLANSDIOCLOCK 7 +#define WLANWMM_ENABLE 8 +#define WLANNULLGEN 10 +#define WLANADHOCCSET 11 +#define WLAN_ADHOC_G_PROT 12 + +#define WLAN_SETNONE_GETNONE (WLANIOCTL + 8) +#define WLANDEAUTH 1 +#define WLANRADIOON 2 +#define WLANRADIOOFF 3 +#define WLANREMOVEADHOCAES 4 +#define WLANADHOCSTOP 5 +#ifdef REASSOCIATION +#define WLANREASSOCIATIONAUTO 8 +#define WLANREASSOCIATIONUSER 9 +#endif /* REASSOCIATION */ +#define WLANWLANIDLEON 10 +#define WLANWLANIDLEOFF 11 + +#define WLANGETLOG (WLANIOCTL + 9) +#define WLAN_SETCONF_GETCONF (WLANIOCTL + 10) + +#define BG_SCAN_CONFIG 1 +#define CAL_DATA_EXT_CONFIG 2 + +#define WLANSCAN_TYPE (WLANIOCTL + 11) + +#define WLAN_SET_GET_2K (WLANIOCTL + 13) +#define WLAN_SET_USER_SCAN 1 +#define WLAN_GET_SCAN_TABLE 2 +#define WLAN_SET_MRVL_TLV 3 +#define WLAN_GET_ASSOC_RSP 4 +#define WLAN_ADDTS_REQ 5 +#define WLAN_DELTS_REQ 6 +#define WLAN_QUEUE_CONFIG 7 +#define WLAN_QUEUE_STATS 8 +#define WLAN_GET_CFP_TABLE 9 +#define WLAN_TX_PKT_STATS 12 + +#define WLAN_SETNONE_GETONEINT (WLANIOCTL + 15) +#define WLANGETREGION 1 +#define WLAN_GET_LISTEN_INTERVAL 2 +#define WLAN_GET_MULTIPLE_DTIM 3 +#define WLAN_GET_TX_RATE 4 +#define WLANGETBCNAVG 5 +#define WLANGETDATAAVG 6 +#define WLANGETDTIM 7 + +#define WLAN_SETNONE_GETTWELVE_CHAR (WLANIOCTL + 19) +#define WLAN_SUBCMD_GETRXANTENNA 1 +#define WLAN_SUBCMD_GETTXANTENNA 2 +#define WLAN_GET_TSF 3 +#define WLAN_WPS_SESSION 4 + +#define WLAN_SETWORDCHAR_GETNONE (WLANIOCTL + 20) +#define WLANSETADHOCAES 1 + +#define WLAN_SETONEINT_GETWORDCHAR (WLANIOCTL + 21) +#define WLANGETADHOCAES 1 +#define WLANVERSION 2 +#define WLANVEREXT 3 + +#define WLAN_SETONEINT_GETONEINT (WLANIOCTL + 23) +#define WLAN_WMM_QOSINFO 2 +#define WLAN_LISTENINTRVL 3 +#define WLAN_FW_WAKEUP_METHOD 4 +#define WAKEUP_FW_UNCHANGED 0 +#define WAKEUP_FW_THRU_INTERFACE 1 +#define WAKEUP_FW_THRU_GPIO 2 + +#define WLAN_NULLPKTINTERVAL 5 +#define WLAN_BCN_MISS_TIMEOUT 6 +#define WLAN_ADHOC_AWAKE_PERIOD 7 +#define WLAN_LDO 8 +#define WLAN_SDIO_MODE 9 +#define WLAN_AUTODEEPSLEEP 12 +#define WLAN_WAKEUP_MT 13 + +#define WLAN_RTS_CTS_CTRL 14 + +#define WLAN_SETONEINT_GETNONE (WLANIOCTL + 24) +#define WLAN_SUBCMD_SETRXANTENNA 1 +#define WLAN_SUBCMD_SETTXANTENNA 2 +#define WLANSETAUTHALG 4 +#define WLANSETENCRYPTIONMODE 5 +#define WLANSETREGION 6 +#define WLAN_SET_LISTEN_INTERVAL 7 + +#define WLAN_SET_MULTIPLE_DTIM 8 + +#define WLANSETBCNAVG 9 +#define WLANSETDATAAVG 10 +#define WLANASSOCIATE 11 + +#define WLAN_SET64CHAR_GET64CHAR (WLANIOCTL + 25) +#define WLANSLEEPPARAMS 2 +#define WLAN_BCA_TIMESHARE 3 +#define WLANSCAN_MODE 6 + +#define WLAN_GET_ADHOC_STATUS 9 + +#define WLAN_SET_GEN_IE 10 +#define WLAN_GET_GEN_IE 11 + +#define WLAN_WMM_QUEUE_STATUS 13 + +#define WLANEXTSCAN (WLANIOCTL + 26) +#define WLANDEEPSLEEP (WLANIOCTL + 27) +#define DEEP_SLEEP_ENABLE 1 +#define DEEP_SLEEP_DISABLE 0 + +#define WLAN_SET_GET_SIXTEEN_INT (WLANIOCTL + 29) +#define WLAN_TPCCFG 1 +#define WLAN_LED_GPIO_CTRL 5 +#define WLAN_SCANPROBES 6 +#define WLAN_SLEEP_PERIOD 7 +#define WLAN_ADAPT_RATESET 8 +#define WLAN_INACTIVITY_TIMEOUT 9 +#define WLANSNR 10 +#define WLAN_GET_RATE 11 +#define WLAN_GET_RXINFO 12 +#define WLAN_SET_ATIM_WINDOW 13 +#define WLAN_BEACON_INTERVAL 14 +#define WLAN_SDIO_PULL_CTRL 15 +#define WLAN_SCAN_TIME 16 +#define WLAN_DATA_SUBSCRIBE_EVENT 18 +#define WLAN_TXCONTROL 19 +#define WLANHSCFG 21 +#define WLANHSSETPARA 22 +#ifdef DEBUG_LEVEL1 +#define WLAN_DRV_DBG 25 +#endif + +#define WLANCMD52RDWR (WLANIOCTL + 30) +#define WLANCMD53RDWR (WLANIOCTL + 31) +#define CMD53BUFLEN 32 + +#define REG_MAC 0x19 +#define REG_BBP 0x1a +#define REG_RF 0x1b +#define REG_EEPROM 0x59 + +#define CMD_DISABLED 0 +#define CMD_ENABLED 1 +#define CMD_GET 2 +#define SKIP_CMDNUM 4 +#define SKIP_TYPE 1 +#define SKIP_SIZE 2 +#define SKIP_ACTION 2 +#define SKIP_TYPE_SIZE (SKIP_TYPE + SKIP_SIZE) +#define SKIP_TYPE_ACTION (SKIP_TYPE + SKIP_ACTION) + +#define MAX_SETGET_CONF_SIZE 2000 /* less than MRVDRV_SIZE_OF_CMD_BUFFER */ +#define MAX_SETGET_CONF_CMD_LEN (MAX_SETGET_CONF_SIZE - SKIP_CMDNUM) + +/* define custom events */ +#define CUS_EVT_HS_ACTIVATED "HS_ACTIVATED " +#define CUS_EVT_HS_DEACTIVATED "HS_DEACTIVATED " +#define CUS_EVT_HS_GPIO_INT "HS_GPIO_INT " +#define CUS_EVT_BEACON_RSSI_LOW "EVENT=BEACON_RSSI_LOW" +#define CUS_EVT_BEACON_SNR_LOW "EVENT=BEACON_SNR_LOW" +#define CUS_EVT_BEACON_RSSI_HIGH "EVENT=BEACON_RSSI_HIGH" +#define CUS_EVT_BEACON_SNR_HIGH "EVENT=BEACON_SNR_HIGH" +#define CUS_EVT_MAX_FAIL "EVENT=MAX_FAIL" +#define CUS_EVT_MLME_MIC_ERR_UNI "MLME-MICHAELMICFAILURE.indication unicast " +#define CUS_EVT_MLME_MIC_ERR_MUL "MLME-MICHAELMICFAILURE.indication multicast " + +#define CUS_EVT_DATA_RSSI_LOW "EVENT=DATA_RSSI_LOW" +#define CUS_EVT_DATA_SNR_LOW "EVENT=DATA_SNR_LOW" +#define CUS_EVT_DATA_RSSI_HIGH "EVENT=DATA_RSSI_HIGH" +#define CUS_EVT_DATA_SNR_HIGH "EVENT=DATA_SNR_HIGH" +#define CUS_EVT_PRE_BEACON_LOST "EVENT=PRE_BEACON_LOST" + +#define CUS_EVT_DEEP_SLEEP_AWAKE "EVENT=DS_AWAKE" + +#define CUS_EVT_ADHOC_LINK_SENSED "EVENT=ADHOC_LINK_SENSED" +#define CUS_EVT_ADHOC_BCN_LOST "EVENT=ADHOC_BCN_LOST" + +/** + * @brief Maximum number of channels that can be sent in a setuserscan ioctl + * + * @sa wlan_ioctl_user_scan_cfg + */ +#define WLAN_IOCTL_USER_SCAN_CHAN_MAX 50 + +/** wlan_ioctl */ +typedef struct _wlan_ioctl +{ + /** Command ID */ + u16 command; + /** data length */ + u16 len; + /** data pointer */ + u8 *data; +} wlan_ioctl; + +/** wlan_ioctl_rfantenna */ +typedef struct _wlan_ioctl_rfantenna +{ + u16 Action; + u16 AntennaMode; +} wlan_ioctl_rfantenna; + +/** wlan_ioctl_regrdwr */ +typedef struct _wlan_ioctl_regrdwr +{ + /** Which register to access */ + u16 WhichReg; + /** Read or Write */ + u16 Action; + u32 Offset; + u16 NOB; + u32 Value; +} wlan_ioctl_regrdwr; + +/** wlan_ioctl_cfregrdwr */ +typedef struct _wlan_ioctl_cfregrdwr +{ + /** Read or Write */ + u8 Action; + /** register address */ + u16 Offset; + /** register value */ + u16 Value; +} wlan_ioctl_cfregrdwr; + +/** wlan_ioctl_adhoc_key_info */ +typedef struct _wlan_ioctl_adhoc_key_info +{ + u16 action; + u8 key[16]; + u8 tkiptxmickey[16]; + u8 tkiprxmickey[16]; +} wlan_ioctl_adhoc_key_info; + +/** sleep_params */ +typedef struct _wlan_ioctl_sleep_params_config +{ + u16 Action; + u16 Error; + u16 Offset; + u16 StableTime; + u8 CalControl; + u8 ExtSleepClk; + u16 Reserved; +} __ATTRIB_PACK__ wlan_ioctl_sleep_params_config, + *pwlan_ioctl_sleep_params_config; + +/** BCA TIME SHARE */ +typedef struct _wlan_ioctl_bca_timeshare_config +{ + /** ACT_GET/ACT_SET */ + u16 Action; + /** Type: WLAN, BT */ + u16 TrafficType; + /** Interval: 20msec - 60000msec */ + u32 TimeShareInterval; + /** PTA arbiter time in msec */ + u32 BTTime; +} __ATTRIB_PACK__ wlan_ioctl_bca_timeshare_config, + *pwlan_ioctl_bca_timeshare_config; + +#define MAX_CFP_LIST_NUM 64 + +/** wlan_ioctl_cfp_table */ +typedef struct _wlan_ioctl_cfp_table +{ + u32 region; + u32 cfp_no; + struct + { + u16 Channel; + u32 Freq; + u16 MaxTxPower; + u8 Unsupported; + } cfp[MAX_CFP_LIST_NUM]; +} __ATTRIB_PACK__ wlan_ioctl_cfp_table, *pwlan_ioctl_cfp_table; + +/** + * @brief IOCTL channel sub-structure sent in wlan_ioctl_user_scan_cfg + * + * Multiple instances of this structure are included in the IOCTL command + * to configure a instance of a scan on the specific channel. + */ +typedef struct +{ + u8 chanNumber; //!< Channel Number to scan + u8 radioType; //!< Radio type: 'B/G' Band = 0, 'A' Band = 1 + u8 scanType; //!< Scan type: Active = 0, Passive = 1 + u8 reserved; + u16 scanTime; //!< Scan duration in milliseconds; if 0 default used +} __ATTRIB_PACK__ wlan_ioctl_user_scan_chan; +/** + * @brief IOCTL SSID List sub-structure sent in wlan_ioctl_user_scan_cfg + * + * Used to specify SSID specific filters as well as SSID pattern matching + * filters for scan result processing in firmware. + */ +typedef struct +{ + char ssid[MRVDRV_MAX_SSID_LENGTH + 1]; + u8 maxLen; +} __ATTRIB_PACK__ wlan_ioctl_user_scan_ssid; + +/** + * @brief IOCTL input structure to configure an immediate scan cmd to firmware + * + * Used in the setuserscan (WLAN_SET_USER_SCAN) private ioctl. Specifies + * a number of parameters to be used in general for the scan as well + * as a channel list (wlan_ioctl_user_scan_chan) for each scan period + * desired. + * + * @sa wlan_set_user_scan_ioctl + */ +typedef struct +{ + + /** + * @brief Flag set to keep the previous scan table intact + * + * If set, the scan results will accumulate, replacing any previous + * matched entries for a BSS with the new scan data + */ + u8 keepPreviousScan; //!< Do not erase the existing scan results + + /** + * @brief BSS Type to be sent in the firmware command + * + * Field can be used to restrict the types of networks returned in the + * scan. Valid settings are: + * + * - WLAN_SCAN_BSS_TYPE_BSS (infrastructure) + * - WLAN_SCAN_BSS_TYPE_IBSS (adhoc) + * - WLAN_SCAN_BSS_TYPE_ANY (unrestricted, adhoc and infrastructure) + */ + u8 bssType; + + /** + * @brief Configure the number of probe requests for active chan scans + */ + u8 numProbes; + + u8 reserved; + + /** + * @brief BSSID filter sent in the firmware command to limit the results + */ + u8 specificBSSID[MRVDRV_ETH_ADDR_LEN]; + + /** + * @brief SSID filter list used in the to limit the scan results + */ + wlan_ioctl_user_scan_ssid ssidList[MRVDRV_MAX_SSID_LIST_LENGTH]; + + /** + * @brief Variable number (fixed maximum) of channels to scan up + */ + wlan_ioctl_user_scan_chan chanList[WLAN_IOCTL_USER_SCAN_CHAN_MAX]; + +} __ATTRIB_PACK__ wlan_ioctl_user_scan_cfg; + +#endif /* _WLAN_WEXT_H_ */ diff --git a/drivers/net/wireless/marvell8686/wlan_wmm.c b/drivers/net/wireless/marvell8686/wlan_wmm.c new file mode 100644 index 0000000..789ad2e --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_wmm.c @@ -0,0 +1,1666 @@ +/** @file wlan_wmm.c + * @brief This file contains functions for WMM. + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/******************************************************** +Change log: + 10/04/05: Add Doxygen format comments + 11/11/05: Add support for WMM Status change event + 01/05/06: Add kernel 2.6.x support + 01/11/06: Conditionalize new scan/join code modifications. + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support +********************************************************/ +#include "include.h" + +/******************************************************** + Local Variables +********************************************************/ + +/** Maximum value FW can accept for driver delay in packet transmission */ +#define DRV_PKT_DELAY_TO_FW_MAX 512 + +/** Set limit of driver packet delay for use in MSDU lifetime expiry and + * traffic stream metrics. + * + * - Set to 0 to disable driver delay in firmware + * - Set to DRV_PKT_DELAY_TO_FW_MAX to enable all possible values + */ +#define DRV_PKT_DELAY_TO_FW_LIMIT 0 + +/** Upper and Lower threshold for packet queuing in the driver + * + * - When the number of packets queued reaches the upper limit, + * the driver will stop the net queue in the app/kernel space. + * + * - When the number of packets drops beneath the lower limit after + * having reached the upper limit, the driver will restart the net + * queue. + */ +#define WMM_QUEUED_PACKET_LOWER_LIMIT 40 +#define WMM_QUEUED_PACKET_UPPER_LIMIT 50 + +#define IPTOS_OFFSET 5 + +static const u8 wmm_info_ie[] = { WMM_IE, 0x07, + 0x00, 0x50, 0xf2, 0x02, + 0x00, 0x01, 0x00 +}; + +/******************************************************** + Local Functions +********************************************************/ +#ifdef DEBUG_LEVEL2 +/** + * @brief Debug print function to display the priority parameters for a WMM AC + * + * @param acStr String pointer giving the AC enumeration (BK, BE, VI, VO) + * @param pACParam Pointer to the AC paramters to display + * + * @return void + */ +static void +wmm_debugPrintAC(wlan_wmm_ac_e acVal, + const IEEEtypes_WmmAcParameters_t * pACParam) +{ + const char *acStr[] = { "BK", "BE", "VI", "VO" }; + + PRINTM(INFO, "WMM AC_%s: ACI=%d, ACM=%d, Aifsn=%d, " + "EcwMin=%d, EcwMax=%d, TxopLimit=%d\n", + acStr[acVal], pACParam->AciAifsn.Aci, pACParam->AciAifsn.Acm, + pACParam->AciAifsn.Aifsn, pACParam->Ecw.EcwMin, + pACParam->Ecw.EcwMax, wlan_le16_to_cpu(pACParam->TxopLimit)); +} + +#define PRINTM_AC(acStr, pACParam) wmm_debugPrintAC(acStr, pACParam) +#else +#define PRINTM_AC(acStr, pACParam) +#endif + +/** + * @brief Compute the difference between two timestamps. + * + * @param pTv1 Pointer to timestamp1 + * @param pTv2 Pointer to timestamp2 + * + * @return Time difference in ms between pTv1 and pTv2 (pTv1 - pTv2) + */ +static int +timeval_diff_in_ms(const struct timeval *pTv1, const struct timeval *pTv2) +{ + int diff_ms; + + diff_ms = (pTv1->tv_sec - pTv2->tv_sec) * 1000; + diff_ms += (pTv1->tv_usec - pTv2->tv_usec) / 1000; + + return diff_ms; +} + +/** + * @brief Set the WMM queue priorities to their default values + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +static void +wmm_default_queue_priorities(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + /* default queue priorities: VO->VI->BE->BK */ + Adapter->wmm.queuePriority[0] = WMM_AC_VO; + Adapter->wmm.queuePriority[1] = WMM_AC_VI; + Adapter->wmm.queuePriority[2] = WMM_AC_BE; + Adapter->wmm.queuePriority[3] = WMM_AC_BK; +} + +/** + * @brief Initialize WMM priority queues + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +static void +wmm_setup_queue_priorities(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + IEEEtypes_WmmParameter_t *pWmmIe; + wlan_wmm_ac_e acOrder[4] = { WMM_AC_BE, WMM_AC_BK, + WMM_AC_VI, WMM_AC_VO + }; + + u16 cwmax, cwmin, avg_back_off, tmp[4]; + int i, j, numAc; + + if (Adapter->wmm.enabled == FALSE) { + /* WMM is not enabled, just set the defaults and return */ + wmm_default_queue_priorities(priv); + return; + } + + pWmmIe = &Adapter->CurBssParams.BSSDescriptor.wmmIE; + + HEXDUMP("WMM: setup_queue_priorities: param IE", + (u8 *) pWmmIe, sizeof(IEEEtypes_WmmParameter_t)); + + PRINTM(INFO, "WMM Parameter IE: version=%d, " + "QoSInfo Parameter Set Count=%d, Reserved=%#x\n", + pWmmIe->VendHdr.Version, pWmmIe->QoSInfo.ParaSetCount, + pWmmIe->Reserved); + + for (numAc = 0; numAc < NELEMENTS(acOrder); numAc++) { + cwmax = (1 << pWmmIe->AcParams[numAc].Ecw.EcwMax) - 1; + cwmin = (1 << pWmmIe->AcParams[numAc].Ecw.EcwMin) - 1; + avg_back_off = (cwmin >> 1) + pWmmIe->AcParams[numAc].AciAifsn.Aifsn; + Adapter->wmm.queuePriority[numAc] = acOrder[numAc]; + tmp[numAc] = avg_back_off; + + PRINTM(INFO, "WMM: CWmax=%d CWmin=%d Avg Back-off=%d\n", + cwmax, cwmin, avg_back_off); + PRINTM_AC(acOrder[numAc], &pWmmIe->AcParams[numAc]); + } + + HEXDUMP("WMM: avg_back_off", (u8 *) tmp, sizeof(tmp)); + HEXDUMP("WMM: queuePriority", Adapter->wmm.queuePriority, + sizeof(Adapter->wmm.queuePriority)); + + /* bubble sort */ + for (i = 0; i < numAc; i++) { + for (j = 1; j < numAc - i; j++) { + if (tmp[j - 1] > tmp[j]) { + SWAP_U16(tmp[j - 1], tmp[j]); + SWAP_U8(Adapter->wmm.queuePriority[j - 1], + Adapter->wmm.queuePriority[j]); + } else if (tmp[j - 1] == tmp[j]) { + if (Adapter->wmm.queuePriority[j - 1] + < Adapter->wmm.queuePriority[j]) { + SWAP_U8(Adapter->wmm.queuePriority[j - 1], + Adapter->wmm.queuePriority[j]); + } + } + } + } + + HEXDUMP("WMM: avg_back_off, sort", (u8 *) tmp, sizeof(tmp)); + HEXDUMP("WMM: queuePriority, sort", Adapter->wmm.queuePriority, + sizeof(Adapter->wmm.queuePriority)); +} + +/** + * @brief pop up the highest skb from wmm queue + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +static void +wmm_pop_highest_prio_skb(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int i; + u8 ac; + + ENTER(); + + for (i = 0; i < MAX_AC_QUEUES; i++) { + ac = Adapter->wmm.queuePriority[i]; + if (!list_empty((struct list_head *) &Adapter->wmm.txSkbQ[ac])) { + PRINTM(DATA, "WMM: Highest prio pkt in AC Queue %d\n", i); + Adapter->CurrentTxSkb = Adapter->wmm.txSkbQ[ac].next; + Adapter->wmm.packetsOut[ac]++; + list_del((struct list_head *) Adapter->wmm.txSkbQ[ac].next); + break; + } + } + + LEAVE(); +} + +/** + * @brief Evaluate whether or not an AC is to be downgraded + * + * @param priv Pointer to the wlan_private driver data struct + * @param evalAC AC to evaluate for downgrading + * + * @return WMM AC the evalAC traffic is to be sent on. + */ +static wlan_wmm_ac_e +wmm_eval_downgrade_ac(wlan_private * priv, wlan_wmm_ac_e evalAC) +{ + wlan_wmm_ac_e downAC; + wlan_wmm_ac_e retAC; + WmmAcStatus_t *pACStatus; + + pACStatus = &priv->adapter->wmm.acStatus[evalAC]; + + if (pACStatus->Disabled == FALSE) { + /* Okay to use this AC, its enabled */ + return evalAC; + } + + /* Setup a default return value of the lowest priority */ + retAC = WMM_AC_BK; + + /* + * Find the highest AC that is enabled and does not require admission + * control. The spec disallows downgarding to an AC which is enabled + * due to a completed admission control. Unadmitted traffic is not + * to be sent on an AC with admitted traffic. + */ + for (downAC = WMM_AC_BK; downAC < evalAC; downAC++) { + pACStatus = &priv->adapter->wmm.acStatus[downAC]; + + if ((pACStatus->Disabled == FALSE) + && (pACStatus->FlowRequired == FALSE)) { + /* AC is enabled and does not require admission control */ + retAC = downAC; + } + } + + return retAC; +} + +/** + * @brief Downgrade WMM priority queue + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +static void +wmm_setup_ac_downgrade(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_wmm_ac_e acVal; + + PRINTM(INFO, "WMM: AC Priorities: BK(0), BE(1), VI(2), VO(3)\n"); + + if (Adapter->wmm.enabled == FALSE) { + /* WMM is not enabled, default priorities */ + for (acVal = WMM_AC_BK; acVal <= WMM_AC_VO; acVal++) { + for (acVal = WMM_AC_BK; acVal <= WMM_AC_VO; acVal++) { + Adapter->wmm.acDowngradedVals[acVal] = acVal; + } + } + } else { + for (acVal = WMM_AC_BK; acVal <= WMM_AC_VO; acVal++) { + Adapter->wmm.acDowngradedVals[acVal] + = wmm_eval_downgrade_ac(priv, acVal); + PRINTM(INFO, "WMM: AC PRIO %d maps to %d\n", + acVal, Adapter->wmm.acDowngradedVals[acVal]); + } + } +} + +/** + * @brief Convert the IP TOS field to an WMM AC Queue assignment + * + * @param tos IP TOS field + * + * @return WMM AC Queue mapping of the IP TOS field + */ +wlan_wmm_ac_e +wmm_convert_tos_to_ac(int tos) +{ + u8 tosIdx; + + /* Map of TOS UP values to WMM AC */ + const wlan_wmm_ac_e tos_to_ac[] = { WMM_AC_BE, + WMM_AC_BK, + WMM_AC_BK, + WMM_AC_BE, + WMM_AC_VI, + WMM_AC_VI, + WMM_AC_VO, + WMM_AC_VO + }; + + tosIdx = tos >> IPTOS_OFFSET; + + if (tosIdx >= NELEMENTS(tos_to_ac)) { + return WMM_AC_BE; + } + + return tos_to_ac[tosIdx]; +} + +/** + * @brief Evaluate a given AC and downgrade it to a lower AC if the + * WMM Parameter IE received from the AP indicates that the AP + * is disabled (due to call admission control (ACM bit) + * + * @param priv Pointer to the wlan_private driver data struct + * @param acVal AC to evaulate for downgrading + * + * @return Same AC as input if downgrading not required or + * the AC the traffic for the given AC should be downgraded to + */ +wlan_wmm_ac_e +wmm_downgrade_ac(wlan_private * priv, wlan_wmm_ac_e acVal) +{ + wlan_adapter *Adapter = priv->adapter; + + return (Adapter->wmm.acDowngradedVals[acVal]); +} + +/** + * @brief Map the IP TOS field to a user priority value + * + * @param tos IP TOS field + * + * @return User priority tos input parameter maps to + */ +static u8 +wmm_tos_to_priority(u8 tos) +{ + u8 tosIdx; + const u8 tos_to_priority[] = { + /* Priority DSCP_P2 DSCP_P1 DSCP_P0 WMM_AC */ + 0x00, /* 0 0 0 AC_BE */ + 0x01, /* 0 0 1 AC_BK */ + 0x02, /* 0 1 0 AC_BK */ + 0x03, /* 0 1 1 AC_BE */ + 0x04, /* 1 0 0 AC_VI */ + 0x05, /* 1 0 1 AC_VI */ + 0x06, /* 1 1 0 AC_VO */ + 0x07 /* 1 1 1 AC_VO */ + }; + + tosIdx = tos >> IPTOS_OFFSET; + + if (tosIdx >= NELEMENTS(tos_to_priority)) { + return WMM_AC_BE; + } + + return tos_to_priority[tosIdx]; +} + +/** + * @brief Process a transfer of a data packet to the firmware from the + * driver queue in order to manipulate flow control in the driver. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_process_fw_iface_tx_xfer_start(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + if (--Adapter->wmm.packetsQueued < WMM_QUEUED_PACKET_LOWER_LIMIT) { + PRINTM(DATA, "WMM: FW OS+: %d\n", Adapter->wmm.packetsQueued); + os_start_queue(priv); + } +} + +/** + * @brief Process the completion of a data packet transfer to the firmware + * from the driver queue in order to manipulate flow control in the + * driver. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + * + */ +void +wmm_process_fw_iface_tx_xfer_end(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + if (Adapter->wmm.packetsQueued) { + PRINTM(DATA, "WMM: FW OS-: %d\n", Adapter->wmm.packetsQueued); + os_stop_queue(priv); + } +} + +/** + * @brief Process a transfer of a data packet from the OS to the driver + * queue in order to manipulate flow control in the driver. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_process_app_iface_tx(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + Adapter->wmm.packetsQueued++; + + if ((!priv->wlan_dev.dnld_sent && (Adapter->PSState != PS_STATE_SLEEP)) + || (Adapter->wmm.packetsQueued >= WMM_QUEUED_PACKET_UPPER_LIMIT)) { + PRINTM(DATA, "WMM: APP OS-: %d\n", Adapter->wmm.packetsQueued); + os_stop_queue(priv); + } +} + +/** + * @brief Stop the WMM data queues. Traffic is still accepted from the + * OS until the buffer limits are reached. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_stop_queue(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + PRINTM(DATA, "WMM: Q-: %d\n", Adapter->wmm.packetsQueued); + Adapter->wmm.queueStopped = TRUE; +} + +/** + * @brief Start/re-start the WMM data queues and indicate to the OS layer + * that data is being accepted again. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_start_queue(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + + PRINTM(DATA, "WMM: Q+: %d\n", Adapter->wmm.packetsQueued); + Adapter->wmm.queueStopped = FALSE; + if (Adapter->wmm.packetsQueued) { + wake_up_interruptible(&priv->MainThread.waitQ); + } + os_carrier_on(priv); + os_start_queue(priv); +} + +/** + * @brief Query the status of the WMM queues. Determine if the driver data + * path is active or not. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return TRUE if WMM queues have been stopped, FALSE if still active + */ +int +wmm_is_queue_stopped(wlan_private * priv) +{ + return (priv->adapter->wmm.queueStopped == TRUE); +} + +/** + * @brief Initialize the WMM state information and the WMM data path queues. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_init(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + int i; + memset(&Adapter->wmm, 0x00, sizeof(Adapter->wmm)); + + for (i = 0; i < MAX_AC_QUEUES; i++) { + INIT_LIST_HEAD((struct list_head *) &Adapter->wmm.txSkbQ[i]); + } + + Adapter->wmm.required = FALSE; + + Adapter->gen_null_pkg = TRUE; /*Enable NULL Pkg generation */ +} + +/** + * @brief Setup the queue priorities and downgrade any queues as required + * by the WMM info. Setups default values if WMM is not active + * for this association. + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_setup_queues(wlan_private * priv) +{ + wmm_setup_queue_priorities(priv); + wmm_setup_ac_downgrade(priv); +} + +/** + * @brief implement WMM enable command + * + * @param priv Pointer to the wlan_private driver data struct + * @param wrq Pointer to user data + * + * @return WLAN_STATUS_SUCCESS if success; otherwise <0 + */ +int +wlan_wmm_enable_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + ulong flags; + int data, data1; + int *val; + + ENTER(); + + data = *((int *) (wrq->u.name + SUBCMD_OFFSET)); + switch (data) { + case CMD_DISABLED: /* disable */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + return -EPERM; + } + + spin_lock_irqsave(&Adapter->CurrentTxLock, flags); + Adapter->wmm.required = FALSE; + if (!Adapter->wmm.enabled) { + spin_unlock_irqrestore(&Adapter->CurrentTxLock, flags); + data1 = Adapter->wmm.required; + val = (int *) wrq->u.name; + *val = data; + return WLAN_STATUS_SUCCESS; + } else { + Adapter->wmm.enabled = 0; + } + + if (Adapter->CurrentTxSkb) { + kfree_skb(Adapter->CurrentTxSkb); + OS_INT_DISABLE; + Adapter->CurrentTxSkb = NULL; + OS_INT_RESTORE; + priv->stats.tx_dropped++; + } + + spin_unlock_irqrestore(&Adapter->CurrentTxLock, flags); + break; + + case CMD_ENABLED: /* enable */ + if (Adapter->MediaConnectStatus == WlanMediaStateConnected) { + return -EPERM; + } + spin_lock_irqsave(&Adapter->CurrentTxLock, flags); + Adapter->wmm.required = TRUE; + spin_unlock_irqrestore(&Adapter->CurrentTxLock, flags); + break; + + case CMD_GET: + break; + default: + PRINTM(INFO, "Invalid option\n"); + return -EINVAL; + } + + data = Adapter->wmm.required; + val = (int *) wrq->u.name; + *val = data; + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Implement cmd HostCmd_CMD_WMM_GET_STATUS + * + * @param priv Pointer to the wlan_private driver data struct + * @param cmd Pointer to CMD buffer + * @param InfoBuf Pointer to cmd data + * + * @return WLAN_STATUS_SUCCESS + */ +int +wlan_cmd_wmm_get_status(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf) +{ + PRINTM(INFO, "WMM: WMM_GET_STATUS cmd sent\n"); + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_WMM_GET_STATUS); + cmd->Size = + wlan_cpu_to_le16(sizeof(HostCmd_DS_WMM_GET_STATUS) + S_DS_GEN); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Send a command to firmware to retrieve the current WMM status + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return WLAN_STATUS_SUCCESS; WLAN_STATUS_FAILURE + */ +int +sendWMMStatusChangeCmd(wlan_private * priv) +{ + return PrepareAndSendCommand(priv, HostCmd_CMD_WMM_GET_STATUS, + 0, 0, 0, NULL); +} + +/** + * @brief Check if wmm TX queue is empty + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return FALSE if not empty; TRUE if empty + */ +int +wmm_lists_empty(wlan_private * priv) +{ + int i; + + for (i = 0; i < MAX_AC_QUEUES; i++) { + if (!list_empty((struct list_head *) &priv->adapter->wmm.txSkbQ[i])) { + return FALSE; + } + } + return TRUE; +} + +/** + * @brief Cleanup wmm TX queue + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_cleanup_queues(wlan_private * priv) +{ + int i; + struct sk_buff *delNode, *Q; + + ENTER(); + + for (i = 0; i < MAX_AC_QUEUES; i++) { + Q = &priv->adapter->wmm.txSkbQ[i]; + + while (!list_empty((struct list_head *) Q)) { + delNode = Q->next; + list_del((struct list_head *) delNode); + kfree_skb(delNode); + } + } + + priv->adapter->wmm.packetsQueued = 0; + + LEAVE(); +} + +/** + * @brief Add skb to WMM queue + * + * @param priv Pointer to the wlan_private driver data struct + * @param skb Pointer to sk_buff + * + * @return void + */ +void +wmm_map_and_add_skb(wlan_private * priv, struct sk_buff *skb) +{ + wlan_adapter *Adapter = priv->adapter; + struct ethhdr *eth; + struct timeval tstamp; + u8 tos; + wlan_wmm_ac_e ac; + wlan_wmm_ac_e ac_down; + + eth = (struct ethhdr *) skb->data; + + switch (eth->h_proto) { + case __constant_htons(ETH_P_IP): +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22) + PRINTM(INFO, "packet type ETH_P_IP: %04x, tos=%#x prio=%#x\n", + eth->h_proto, ip_hdr(skb)->tos, skb->priority); + tos = IPTOS_PREC(ip_hdr(skb)->tos) >> IPTOS_OFFSET; +#else + PRINTM(DATA, "packet type ETH_P_IP: %04x, tos=%#x prio=%#x\n", + eth->h_proto, skb->nh.iph->tos, skb->priority); + tos = IPTOS_PREC(skb->nh.iph->tos); +#endif + break; + case __constant_htons(ETH_P_ARP): + PRINTM(DATA, "ARP packet %04x\n", eth->h_proto); + default: + tos = 0; + break; + } + + ac = wmm_convert_tos_to_ac(tos); + ac_down = wmm_downgrade_ac(priv, ac); + + skb->priority = wmm_tos_to_priority(tos); + PRINTM(DATA, "wmm_map: tos=%#x, ac=%#x ac_down=%#x, priority=%#x\n", + tos, ac, ac_down, skb->priority); + + list_add_tail((struct list_head *) skb, + (struct list_head *) &Adapter->wmm.txSkbQ[ac_down]); + + wmm_process_app_iface_tx(priv); + + /* Record the current time the packet was queued; used to determine + * the amount of time the packet was queued in the driver before it + * was sent to the firmware. The delay is then sent along with the + * packet to the firmware for aggregate delay calculation for stats + * and MSDU lifetime expiry. + */ + do_gettimeofday(&tstamp); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22) + skb->tstamp = timeval_to_ktime(tstamp); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14) + skb_set_timestamp(skb, &tstamp); +#else + memcpy(&skb->stamp, &tstamp, sizeof(skb->stamp)); +#endif +} + +/** + * @brief Process the GET_WMM_STATUS command response from firmware + * + * The GET_WMM_STATUS command returns multiple TLVs for: + * - Each AC Queue status + * - Current WMM Parameter IE + * + * This function parses the TLVs and then calls further functions + * to process any changes in the queue prioritization or state. + * + * @param priv Pointer to the wlan_private driver data struct + * @param resp Pointer to the command response buffer including TLVs + * TLVs for each queue and the WMM Parameter IE. + * + * @return WLAN_STATUS_SUCCESS + */ +int +wlan_cmdresp_wmm_get_status(wlan_private * priv, + const HostCmd_DS_COMMAND * resp) +{ + wlan_adapter *Adapter = priv->adapter; + u8 *pCurrent = (u8 *) & resp->params.getWmmStatus; + u32 respLen = resp->Size; + int valid = TRUE; + int enableData = TRUE; + + MrvlIEtypes_Data_t *pTlvHdr; + MrvlIEtypes_WmmQueueStatus_t *pTlvWmmQStatus; + IEEEtypes_WmmParameter_t *pWmmParamIe; + WmmAcStatus_t *pACStatus; + + PRINTM(INFO, "WMM: WMM_GET_STATUS cmdresp received: %d\n", respLen); + HEXDUMP("CMD_RESP: WMM_GET_STATUS", pCurrent, respLen); + + while ((respLen >= sizeof(pTlvHdr->Header)) && valid) { + pTlvHdr = (MrvlIEtypes_Data_t *) pCurrent; + pTlvHdr->Header.Len = wlan_le16_to_cpu(pTlvHdr->Header.Len); + + switch (wlan_le16_to_cpu(pTlvHdr->Header.Type)) { + case TLV_TYPE_WMMQSTATUS: + pTlvWmmQStatus = (MrvlIEtypes_WmmQueueStatus_t *) pTlvHdr; + PRINTM(INFO, + "CMD_RESP: WMM_GET_STATUS: QSTATUS TLV: %d, %d, %d\n", + pTlvWmmQStatus->QueueIndex, pTlvWmmQStatus->FlowRequired, + pTlvWmmQStatus->Disabled); + + pACStatus = &Adapter->wmm.acStatus[pTlvWmmQStatus->QueueIndex]; + pACStatus->Disabled = pTlvWmmQStatus->Disabled; + pACStatus->FlowRequired = pTlvWmmQStatus->FlowRequired; + pACStatus->FlowCreated = pTlvWmmQStatus->FlowCreated; + break; + + case WMM_IE: + /* + * Point the regular IEEE IE 2 bytes into the Marvell IE + * and setup the IEEE IE type and length byte fields + */ + + HEXDUMP("WMM: WMM TLV:", (u8 *) pTlvHdr, pTlvHdr->Header.Len + 4); + + pWmmParamIe = (IEEEtypes_WmmParameter_t *) (pCurrent + 2); + pWmmParamIe->VendHdr.Len = pTlvHdr->Header.Len; + pWmmParamIe->VendHdr.ElementId = WMM_IE; + + PRINTM(INFO, "CMD_RESP: WMM_GET_STATUS: WMM Parameter Set: %d\n", + pWmmParamIe->QoSInfo.ParaSetCount); + + memcpy((u8 *) & Adapter->CurBssParams.BSSDescriptor.wmmIE, + pWmmParamIe, pWmmParamIe->VendHdr.Len + 2); + + break; + + default: + valid = FALSE; + break; + } + + pCurrent += (pTlvHdr->Header.Len + sizeof(pTlvHdr->Header)); + respLen -= (pTlvHdr->Header.Len + sizeof(pTlvHdr->Header)); + } + + wmm_setup_queue_priorities(priv); + wmm_setup_ac_downgrade(priv); + + if (enableData) { + wmm_start_queue(priv); + os_carrier_on(priv); + os_start_queue(priv); + } + + send_iwevcustom_event(priv, WMM_CONFIG_CHANGE_INDICATION); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Call back from the command module to allow insertion of a WMM TLV + * + * If the BSS we are associating to supports WMM, add the required WMM + * Information IE to the association request command buffer in the form + * of a Marvell extended IEEE IE. + * + * @param priv Pointer to the wlan_private driver data struct + * @param ppAssocBuf Output parameter: Pointer to the TLV output buffer, + * modified on return to point after the appended WMM TLV + * @param pWmmIE Pointer to the WMM IE for the BSS we are joining + * + * @return Length of data appended to the association tlv buffer + */ +u32 +wlan_wmm_process_association_req(wlan_private * priv, + u8 ** ppAssocBuf, + IEEEtypes_WmmParameter_t * pWmmIE) +{ + wlan_adapter *Adapter = priv->adapter; + MrvlIEtypes_WmmParamSet_t *pWmmTlv; + u32 retLen = 0; + + /* Null checks */ + if (ppAssocBuf == 0) + return 0; + if (*ppAssocBuf == 0) + return 0; + if (pWmmIE == 0) + return 0; + + PRINTM(INFO, "WMM: process assoc req: bss->wmmIe=%x\n", + pWmmIE->VendHdr.ElementId); + + if (Adapter->wmm.required && pWmmIE->VendHdr.ElementId == WMM_IE) { + pWmmTlv = (MrvlIEtypes_WmmParamSet_t *) * ppAssocBuf; + pWmmTlv->Header.Type = (u16) wmm_info_ie[0]; + pWmmTlv->Header.Type = wlan_cpu_to_le16(pWmmTlv->Header.Type); + pWmmTlv->Header.Len = (u16) wmm_info_ie[1]; + pWmmTlv->Header.Len = wlan_cpu_to_le16(pWmmTlv->Header.Len); + + memcpy(pWmmTlv->WmmIE, &wmm_info_ie[2], pWmmTlv->Header.Len); +#define QOS_INFO_PARA_MASK 0x0f + if (pWmmIE->QoSInfo.QosUAPSD + && ((Adapter->wmm.qosinfo & QOS_INFO_PARA_MASK) != 0)) { + memcpy((u8 *) (pWmmTlv->WmmIE + pWmmTlv->Header.Len + - sizeof(Adapter->wmm.qosinfo)), + &Adapter->wmm.qosinfo, sizeof(Adapter->wmm.qosinfo)); + } + retLen = sizeof(pWmmTlv->Header) + pWmmTlv->Header.Len; + + HEXDUMP("ASSOC_CMD: WMM IE", (u8 *) pWmmTlv, retLen); + *ppAssocBuf += retLen; + } + + return retLen; +} + +/** + * @brief Compute the time delay in the driver queues for a given skb. + * + * When the skb is received at the OS/Driver interface, the current + * time is set in the skb structure. The difference between the present + * time and that received time is computed in this function and limited + * based on pre-compiled limits in the driver. + * + * @param skb Pointer to a sk_buff which has been previously timestamped + * + * @return Time delay of the packet in 2ms units after having limit applied + */ +u8 +wmm_compute_driver_packet_delay(const struct sk_buff * skb) +{ + u8 retVal; + struct timeval in_tv; + struct timeval out_tv; + int queue_delay; + + retVal = 0; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14) + skb_get_timestamp(skb, &in_tv); +#else + memcpy(&in_tv, &skb->stamp, sizeof(in_tv)); +#endif + do_gettimeofday(&out_tv); + + queue_delay = timeval_diff_in_ms(&out_tv, &in_tv); + + /* Queue delay is passed as a uint8 in units of 2ms (ms shifted + * by 1). Min value (other than 0) is therefore 2ms, max is 510ms. + * + * Pass max value if queue_delay is beyond the uint8 range + */ + retVal = MIN(queue_delay, DRV_PKT_DELAY_TO_FW_LIMIT) >> 1; + + PRINTM(DATA, "WMM: Pkt Delay: %d ms, %d ms sent to FW\n", + queue_delay, retVal); + + return retVal; +} + +/** + * @brief Transmit the highest priority packet awaiting in the WMM Queues + * + * @param priv Pointer to the wlan_private driver data struct + * + * @return void + */ +void +wmm_process_tx(wlan_private * priv) +{ + wlan_adapter *Adapter = priv->adapter; + ulong flags; + + OS_INTERRUPT_SAVE_AREA; + + ENTER(); + + if ((Adapter->PSState == PS_STATE_SLEEP) + || (Adapter->PSState == PS_STATE_PRE_SLEEP)) { + PRINTM(INFO, "In PS State %d" + " - Not sending the packet\n", Adapter->PSState); + LEAVE(); + + return; + } + + spin_lock_irqsave(&Adapter->CurrentTxLock, flags); + + if (priv->wlan_dev.dnld_sent) { + spin_unlock_irqrestore(&Adapter->CurrentTxLock, flags); + + LEAVE(); + + return; + } + + UpdateTransStart(priv->wlan_dev.netdev); + wmm_pop_highest_prio_skb(priv); + + spin_unlock_irqrestore(&Adapter->CurrentTxLock, flags); + + if (Adapter->CurrentTxSkb) { + wlan_process_tx(priv); + } + + LEAVE(); +} + +/** + * @brief Private IOCTL entry to get the status of the WMM queues + * + * Return the following information for each WMM AC: + * - WMM IE Acm Required + * - Firmware Flow Required + * - Firmware Flow Established + * - Firmware Queue Enabled + * + * @param priv Pointer to the wlan_private driver data struct + * @param wrq A pointer to iwreq structure containing the + * wlan_ioctl_wmm_queue_status_t struct for request + * + * @return 0 if successful; IOCTL error code otherwise + */ +int +wlan_wmm_queue_status_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_adapter *Adapter = priv->adapter; + wlan_ioctl_wmm_queue_status_t qstatus; + wlan_wmm_ac_e acVal; + WmmAcStatus_t *pACStatus; + IEEEtypes_WmmAcParameters_t *pWmmIeAC; + + for (acVal = WMM_AC_BK; acVal <= WMM_AC_VO; acVal++) { + pACStatus = &Adapter->wmm.acStatus[acVal]; + pWmmIeAC = &Adapter->CurBssParams.BSSDescriptor.wmmIE.AcParams[acVal]; + + /* Acm bit */ + qstatus.acStatus[acVal].wmmAcm = pWmmIeAC->AciAifsn.Acm; + + /* Firmware status */ + qstatus.acStatus[acVal].flowRequired = pACStatus->FlowRequired; + qstatus.acStatus[acVal].flowCreated = pACStatus->FlowCreated; + qstatus.acStatus[acVal].disabled = pACStatus->Disabled; + } + + if (copy_to_user(wrq->u.data.pointer, &qstatus, sizeof(qstatus))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Private IOCTL entry to send an ADDTS TSPEC + * + * Receive a ADDTS command from the application. The command structure + * contains a TSPEC and timeout in milliseconds. The timeout is performed + * in the firmware after the ADDTS command frame is sent. + * + * The TSPEC is received in the API as an opaque block whose length is + * calculated from the IOCTL data length. The firmware will send the + * entire data block, including the bytes after the TSPEC. This is done + * to allow extra IEs to be packaged with the TSPEC in the ADDTS action + * frame. + * + * The IOCTL structure contains two return fields: + * - The firmware command result which indicates failure and timeouts + * - The IEEE Status code which contains the corresponding value from + * any ADDTS response frame received. + * + * In addition, the opaque TSPEC data block passed in is replaced with the + * TSPEC recieved in the ADDTS response frame. In case of failure, the + * AP may modify the TSPEC on return and in the case of success, the + * medium time is returned as calculated by the AP. Along with the TSPEC, + * any IEs that are sent in the ADDTS response are also returned and can be + * parsed using the IOCTL length as an indicator of extra elements. + * + * The return value to the application layer indicates a driver execution + * success or failure. A successful return could still indicate a firmware + * failure or AP negotiation failure via the commandResult field copied + * back to the application. + * + * @param priv Pointer to the wlan_private driver data struct + * @param wrq A pointer to iwreq structure containing the + * wlan_ioctl_wmm_addts_req_t struct for this ADDTS request + * + * @return 0 if successful; IOCTL error code otherwise + */ +int +wlan_wmm_addts_req_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + static u8 dialogTok = 0; + wlan_ioctl_wmm_addts_req_t addtsIoctl; + wlan_cmd_wmm_addts_req_t addtsCmd; + int retcode; + + if (copy_from_user(&addtsIoctl, + wrq->u.data.pointer, + MIN(wrq->u.data.length, sizeof(addtsIoctl))) != 0) { + /* copy_from_user failed */ + PRINTM(INFO, "TSPEC: ADDTS copy from user failed\n"); + retcode = -EFAULT; + + } else { + memset(&addtsCmd, 0x00, sizeof(addtsCmd)); + if ((++dialogTok) == 0) + dialogTok = 1; + + addtsCmd.dialogToken = dialogTok; + addtsCmd.timeout_ms = addtsIoctl.timeout_ms; + addtsCmd.tspecDataLen = (wrq->u.data.length + - sizeof(addtsCmd.timeout_ms) + - sizeof(addtsCmd.commandResult) + - sizeof(addtsCmd.ieeeStatusCode)); + memcpy(addtsCmd.tspecData, + addtsIoctl.tspecData, addtsCmd.tspecDataLen); + + retcode = PrepareAndSendCommand(priv, + HostCmd_CMD_WMM_ADDTS_REQ, 0, + HostCmd_OPTION_WAITFORRSP, 0, + &addtsCmd); + + wrq->u.data.length = (sizeof(addtsIoctl.timeout_ms) + + sizeof(addtsIoctl.commandResult) + + sizeof(addtsIoctl.ieeeStatusCode) + + addtsCmd.tspecDataLen); + + addtsIoctl.commandResult = addtsCmd.commandResult; + addtsIoctl.ieeeStatusCode = addtsCmd.ieeeStatusCode; + memcpy(addtsIoctl.tspecData, + addtsCmd.tspecData, addtsCmd.tspecDataLen); + + if (copy_to_user(wrq->u.data.pointer, + &addtsIoctl, sizeof(addtsIoctl))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + if (retcode) { + return -EFAULT; + } + } + + return retcode; +} + +/** + * @brief Private IOCTL entry to send a DELTS TSPEC + * + * Receive a DELTS command from the application. The command structure + * contains a TSPEC and reason code along with space for a command result + * to be returned. The information is packaged is sent to the wlan_cmd.c + * firmware command prep and send routines for execution in the firmware. + * + * The reason code is not used for WMM implementations but is indicated in + * the 802.11e specification. + * + * The return value to the application layer indicates a driver execution + * success or failure. A successful return could still indicate a firmware + * failure via the commandResult field copied back to the application. + * + * @param priv Pointer to the wlan_private driver data struct + * @param wrq A pointer to iwreq structure containing the + * wlan_ioctl_wmm_delts_req_t struct for this DELTS request + * + * @return 0 if successful; IOCTL error code otherwise + */ +int +wlan_wmm_delts_req_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_ioctl_wmm_delts_req_t deltsIoctl; + wlan_cmd_wmm_delts_req_t deltsCmd; + int retcode; + + if (copy_from_user(&deltsIoctl, + wrq->u.data.pointer, + MIN(wrq->u.data.length, sizeof(deltsIoctl))) != 0) { + /* copy_from_user failed */ + PRINTM(INFO, "TSPEC: DELTS copy from user failed\n"); + retcode = -EFAULT; + + } else { + memset(&deltsCmd, 0x00, sizeof(deltsCmd)); + + /* Dialog token unused for WMM implementations */ + deltsCmd.dialogToken = 0; + + deltsCmd.ieeeReasonCode = deltsIoctl.ieeeReasonCode; + + /* Calculate the length of the TSPEC and any other IEs */ + deltsCmd.tspecDataLen = (wrq->u.data.length + - sizeof(deltsCmd.commandResult) + - sizeof(deltsCmd.ieeeReasonCode)); + memcpy(deltsCmd.tspecData, + deltsIoctl.tspecData, deltsCmd.tspecDataLen); + + /* Send the DELTS request to firmware, wait for a response */ + retcode = PrepareAndSendCommand(priv, + HostCmd_CMD_WMM_DELTS_REQ, 0, + HostCmd_OPTION_WAITFORRSP, 0, + &deltsCmd); + + /* Return the firmware command result back to the application layer */ + deltsIoctl.commandResult = deltsCmd.commandResult; + + if (copy_to_user(wrq->u.data.pointer, + &deltsCmd, + MIN(wrq->u.data.length, sizeof(deltsIoctl)))) { + PRINTM(INFO, "Copy to user failed\n"); + return -EFAULT; + } + + if (retcode) { + retcode = -EFAULT; + } + } + + return retcode; +} + +/** + * @brief Process the ADDTS_REQ command response from firmware + * + * Return the ADDTS firmware response to the calling thread that sent + * the command. The result is then relayed back the app layer. + * + * @param priv Pointer to the wlan_private driver data struct + * @param resp Pointer to the command response buffer including the + * command result and any returned ADDTS response TSPEC + * elements + * + * @return WLAN_STATUS_SUCCESS + * + * @sa wlan_wmm_addts_req_ioctl + */ +int +wlan_cmdresp_wmm_addts_req(wlan_private * priv, + const HostCmd_DS_COMMAND * resp) +{ + wlan_cmd_wmm_addts_req_t *pAddTsCmd; + const HostCmd_DS_WMM_ADDTS_REQ *pCmdResp; + + /* Cast the NULL pointer of the buffer the IOCTL sent in the command req */ + pAddTsCmd = (wlan_cmd_wmm_addts_req_t *) priv->adapter->CurCmd->pdata_buf; + + /* Convenience variable for the ADDTS response from the firmware */ + pCmdResp = &resp->params.addTsReq; + + /* Assign return data */ + pAddTsCmd->commandResult = pCmdResp->commandResult; + + if (pCmdResp->commandResult == TSPEC_RESULT_SUCCESS) { + pAddTsCmd->dialogToken = pCmdResp->dialogToken; + pAddTsCmd->ieeeStatusCode = pCmdResp->ieeeStatusCode; + + /* The tspecData field is potentially variable in size due to extra IEs + * that may have been in the ADDTS response action frame. Calculate + * the data length from the firmware command response. + */ + pAddTsCmd->tspecDataLen = (resp->Size + - sizeof(pCmdResp->commandResult) + - sizeof(pCmdResp->timeout_ms) + - sizeof(pCmdResp->dialogToken) + - sizeof(pCmdResp->ieeeStatusCode) + - S_DS_GEN); + + /* Copy the TSPEC data include any extra IEs after the TSPEC */ + memcpy(pAddTsCmd->tspecData, + pCmdResp->tspecData, + MIN(pAddTsCmd->tspecDataLen, sizeof(pAddTsCmd->tspecData) + + sizeof(pAddTsCmd->addtsExtraIEBuf))); + } else { + pAddTsCmd->dialogToken = 0; + pAddTsCmd->ieeeStatusCode = 0; + pAddTsCmd->tspecDataLen = 0; + } + + PRINTM(INFO, "TSPEC: ADDTS ret = %d,%d sz=%d\n", + pAddTsCmd->commandResult, pAddTsCmd->ieeeStatusCode, + pAddTsCmd->tspecDataLen); + + HEXDUMP("TSPEC: ADDTS data", + pAddTsCmd->tspecData, pAddTsCmd->tspecDataLen); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Process the DELTS_REQ command response from firmware + * + * Return the DELTS firmware response to the calling thread that sent + * the command. The result is then relayed back the app layer. + * + * @param priv Pointer to the wlan_private driver data struct + * @param resp Pointer to the command response buffer with the command + * result. No other response information is passed back + * to the driver. + * + * @return WLAN_STATUS_SUCCESS + * + * @sa wlan_wmm_delts_req_ioctl + */ +int +wlan_cmdresp_wmm_delts_req(wlan_private * priv, + const HostCmd_DS_COMMAND * resp) +{ + wlan_cmd_wmm_delts_req_t *pDelTsCmd; + + /* Cast the NULL pointer of the buffer the IOCTL sent in the command req */ + pDelTsCmd = (wlan_cmd_wmm_delts_req_t *) priv->adapter->CurCmd->pdata_buf; + + pDelTsCmd->commandResult = + wlan_le16_to_cpu(resp->params.delTsReq.commandResult); + + PRINTM(INFO, "TSPEC: DELTS result = %d\n", pDelTsCmd->commandResult); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Implement cmd HostCmd_DS_WMM_ADDTS_REQ + * + * @param priv Pointer to the wlan_private driver data struct + * @param cmd Pointer to CMD buffer + * @param InfoBuf Pointer to cmd data + * + * @return WLAN_STATUS_SUCCESS + * + * @sa wlan_wmm_addts_req_ioctl + */ +int +wlan_cmd_wmm_addts_req(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf) +{ + wlan_cmd_wmm_addts_req_t *pAddTsCmd; + int tspecCopySize; + + pAddTsCmd = (wlan_cmd_wmm_addts_req_t *) InfoBuf; + + cmd->params.addTsReq.timeout_ms = pAddTsCmd->timeout_ms; + cmd->params.addTsReq.dialogToken = pAddTsCmd->dialogToken; + + tspecCopySize = MIN(pAddTsCmd->tspecDataLen, + sizeof(cmd->params.addTsReq.tspecData)); + memcpy(&cmd->params.addTsReq.tspecData, + pAddTsCmd->tspecData, tspecCopySize); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_WMM_ADDTS_REQ); + cmd->Size = wlan_cpu_to_le16(sizeof(cmd->params.addTsReq.dialogToken) + + sizeof(cmd->params.addTsReq.timeout_ms) + + sizeof(cmd->params.addTsReq.commandResult) + + sizeof(cmd->params.addTsReq.ieeeStatusCode) + + tspecCopySize + S_DS_GEN); + + cmd->params.addTsReq.timeout_ms + = wlan_cpu_to_le32(cmd->params.addTsReq.timeout_ms); + + PRINTM(INFO, "WMM: ADDTS Cmd: Data Len = %d\n", pAddTsCmd->tspecDataLen); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Implement cmd HostCmd_DS_WMM_DELTS_REQ + * + * @param priv Pointer to the wlan_private driver data struct + * @param cmd Pointer to CMD buffer + * @param InfoBuf Void pointer cast of a wlan_cmd_wmm_delts_req_t struct + * + * @return WLAN_STATUS_SUCCESS + * + * @sa wlan_wmm_delts_req_ioctl + */ +int +wlan_cmd_wmm_delts_req(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf) +{ + wlan_cmd_wmm_delts_req_t *pDelTsCmd; + int tspecCopySize; + + pDelTsCmd = (wlan_cmd_wmm_delts_req_t *) InfoBuf; + + cmd->params.delTsReq.dialogToken = pDelTsCmd->dialogToken; + cmd->params.delTsReq.ieeeReasonCode = pDelTsCmd->ieeeReasonCode; + + tspecCopySize = MIN(pDelTsCmd->tspecDataLen, + sizeof(cmd->params.delTsReq.tspecData)); + memcpy(&cmd->params.delTsReq.tspecData, + pDelTsCmd->tspecData, tspecCopySize); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_WMM_DELTS_REQ); + cmd->Size = wlan_cpu_to_le16(sizeof(cmd->params.delTsReq.dialogToken) + + sizeof(cmd->params.delTsReq.commandResult) + + sizeof(cmd->params.delTsReq.ieeeReasonCode) + + tspecCopySize + S_DS_GEN); + + PRINTM(INFO, "WMM: DELTS Cmd prepared\n"); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Prepare the firmware command buffer for the WMM_QUEUE_CONFIG command + * + * @param priv Pointer to the wlan_private driver data struct + * @param cmd Pointer to CMD buffer + * @param InfoBuf Void pointer cast of a wlan_cmd_wmm_queue_config_t struct + * + * @return WLAN_STATUS_SUCCESS + */ +int +wlan_cmd_wmm_queue_config(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf) +{ + wlan_cmd_wmm_queue_config_t *pQConfigCmd; + int tlvCopySize; + + pQConfigCmd = (wlan_cmd_wmm_queue_config_t *) InfoBuf; + + cmd->params.queueConfig.action = pQConfigCmd->action; + cmd->params.queueConfig.accessCategory = pQConfigCmd->accessCategory; + cmd->params.queueConfig.msduLifetimeExpiry + = wlan_cpu_to_le16(pQConfigCmd->msduLifetimeExpiry); + + tlvCopySize = MIN(pQConfigCmd->tlvBufLen, + sizeof(cmd->params.queueConfig.tlvBuffer)); + memcpy(&cmd->params.queueConfig.tlvBuffer, + pQConfigCmd->tlvBuffer, tlvCopySize); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_WMM_QUEUE_CONFIG); + cmd->Size = wlan_cpu_to_le16(sizeof(cmd->params.queueConfig.action) + + + sizeof(cmd->params.queueConfig. + accessCategory) + + + sizeof(cmd->params.queueConfig. + msduLifetimeExpiry) + + tlvCopySize + S_DS_GEN); + + PRINTM(INFO, "WMM: QUEUE CONFIG Cmd prepared\n"); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Process the WMM_QUEUE_CONFIG command response from firmware + * + * Return the firmware command response to the blocked IOCTL caller function. + * + * @param priv Pointer to the wlan_private driver data struct + * @param resp Pointer to the command response buffer with: + * - action code + * - access category + * - collected statistics if requested + * + * @return WLAN_STATUS_SUCCESS + * + * @sa wlan_wmm_queue_config_ioctl + */ +int +wlan_cmdresp_wmm_queue_config(wlan_private * priv, + const HostCmd_DS_COMMAND * resp) +{ + wlan_cmd_wmm_queue_config_t *pQConfigCmd; + const HostCmd_DS_WMM_QUEUE_CONFIG *pCmdResp; + + pQConfigCmd = + (wlan_cmd_wmm_queue_config_t *) (priv->adapter->CurCmd->pdata_buf); + pCmdResp = &resp->params.queueConfig; + + pQConfigCmd->action = pCmdResp->action; + pQConfigCmd->accessCategory = pCmdResp->accessCategory; + pQConfigCmd->msduLifetimeExpiry + = wlan_le16_to_cpu(pCmdResp->msduLifetimeExpiry); + + pQConfigCmd->tlvBufLen = (resp->Size - sizeof(pCmdResp->action) + - sizeof(pCmdResp->accessCategory) + - sizeof(pCmdResp->msduLifetimeExpiry) + - S_DS_GEN); + + memcpy(pQConfigCmd->tlvBuffer, + pCmdResp->tlvBuffer, pQConfigCmd->tlvBufLen); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Private IOCTL entry to get/set a specified AC Queue's parameters + * + * Receive a AC Queue configuration command which is used to get, set, or + * default the parameters associated with a specific WMM AC Queue. + * + * @param priv Pointer to the wlan_private driver data struct + * @param wrq A pointer to iwreq structure containing the + * wlan_ioctl_wmm_queue_config_t struct + * + * @return 0 if successful; IOCTL error code otherwise + */ +int +wlan_wmm_queue_config_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_ioctl_wmm_queue_config_t queueConfigIoctl; + wlan_cmd_wmm_queue_config_t queueConfigCmd; + int retcode; + + PRINTM(INFO, "WMM: Queue Config IOCTL Enter\n"); + + if (copy_from_user(&queueConfigIoctl, + wrq->u.data.pointer, + MIN(wrq->u.data.length, + sizeof(queueConfigIoctl))) != 0) { + /* copy_from_user failed */ + PRINTM(INFO, "WMM: Queue Config: copy from user failed\n"); + retcode = -EFAULT; + + } else { + memset(&queueConfigCmd, 0x00, sizeof(queueConfigCmd)); + + queueConfigCmd.action = queueConfigIoctl.action; + queueConfigCmd.accessCategory = queueConfigIoctl.accessCategory; + queueConfigCmd.msduLifetimeExpiry + = queueConfigIoctl.msduLifetimeExpiry; + + /* Create a rates TLV from the supportedRates[] ioctl field */ + queueConfigCmd.tlvBufLen = 0; + + retcode = PrepareAndSendCommand(priv, + HostCmd_CMD_WMM_QUEUE_CONFIG, 0, + HostCmd_OPTION_WAITFORRSP, 0, + &queueConfigCmd); + if (retcode) { + retcode = -EFAULT; + } else { + memset(&queueConfigIoctl, 0x00, sizeof(queueConfigIoctl)); + + queueConfigIoctl.action = queueConfigCmd.action; + queueConfigIoctl.accessCategory = queueConfigCmd.accessCategory; + queueConfigIoctl.msduLifetimeExpiry + = queueConfigCmd.msduLifetimeExpiry; + + wrq->u.data.length = sizeof(queueConfigIoctl); + + if (copy_to_user(wrq->u.data.pointer, + &queueConfigIoctl, sizeof(queueConfigIoctl))) { + PRINTM(INFO, "Copy to user failed\n"); + retcode = -EFAULT; + } + } + } + + return retcode; +} + +/** + * @brief Prepare the firmware command buffer for the WMM_QUEUE_STATS command + * + * @param priv Pointer to the wlan_private driver data struct + * @param cmd pointer to CMD buffer + * @param InfoBuf void pointer cast of a HostCmd_CMD_WMM_QUEUE_STATS struct + * + * @return WLAN_STATUS_SUCCESS + */ +int +wlan_cmd_wmm_queue_stats(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf) +{ + memcpy(&cmd->params.queueStats, InfoBuf, sizeof(cmd->params.queueStats)); + + cmd->Command = wlan_cpu_to_le16(HostCmd_CMD_WMM_QUEUE_STATS); + cmd->Size = wlan_cpu_to_le16(sizeof(HostCmd_DS_WMM_QUEUE_STATS) + + S_DS_GEN); + + PRINTM(INFO, "WMM: QUEUE STATS Cmd prepared\n"); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Process the WMM_QUEUE_STATS command response from firmware + * + * Return the firmware command response to the blocked IOCTL caller function. + * + * @param priv Pointer to the wlan_private driver data struct + * @param resp Pointer to the command response buffer with: + * - action code + * - access category + * - collected statistics if requested + * + * @return WLAN_STATUS_SUCCESS + * + * @sa wlan_wmm_queue_stats_ioctl + */ +int +wlan_cmdresp_wmm_queue_stats(wlan_private * priv, + const HostCmd_DS_COMMAND * resp) +{ + HostCmd_DS_WMM_QUEUE_STATS *pQueueStats = + (HostCmd_DS_WMM_QUEUE_STATS *) priv->adapter->CurCmd->pdata_buf; + + memcpy(pQueueStats, &resp->params.queueStats, (resp->Size - S_DS_GEN)); + + pQueueStats->pktCount = wlan_le16_to_cpu(pQueueStats->pktCount); + pQueueStats->pktLoss = wlan_le16_to_cpu(pQueueStats->pktLoss); + pQueueStats->avgQueueDelay = wlan_le32_to_cpu(pQueueStats->avgQueueDelay); + pQueueStats->avgTxDelay = wlan_le32_to_cpu(pQueueStats->avgTxDelay); + pQueueStats->usedTime = wlan_le32_to_cpu(pQueueStats->usedTime); + + PRINTM(INFO, "WMM: Queue Stats response: %d\n", resp->Size - S_DS_GEN); + + return WLAN_STATUS_SUCCESS; +} + +/** + * @brief Private IOCTL entry to get and start/stop queue stats on a WMM AC + * + * Receive a AC Queue statistics command from the application for a specific + * WMM AC. The command can: + * - Turn stats on + * - Turn stats off + * - Collect and clear the stats + * + * @param priv Pointer to the wlan_private driver data struct + * @param wrq A pointer to iwreq structure containing the + * wlan_ioctl_wmm_queue_stats_t struct + * + * @return 0 if successful; IOCTL error code otherwise + */ +int +wlan_wmm_queue_stats_ioctl(wlan_private * priv, struct iwreq *wrq) +{ + wlan_ioctl_wmm_queue_stats_t queueStatsIoctl; + HostCmd_DS_WMM_QUEUE_STATS queueStatsCmd; + int retcode; + + if (copy_from_user(&queueStatsIoctl, + wrq->u.data.pointer, + MIN(wrq->u.data.length, + sizeof(queueStatsIoctl))) != 0) { + /* copy_from_user failed */ + PRINTM(INFO, "WMM: Queue Stats: copy from user failed\n"); + retcode = -EFAULT; + + } else { + memcpy(&queueStatsCmd, &queueStatsIoctl, sizeof(queueStatsIoctl)); + + PRINTM(INFO, "WMM: QUEUE STATS Ioctl: %d, %d\n", + queueStatsCmd.action, queueStatsCmd.accessCategory); + + retcode = PrepareAndSendCommand(priv, + HostCmd_CMD_WMM_QUEUE_STATS, 0, + HostCmd_OPTION_WAITFORRSP, 0, + &queueStatsCmd); + if (retcode) { + retcode = -EFAULT; + } else { + if (copy_to_user(wrq->u.data.pointer, + &queueStatsCmd, + MIN(wrq->u.data.length, + sizeof(queueStatsCmd)))) { + PRINTM(INFO, "Copy to user failed\n"); + retcode = -EFAULT; + } + } + } + + if (retcode != WLAN_STATUS_SUCCESS) { + PRINTM(INFO, "WMM: QUEUE STATS Ioctl FAILED: %d, %d\n", + queueStatsIoctl.action, queueStatsIoctl.accessCategory); + } + + return retcode; +} diff --git a/drivers/net/wireless/marvell8686/wlan_wmm.h b/drivers/net/wireless/marvell8686/wlan_wmm.h new file mode 100644 index 0000000..97c24db --- /dev/null +++ b/drivers/net/wireless/marvell8686/wlan_wmm.h @@ -0,0 +1,111 @@ +/** @file wlan_wmm.h + * @brief This file contains related macros, enum, and struct + * of wmm functionalities + * + * Copyright © Marvell International Ltd. and/or its affiliates, 2003-2006 + */ +/**************************************************** +Change log: + 09/26/05: add Doxygen format comments + 04/06/06: Add TSPEC, queue metrics, and MSDU expiry support +****************************************************/ + +#ifndef __WLAN_WMM_H +#define __WLAN_WMM_H + +/** Custom indiciation message sent to the application layer for WMM changes */ +#define WMM_CONFIG_CHANGE_INDICATION "WMM_CONFIG_CHANGE.indication" + +/** Highest priority setting for a packet (uses voice AC) */ +#define WMM_HIGHEST_PRIORITY 7 + +#ifdef __KERNEL__ + +/** struct of WMM DESC */ +typedef struct +{ + u8 required; + u8 enabled; + u8 packetsQueued; + u8 queueStopped; + u32 packetsOut[MAX_AC_QUEUES]; + u32 userPriPktTxCtrl[WMM_HIGHEST_PRIORITY + 1]; /* UP: 0 to 7 */ + + struct sk_buff txSkbQ[MAX_AC_QUEUES]; + WmmAcStatus_t acStatus[MAX_AC_QUEUES]; + wlan_wmm_ac_e acDowngradedVals[MAX_AC_QUEUES]; + + /** wmm queue priority table*/ + u8 queuePriority[MAX_AC_QUEUES]; + + u8 qosinfo; +} __ATTRIB_PACK__ WMM_DESC; + +extern void wmm_map_and_add_skb(wlan_private * priv, struct sk_buff *); +extern u8 wmm_compute_driver_packet_delay(const struct sk_buff *skb); +#endif + +extern int sendWMMStatusChangeCmd(wlan_private * priv); +extern int wmm_lists_empty(wlan_private * priv); +extern void wmm_cleanup_queues(wlan_private * priv); +extern void wmm_process_tx(wlan_private * priv); + +extern void wmm_init(wlan_private * priv); +extern void wmm_setup_queues(wlan_private * priv); +extern void wmm_start_queue(wlan_private * priv); +extern void wmm_stop_queue(wlan_private * priv); +extern int wmm_is_queue_stopped(wlan_private * priv); + +extern void wmm_process_fw_iface_tx_xfer_start(wlan_private * priv); +extern void wmm_process_fw_iface_tx_xfer_end(wlan_private * priv); +extern void wmm_process_app_iface_tx(wlan_private * priv); +extern wlan_wmm_ac_e wmm_convert_tos_to_ac(int tos); +extern wlan_wmm_ac_e wmm_downgrade_ac(wlan_private * priv, + wlan_wmm_ac_e acVal); + +extern u32 wlan_wmm_process_association_req(wlan_private * priv, + u8 ** ppAssocBuf, + IEEEtypes_WmmParameter_t * + pWmmIE); + +/* + * Functions used in the cmd handling routine + */ +extern int wlan_cmd_wmm_get_status(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf); +extern int wlan_cmd_wmm_addts_req(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf); +extern int wlan_cmd_wmm_delts_req(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf); +extern int wlan_cmd_wmm_queue_config(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf); +extern int wlan_cmd_wmm_queue_stats(wlan_private * priv, + HostCmd_DS_COMMAND * cmd, void *InfoBuf); + +/* + * Functions used in the cmdresp handling routine + */ +extern int wlan_cmdresp_wmm_get_status(wlan_private * priv, + const HostCmd_DS_COMMAND * resp); +extern int wlan_cmdresp_wmm_addts_req(wlan_private * priv, + const HostCmd_DS_COMMAND * resp); +extern int wlan_cmdresp_wmm_delts_req(wlan_private * priv, + const HostCmd_DS_COMMAND * resp); +extern int wlan_cmdresp_wmm_queue_config(wlan_private * priv, + const HostCmd_DS_COMMAND * resp); +extern int wlan_cmdresp_wmm_queue_stats(wlan_private * priv, + const HostCmd_DS_COMMAND * resp); + +/* + * IOCTLs + */ +extern int wlan_wmm_enable_ioctl(wlan_private * priv, struct iwreq *wrq); +extern int wlan_wmm_queue_status_ioctl(wlan_private * priv, + struct iwreq *wrq); + +extern int wlan_wmm_addts_req_ioctl(wlan_private * priv, struct iwreq *wrq); +extern int wlan_wmm_delts_req_ioctl(wlan_private * priv, struct iwreq *wrq); +extern int wlan_wmm_queue_config_ioctl(wlan_private * priv, + struct iwreq *wrq); +extern int wlan_wmm_queue_stats_ioctl(wlan_private * priv, struct iwreq *wrq); +#endif /* __WLAN_WMM_H */ diff --git a/drivers/rtc/rtc-s3c.c b/drivers/rtc/rtc-s3c.c index de5dbcd..41001fa 100644 --- a/drivers/rtc/rtc-s3c.c +++ b/drivers/rtc/rtc-s3c.c @@ -36,7 +36,8 @@ static struct resource *s3c_rtc_mem; -static void __iomem *s3c_rtc_base; +void __iomem *s3c_rtc_base; +EXPORT_SYMBOL(s3c_rtc_base); static int s3c_rtc_alarmno = NO_IRQ; static int s3c_rtc_tickno = NO_IRQ; static int s3c_rtc_freq = 1; @@ -77,7 +78,7 @@ static irqreturn_t s3c_rtc_tickirq(int irq, void *id) } /* Update control registers */ -static void s3c_rtc_setaie(int to) +void s3c_rtc_setaie(int to) { unsigned int tmp; @@ -97,6 +98,7 @@ static void s3c_rtc_setaie(int to) disable_irq_wake(s3c_rtc_alarmno); } +EXPORT_SYMBOL(s3c_rtc_setaie); static void s3c_rtc_setpie(int to) { @@ -120,7 +122,7 @@ static void s3c_rtc_setfreq(int freq) /* Time read/write */ -static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) +int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) { unsigned int have_retried = 0; void __iomem *base = s3c_rtc_base; @@ -159,6 +161,7 @@ static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) return 0; } +EXPORT_SYMBOL(s3c_rtc_gettime); static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm) { @@ -172,10 +175,8 @@ static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm) /* we get around y2k by simply not supporting it */ - if (year < 0 || year >= 100) { - dev_err(dev, "rtc only supports 100 years\n"); - return -EINVAL; - } + if(year < 0) year = 0; + if(year >= 100) year = 99; writeb(BIN2BCD(tm->tm_sec), base + S3C2410_RTCSEC); writeb(BIN2BCD(tm->tm_min), base + S3C2410_RTCMIN); @@ -247,7 +248,7 @@ static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) return 0; } -static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) { struct rtc_time *tm = &alrm->time; void __iomem *base = s3c_rtc_base; @@ -290,6 +291,7 @@ static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) return 0; } +EXPORT_SYMBOL(s3c_rtc_setalarm); static int s3c_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) diff --git a/drivers/serial/s3c6400.c b/drivers/serial/s3c6400.c index 89e15c7..2238df2 100644 --- a/drivers/serial/s3c6400.c +++ b/drivers/serial/s3c6400.c @@ -797,10 +797,11 @@ static int s3c_serial_calcbaud(struct baud_calc *calc, calc->quot = tempdiv/1000; calc->calc = (rate / (calc->quot * 16)); calc->slot = nSlotTable[nslot]; + +return 1; printk(KERN_DEBUG "FOUND quot %d, calc %d, slot 0x%x, nslot %ld\n", calc->quot, calc->calc , calc->slot, nslot); - return 1; } static unsigned int s3c_serial_getclk(struct uart_port *port, @@ -882,15 +883,14 @@ static unsigned int s3c_serial_getclk(struct uart_port *port, printk(KERN_DEBUG "The best clksrc among candidate clksrc: %p(%s) (deviation: %d bps)\n", best, best->clksrc->name, deviation); } - printk(KERN_DEBUG "Selected clock is %p (%s) quot %d, calc %d\n", - best->clksrc, best->clksrc->name, best->quot, best->calc); - /* store results to pass back */ *clksrc = best->clksrc; *clk = best->src; *slot = best->slot; - return best->quot; +return best->quot; + printk(KERN_DEBUG "Selected clock is %p (%s) quot %d, calc %d\n", + best->clksrc, best->clksrc->name, best->quot, best->calc); } static void s3c_serial_set_termios(struct uart_port *port, diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index b469718..64d182d 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -26,9 +26,13 @@ #include #include #include +#include #include "hcd.h" #include "usb.h" +struct usb_device *sr_udev; +EXPORT_SYMBOL(sr_udev); + #ifdef CONFIG_HOTPLUG @@ -1224,13 +1228,13 @@ static int usb_autopm_do_device(struct usb_device *udev, int inc_usage_cnt) usb_pm_unlock(udev); return status; } - +EXPORT_SYMBOL(usb_autopm_do_device); /* usb_autosuspend_work - callback routine to autosuspend a USB device */ void usb_autosuspend_work(struct work_struct *work) { struct usb_device *udev = container_of(work, struct usb_device, autosuspend.work); - + sr_udev = udev; usb_autopm_do_device(udev, 0); } @@ -1282,7 +1286,7 @@ void usb_try_autosuspend_device(struct usb_device *udev) dev_vdbg(&udev->dev, "%s: cnt %d\n", __FUNCTION__, udev->pm_usage_cnt); } - +EXPORT_SYMBOL(usb_try_autosuspend_device); /** * usb_autoresume_device - immediately autoresume a USB device and its interfaces * @udev: the usb_device to autoresume @@ -1511,6 +1515,7 @@ int usb_external_resume_device(struct usb_device *udev) usb_try_autosuspend_device(udev); return status; } +EXPORT_SYMBOL(usb_external_resume_device); static int usb_suspend(struct device *dev, pm_message_t message) { diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 10e2993..f71d038 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -297,7 +297,7 @@ config USB_GADGET_S3C_HS config USB_GADGET_S3C_OTGD_HS boolean "S3C USB OTG controller" - depends on PLAT_S3C64XX && !(USB_S3C_OTG_HOST) + depends on PLAT_S3C64XX #&& !(USB_S3C_OTG_HOST) # XXX: mhfan select USB_GADGET_SELECTED select USB_GADGET_DUALSPEED help @@ -307,7 +307,7 @@ config USB_GADGET_S3C_OTGD_HS config USB_S3C tristate - depends on USB_GADGET_S3C_FS && USB_GADGET_S3C_HS && USB_GADGET_S3C_OTGD_HS + depends on USB_GADGET_S3C_FS || USB_GADGET_S3C_HS || USB_GADGET_S3C_OTGD_HS default USB_GADGET select USB_GADGET_SELECTED @@ -401,7 +401,7 @@ comment "NOTE: S3C OTG device role enables the controller driver below" config USB_S3C_OTGD_HS tristate "S3C high speed(2.0, dual-speed) USB OTG device" - depends on USB_GADGET && USB_GADGET_S3C_OTGD_HS && !(USB_S3C_OTG_HOST) + depends on USB_GADGET && USB_GADGET_S3C_OTGD_HS default y help Say "y" to link the driver statically, or "m" to build a diff --git a/drivers/usb/gadget/s3c-udc-otg-hs.c b/drivers/usb/gadget/s3c-udc-otg-hs.c index 7b95e2c..6408c21 100644 --- a/drivers/usb/gadget/s3c-udc-otg-hs.c +++ b/drivers/usb/gadget/s3c-udc-otg-hs.c @@ -128,7 +128,7 @@ static u32 ep_fifo_size2 = 1024; static int reset_available = 1; /* extern declarations in arch/arm/mach-s3c64xx*/ -#define OTGD_PHY_CLK_VALUE (0x20) /* UTMI Interface, Oscillator */ +#define OTGD_PHY_CLK_VALUE (0x02) /* UTMI Interface, Oscillator */ extern void otg_phy_init(u32 otg_phy_clk); extern void otg_phy_off(void); extern struct usb_ctrlrequest usb_ctrl; diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index c214ef5..c3401db 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -35,8 +35,11 @@ static struct clk *clk; static struct clk *usb_clk; -#undef USB_HOST_PORT2_EN -//#define USB_HOST_PORT2_EN +#if defined (CONFIG_LCD_7) +#undef USB_HOST_PORT2_EN +#else +#define USB_HOST_PORT2_EN +#endif #if defined(CONFIG_PLAT_S3C64XX) #define S3C_USB_CLKSRC_EPLL 0 @@ -45,9 +48,9 @@ extern void usb_host_clk_en(int usb_host_clksrc, u32 otg_phy_clk); #ifdef USB_HOST_PORT2_EN static struct clk *otg_clk; -#define OTGH_PHY_CLK_VALUE (0x60) /* Serial Interface, Oscillator */ +#define OTGH_PHY_CLK_VALUE (0x42) /* Serial Interface, Oscillator */ #else -#define OTGH_PHY_CLK_VALUE (0x20) /* UTMI Interface, Oscillator */ +#define OTGH_PHY_CLK_VALUE (0x02) /* UTMI Interface, Oscillator */ #endif #endif @@ -580,12 +583,58 @@ static int ohci_hcd_s3c2410_drv_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int ohci_hcd_s3c2410_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + s3c2410_stop_hc(pdev); + hcd->state = HC_STATE_SUSPENDED; + pdev->dev.power.power_state = PMSG_SUSPEND; + + return 0; +} + +static int ohci_hcd_s3c2410_drv_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + +#if defined(CONFIG_PLAT_S3C64XX) + usb_host_clk_en(S3C_USB_CLKSRC_48M, OTGH_PHY_CLK_VALUE); +#endif + + s3c2410_usb_set_power(pdev->dev.platform_data, 1, 1); + s3c2410_usb_set_power(pdev->dev.platform_data, 2, 1); + + s3c2410_start_hc(pdev, hcd); + + ohci_hcd_init(hcd_to_ohci(hcd)); + + pdev->dev.power.power_state = PMSG_ON; + usb_hcd_resume_root_hub(hcd); + + return 0; +} +#endif + static struct platform_driver ohci_hcd_s3c2410_driver = { .probe = ohci_hcd_s3c2410_drv_probe, .remove = ohci_hcd_s3c2410_drv_remove, .shutdown = usb_hcd_platform_shutdown, - /*.suspend = ohci_hcd_s3c2410_drv_suspend, */ - /*.resume = ohci_hcd_s3c2410_drv_resume, */ +#ifdef CONFIG_PM + .suspend = ohci_hcd_s3c2410_drv_suspend, + .resume = ohci_hcd_s3c2410_drv_resume, +#endif .driver = { .owner = THIS_MODULE, .name = "s3c2410-ohci", diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c index cfb8590..efca605 100644 --- a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c @@ -29,9 +29,43 @@ ****************************************************************************/ #include "s3c-otg-hcdi-hcd.h" +#include +#include +#include static SPINLOCK_t otg_hcd_spin_lock = SPIN_LOCK_INIT; +struct work_struct port_work; +struct work_struct disconnect_work; +int suspend_yet; +extern struct usb_device *sr_udev; +extern void usb_try_autosuspend_device(struct usb_device *udev); +extern int usb_external_resume_device(struct usb_device *udev); +extern int usb_autopm_do_device(struct usb_device *udev, int inc_usage_cnt); + +void do_port_work(struct work_struct* work) +{ + if(suspend_yet == 0) + return; + else + usb_external_resume_device(sr_udev); + return; +} + +void do_disconnect_work(struct work_struct* work) +{ + if(suspend_yet == 1) + return; + else + usb_try_autosuspend_device(sr_udev); + return; +} + +EXPORT_SYMBOL(suspend_yet); +EXPORT_SYMBOL(do_port_work); +EXPORT_SYMBOL(do_disconnect_work); +EXPORT_SYMBOL(port_work); +EXPORT_SYMBOL(disconnect_work); /** * otg_hcd_init_modules() * @@ -44,6 +78,8 @@ int otg_hcd_init_modules(void) { u32 spin_lock_flag = 0; + INIT_WORK(&port_work, do_port_work); + INIT_WORK(&disconnect_work, do_disconnect_work); otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "OTGHCD_InitModuless \n"); spin_lock_irg_save_otg(&otg_hcd_spin_lock, spin_lock_flag); @@ -60,7 +96,7 @@ int otg_hcd_init_modules(void) * void otg_hcd_deinit_modules(void) * * @brief call other modules' de-init functions - * + * i * @return PASS : If success \n * FAIL : If fail \n */ @@ -165,6 +201,10 @@ void s3c6410_otghcd_stop(struct usb_hcd *hcd) } //------------------------------------------------------------------------------- +void s3c6410_otghcd_resume(struct usb_hcd *usb_hcd_p) +{ +} + /** * void s3c6410_otghcd_shutdown(struct usb_hcd *hcd) * @@ -174,8 +214,7 @@ void s3c6410_otghcd_stop(struct usb_hcd *hcd) * */ void s3c6410_otghcd_shutdown(struct usb_hcd *usb_hcd_p) -{ - +{ u32 spin_lock_flag = 0; otg_dbg(OTG_DBG_OTGHCDI_HCD, "s3c6410_otghcd_shutdown \n"); @@ -583,7 +622,6 @@ s3c6410_otghcd_hub_control int s3c6410_otghcd_bus_suspend(struct usb_hcd *hcd) { u32 spin_lock_flag = 0; - otg_dbg(OTG_DBG_OTGHCDI_HCD, "s3c6410_otghcd_bus_suspend \n"); spin_lock_irg_save_otg(&otg_hcd_spin_lock, spin_lock_flag); bus_suspend(); @@ -603,9 +641,8 @@ int s3c6410_otghcd_bus_suspend(struct usb_hcd *hcd) * @return USB_ERR_SUCCESS \n */ int s3c6410_otghcd_bus_resume(struct usb_hcd *hcd) -{ +{ u32 spin_lock_flag = 0; - otg_dbg(OTG_DBG_OTGHCDI_HCD, "s3c6410_otghcd_bus_resume \n"); spin_lock_irg_save_otg(&otg_hcd_spin_lock, spin_lock_flag); if(bus_resume() != USB_ERR_SUCCESS) diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h index 031ebf6..f670990 100644 --- a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h @@ -132,7 +132,7 @@ static const struct hc_driver s3c6410_otg_hc_driver = { .hub_status_data = s3c6410_otghcd_hub_status_data, .hub_control = s3c6410_otghcd_hub_control, //.hub_irq_enable = - .bus_suspend = s3c6410_otghcd_bus_suspend, + //.bus_suspend = s3c6410_otghcd_bus_suspend, .bus_resume = s3c6410_otghcd_bus_resume, .start_port_reset = s3c6410_otghcd_start_port_reset, }; diff --git a/drivers/usb/host/s3c-otg/s3c-otg-isr.c b/drivers/usb/host/s3c-otg/s3c-otg-isr.c index b2946a5..ba2c359 100644 --- a/drivers/usb/host/s3c-otg/s3c-otg-isr.c +++ b/drivers/usb/host/s3c-otg/s3c-otg-isr.c @@ -28,6 +28,9 @@ ****************************************************************************/ #include "s3c-otg-isr.h" +#include +#include +#include /** * void otg_handle_interrupt(void) @@ -44,6 +47,9 @@ extern port_flags_t port_flag; extern bool ch_halt; +extern int suspend_yet; +extern struct work_struct port_work; +extern struct work_struct disconnect_work; __inline__ void otg_handle_interrupt(void) { @@ -64,6 +70,8 @@ __inline__ void otg_handle_interrupt(void) port_flag.b.port_connect_status_change = 1; port_flag.b.port_connect_status = 0; clearIntr.b.disconnect = 1; + if(suspend_yet == 0) + schedule_work(&disconnect_work); } if (gintsts.b.conidstschng) @@ -89,6 +97,8 @@ __inline__ void otg_handle_interrupt(void) // Read Only //otg_dbg(OTG_DBG_ISR, "Port Interrupt\n"); process_port_intr(); + if(suspend_yet == 1) + schedule_work(&port_work); } diff --git a/drivers/usb/host/s3c-otg/s3c-otg-oci.c b/drivers/usb/host/s3c-otg/s3c-otg-oci.c index 5e927c0..d054660 100644 --- a/drivers/usb/host/s3c-otg/s3c-otg-oci.c +++ b/drivers/usb/host/s3c-otg/s3c-otg-oci.c @@ -609,7 +609,7 @@ int oci_sys_init(void) syscon_reg->HCLK_GATE |= (0x1<<20); syscon_reg->OTHERS |= (0x1<<16); otgphy_reg->OPHYPWR = 0x0; - otgphy_reg->OPHYCLK = 0x20; + otgphy_reg->OPHYCLK = 0x02; otgphy_reg->ORSTCON = 0x1; mdelay(80); diff --git a/drivers/usb/host/s3c-otg/s3c-otg-roothub.c b/drivers/usb/host/s3c-otg/s3c-otg-roothub.c index c38d115..e2fbf0f 100644 --- a/drivers/usb/host/s3c-otg/s3c-otg-roothub.c +++ b/drivers/usb/host/s3c-otg/s3c-otg-roothub.c @@ -30,7 +30,7 @@ #include "s3c-otg-roothub.h" port_flags_t port_flag; - +extern int suspend_yet; /** * int get_otg_port_status(const u8 port, char* status) * @@ -393,6 +393,7 @@ __inline__ int root_hub_feature(const u8 port, */ void bus_suspend(void) { + suspend_yet = 1; hprt_t hprt; pcgcctl_t pcgcctl; @@ -402,14 +403,6 @@ void bus_suspend(void) hprt.b.prtsusp = 1; update_reg_32(HPRT, hprt.d32); - pcgcctl.b.pwrclmp = 1; - update_reg_32(PCGCCTL,pcgcctl.d32); - udelay(1); - - pcgcctl.b.rstpdwnmodule = 1; - update_reg_32(PCGCCTL,pcgcctl.d32); - udelay(1); - pcgcctl.b.stoppclk = 1; update_reg_32(PCGCCTL,pcgcctl.d32); udelay(1); @@ -430,6 +423,7 @@ void bus_suspend(void) */ int bus_resume(void) { + suspend_yet = 0; /* hprt_t hprt; pcgcctl_t pcgcctl; diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 9c7eb61..055c299 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -4,6 +4,15 @@ comment "USB Miscellaneous drivers" depends on USB +config USB_JZ4755 + tristate "JZ4755 USB Boot interface support" + depends on USB + ---help--- + This driver loads firmware to JZ4755. + + After firmware load the JZ4755 can run Mplayer. + + config USB_EMI62 tristate "EMI 6|2m USB Audio interface support" depends on USB diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index b68e6b7..85f3131 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_TEST) += usbtest.o obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o obj-$(CONFIG_USB_USS720) += uss720.o +obj-$(CONFIG_USB_JZ4755) += jz4755.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index bbbe1b9..73b969a 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -83,43 +83,236 @@ static int option_send_setup(struct usb_serial_port *port); #define OPTION_PRODUCT_VIPER 0x6600 #define OPTION_PRODUCT_VIPER_BUS 0x6601 #define OPTION_PRODUCT_GT_MAX_READY 0x6701 -#define OPTION_PRODUCT_GT_MAX 0x6711 #define OPTION_PRODUCT_FUJI_MODEM_LIGHT 0x6721 #define OPTION_PRODUCT_FUJI_MODEM_GT 0x6741 #define OPTION_PRODUCT_FUJI_MODEM_EX 0x6761 -#define OPTION_PRODUCT_FUJI_NETWORK_LIGHT 0x6731 -#define OPTION_PRODUCT_FUJI_NETWORK_GT 0x6751 -#define OPTION_PRODUCT_FUJI_NETWORK_EX 0x6771 #define OPTION_PRODUCT_KOI_MODEM 0x6800 -#define OPTION_PRODUCT_KOI_NETWORK 0x6811 #define OPTION_PRODUCT_SCORPION_MODEM 0x6901 -#define OPTION_PRODUCT_SCORPION_NETWORK 0x6911 #define OPTION_PRODUCT_ETNA_MODEM 0x7001 -#define OPTION_PRODUCT_ETNA_NETWORK 0x7011 #define OPTION_PRODUCT_ETNA_MODEM_LITE 0x7021 #define OPTION_PRODUCT_ETNA_MODEM_GT 0x7041 #define OPTION_PRODUCT_ETNA_MODEM_EX 0x7061 -#define OPTION_PRODUCT_ETNA_NETWORK_LITE 0x7031 -#define OPTION_PRODUCT_ETNA_NETWORK_GT 0x7051 -#define OPTION_PRODUCT_ETNA_NETWORK_EX 0x7071 #define OPTION_PRODUCT_ETNA_KOI_MODEM 0x7100 -#define OPTION_PRODUCT_ETNA_KOI_NETWORK 0x7111 +#define OPTION_PRODUCT_GTM380_MODEM 0x7201 #define HUAWEI_VENDOR_ID 0x12D1 #define HUAWEI_PRODUCT_E600 0x1001 #define HUAWEI_PRODUCT_E220 0x1003 #define HUAWEI_PRODUCT_E220BIS 0x1004 +#define HUAWEI_PRODUCT_E1401 0x1401 +#define HUAWEI_PRODUCT_E1402 0x1402 +#define HUAWEI_PRODUCT_E1403 0x1403 +#define HUAWEI_PRODUCT_E1404 0x1404 +#define HUAWEI_PRODUCT_E1405 0x1405 +#define HUAWEI_PRODUCT_E1406 0x1406 +#define HUAWEI_PRODUCT_E1407 0x1407 +#define HUAWEI_PRODUCT_E1408 0x1408 +#define HUAWEI_PRODUCT_E1409 0x1409 +#define HUAWEI_PRODUCT_E140A 0x140A +#define HUAWEI_PRODUCT_E140B 0x140B +#define HUAWEI_PRODUCT_E140C 0x140C +#define HUAWEI_PRODUCT_E140D 0x140D +#define HUAWEI_PRODUCT_E140E 0x140E +#define HUAWEI_PRODUCT_E140F 0x140F +#define HUAWEI_PRODUCT_E1410 0x1410 +#define HUAWEI_PRODUCT_E1411 0x1411 +#define HUAWEI_PRODUCT_E1412 0x1412 +#define HUAWEI_PRODUCT_E1413 0x1413 +#define HUAWEI_PRODUCT_E1414 0x1414 +#define HUAWEI_PRODUCT_E1415 0x1415 +#define HUAWEI_PRODUCT_E1416 0x1416 +#define HUAWEI_PRODUCT_E1417 0x1417 +#define HUAWEI_PRODUCT_E1418 0x1418 +#define HUAWEI_PRODUCT_E1419 0x1419 +#define HUAWEI_PRODUCT_E141A 0x141A +#define HUAWEI_PRODUCT_E141B 0x141B +#define HUAWEI_PRODUCT_E141C 0x141C +#define HUAWEI_PRODUCT_E141D 0x141D +#define HUAWEI_PRODUCT_E141E 0x141E +#define HUAWEI_PRODUCT_E141F 0x141F +#define HUAWEI_PRODUCT_E1420 0x1420 +#define HUAWEI_PRODUCT_E1421 0x1421 +#define HUAWEI_PRODUCT_E1422 0x1422 +#define HUAWEI_PRODUCT_E1423 0x1423 +#define HUAWEI_PRODUCT_E1424 0x1424 +#define HUAWEI_PRODUCT_E1425 0x1425 +#define HUAWEI_PRODUCT_E1426 0x1426 +#define HUAWEI_PRODUCT_E1427 0x1427 +#define HUAWEI_PRODUCT_E1428 0x1428 +#define HUAWEI_PRODUCT_E1429 0x1429 +#define HUAWEI_PRODUCT_E142A 0x142A +#define HUAWEI_PRODUCT_E142B 0x142B +#define HUAWEI_PRODUCT_E142C 0x142C +#define HUAWEI_PRODUCT_E142D 0x142D +#define HUAWEI_PRODUCT_E142E 0x142E +#define HUAWEI_PRODUCT_E142F 0x142F +#define HUAWEI_PRODUCT_E1430 0x1430 +#define HUAWEI_PRODUCT_E1431 0x1431 +#define HUAWEI_PRODUCT_E1432 0x1432 +#define HUAWEI_PRODUCT_E1433 0x1433 +#define HUAWEI_PRODUCT_E1434 0x1434 +#define HUAWEI_PRODUCT_E1435 0x1435 +#define HUAWEI_PRODUCT_E1436 0x1436 +#define HUAWEI_PRODUCT_E1437 0x1437 +#define HUAWEI_PRODUCT_E1438 0x1438 +#define HUAWEI_PRODUCT_E1439 0x1439 +#define HUAWEI_PRODUCT_E143A 0x143A +#define HUAWEI_PRODUCT_E143B 0x143B +#define HUAWEI_PRODUCT_E143C 0x143C +#define HUAWEI_PRODUCT_E143D 0x143D +#define HUAWEI_PRODUCT_E143E 0x143E +#define HUAWEI_PRODUCT_E143F 0x143F + +#define QUANTA_VENDOR_ID 0x0408 +#define QUANTA_PRODUCT_Q101 0xEA02 +#define QUANTA_PRODUCT_Q111 0xEA03 +#define QUANTA_PRODUCT_GLX 0xEA04 +#define QUANTA_PRODUCT_GKE 0xEA05 +#define QUANTA_PRODUCT_GLE 0xEA06 #define NOVATELWIRELESS_VENDOR_ID 0x1410 + +/* YISO PRODUCTS */ + +#define YISO_VENDOR_ID 0x0EAB +#define YISO_PRODUCT_U893 0xC893 + +/* MERLIN EVDO PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_V640 0x1100 +#define NOVATELWIRELESS_PRODUCT_V620 0x1110 +#define NOVATELWIRELESS_PRODUCT_V740 0x1120 +#define NOVATELWIRELESS_PRODUCT_V720 0x1130 + +/* MERLIN HSDPA/HSPA PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_U730 0x1400 +#define NOVATELWIRELESS_PRODUCT_U740 0x1410 +#define NOVATELWIRELESS_PRODUCT_U870 0x1420 +#define NOVATELWIRELESS_PRODUCT_XU870 0x1430 +#define NOVATELWIRELESS_PRODUCT_X950D 0x1450 + +/* EXPEDITE PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_EV620 0x2100 +#define NOVATELWIRELESS_PRODUCT_ES720 0x2110 +#define NOVATELWIRELESS_PRODUCT_E725 0x2120 +#define NOVATELWIRELESS_PRODUCT_ES620 0x2130 +#define NOVATELWIRELESS_PRODUCT_EU730 0x2400 +#define NOVATELWIRELESS_PRODUCT_EU740 0x2410 +#define NOVATELWIRELESS_PRODUCT_EU870D 0x2420 + +/* OVATION PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_MC727 0x4100 +#define NOVATELWIRELESS_PRODUCT_MC950D 0x4400 +#define NOVATELWIRELESS_PRODUCT_U727 0x5010 +#define NOVATELWIRELESS_PRODUCT_MC760 0x6000 + +/* FUTURE NOVATEL PRODUCTS */ +#define NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED 0X6001 +#define NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED 0X7000 +#define NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED 0X7001 +#define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED 0X8000 +#define NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED 0X8001 +#define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED 0X9000 +#define NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED 0X9001 +#define NOVATELWIRELESS_PRODUCT_GLOBAL 0XA001 + +/* AMOI PRODUCTS */ +#define AMOI_VENDOR_ID 0x1614 +#define AMOI_PRODUCT_H01 0x0800 +#define AMOI_PRODUCT_H01A 0x7002 +#define AMOI_PRODUCT_H02 0x0802 + #define DELL_VENDOR_ID 0x413C +/* Dell modems */ +#define DELL_PRODUCT_5700_MINICARD 0x8114 +#define DELL_PRODUCT_5500_MINICARD 0x8115 +#define DELL_PRODUCT_5505_MINICARD 0x8116 +#define DELL_PRODUCT_5700_EXPRESSCARD 0x8117 +#define DELL_PRODUCT_5510_EXPRESSCARD 0x8118 + +#define DELL_PRODUCT_5700_MINICARD_SPRINT 0x8128 +#define DELL_PRODUCT_5700_MINICARD_TELUS 0x8129 + +#define DELL_PRODUCT_5720_MINICARD_VZW 0x8133 +#define DELL_PRODUCT_5720_MINICARD_SPRINT 0x8134 +#define DELL_PRODUCT_5720_MINICARD_TELUS 0x8135 +#define DELL_PRODUCT_5520_MINICARD_CINGULAR 0x8136 +#define DELL_PRODUCT_5520_MINICARD_GENERIC_L 0x8137 +#define DELL_PRODUCT_5520_MINICARD_GENERIC_I 0x8138 + +#define DELL_PRODUCT_5730_MINICARD_SPRINT 0x8180 +#define DELL_PRODUCT_5730_MINICARD_TELUS 0x8181 +#define DELL_PRODUCT_5730_MINICARD_VZW 0x8182 + +#define KYOCERA_VENDOR_ID 0x0c88 +#define KYOCERA_PRODUCT_KPC650 0x17da +#define KYOCERA_PRODUCT_KPC680 0x180a + #define ANYDATA_VENDOR_ID 0x16d5 +#define ANYDATA_PRODUCT_ADU_620UW 0x6202 #define ANYDATA_PRODUCT_ADU_E100A 0x6501 #define ANYDATA_PRODUCT_ADU_500A 0x6502 +#define AXESSTEL_VENDOR_ID 0x1726 +#define AXESSTEL_PRODUCT_MV110H 0x1000 + +#define ONDA_VENDOR_ID 0x19d2 +#define ONDA_PRODUCT_MSA501HS 0x0001 +#define ONDA_PRODUCT_ET502HS 0x0002 +#define ONDA_PRODUCT_MT503HS 0x2000 + #define BANDRICH_VENDOR_ID 0x1A8D #define BANDRICH_PRODUCT_C100_1 0x1002 #define BANDRICH_PRODUCT_C100_2 0x1003 +#define BANDRICH_PRODUCT_1004 0x1004 +#define BANDRICH_PRODUCT_1005 0x1005 +#define BANDRICH_PRODUCT_1006 0x1006 +#define BANDRICH_PRODUCT_1007 0x1007 +#define BANDRICH_PRODUCT_1008 0x1008 +#define BANDRICH_PRODUCT_1009 0x1009 +#define BANDRICH_PRODUCT_100A 0x100a + +#define BANDRICH_PRODUCT_100B 0x100b +#define BANDRICH_PRODUCT_100C 0x100c +#define BANDRICH_PRODUCT_100D 0x100d +#define BANDRICH_PRODUCT_100E 0x100e + +#define BANDRICH_PRODUCT_100F 0x100f +#define BANDRICH_PRODUCT_1010 0x1010 +#define BANDRICH_PRODUCT_1011 0x1011 +#define BANDRICH_PRODUCT_1012 0x1012 + +#define AMOI_VENDOR_ID 0x1614 +#define AMOI_PRODUCT_9508 0x0800 + +#define QUALCOMM_VENDOR_ID 0x05C6 + +#define MAXON_VENDOR_ID 0x16d8 + +#define TELIT_VENDOR_ID 0x1bc7 +#define TELIT_PRODUCT_UC864E 0x1003 + +/* ZTE PRODUCTS */ +#define ZTE_VENDOR_ID 0x19d2 +#define ZTE_PRODUCT_MF622 0x0001 +#define ZTE_PRODUCT_MU350 0x0003 +#define ZTE_PRODUCT_MF628 0x0015 +#define ZTE_PRODUCT_MF626 0x0031 +#define ZTE_PRODUCT_AC560 0x0073 +#define ZTE_PRODUCT_CDMA_TECH 0xfffe + +#define BENQ_VENDOR_ID 0x04a5 +#define BENQ_PRODUCT_H10 0x4068 + +#define DLINK_VENDOR_ID 0x1186 +#define DLINK_PRODUCT_DWM_652 0x3e04 + +#define DTT_VENDOR_ID 0x1ab7 +#define DTT_PRODUCT_AIRCARD_901 0x2000 + +/* TOSHIBA PRODUCTS */ +#define TOSHIBA_VENDOR_ID 0x0930 +#define TOSHIBA_PRODUCT_HSDPA_MINICARD 0x1302 static struct usb_device_id option_ids[] = { { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) }, @@ -136,56 +329,213 @@ static struct usb_device_id option_ids[] = { { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_VIPER) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_VIPER_BUS) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GT_MAX_READY) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GT_MAX) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_LIGHT) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_GT) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_MODEM_EX) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_NETWORK_LIGHT) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_NETWORK_GT) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_FUJI_NETWORK_EX) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_KOI_MODEM) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_KOI_NETWORK) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_SCORPION_MODEM) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_SCORPION_NETWORK) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_NETWORK) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_LITE) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_GT) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_MODEM_EX) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_NETWORK_LITE) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_NETWORK_GT) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_NETWORK_EX) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_KOI_MODEM) }, - { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_KOI_NETWORK) }, - { USB_DEVICE(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600) }, + { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_GTM380_MODEM) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_Q101) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_Q111) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLX) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GKE) }, + { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220BIS, 0xff, 0xff, 0xff) }, - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1100) }, /* Novatel Merlin XS620/S640 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1110) }, /* Novatel Merlin S620 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1120) }, /* Novatel Merlin EX720 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1130) }, /* Novatel Merlin S720 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1400) }, /* Novatel U730 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1410) }, /* Novatel U740 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1420) }, /* Novatel EU870 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1430) }, /* Novatel Merlin XU870 HSDPA/3G */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2100) }, /* Novatel EV620 CDMA/EV-DO */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2110) }, /* Novatel Merlin ES620 / Merlin ES720 / Ovation U720 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2130) }, /* Novatel Merlin ES620 SM Bus */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2410) }, /* Novatel EU740 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x4100) }, /* Novatel U727 */ - { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x4400) }, /* Novatel MC950 */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8114) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite EV620 CDMA/EV-DO */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8115) }, /* Dell Wireless 5500 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8116) }, /* Dell Wireless 5505 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8117) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO ExpressCard == Novatel Merlin XV620 CDMA/EV-DO */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8118) }, /* Dell Wireless 5510 Mobile Broadband HSDPA ExpressCard == Novatel Merlin XU870 HSDPA/3G */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8128) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite E720 CDMA/EV-DO */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8136) }, /* Dell Wireless HSDPA 5520 == Novatel Expedite EU860D */ - { USB_DEVICE(DELL_VENDOR_ID, 0x8137) }, /* Dell Wireless HSDPA 5520 */ - { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_E100A) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1401, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1402, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1403, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1404, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1405, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1406, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1407, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1408, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1409, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140A, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140B, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140C, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140D, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140E, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140F, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1410, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1411, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1412, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1413, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1414, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1415, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1416, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1417, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1418, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1419, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141A, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141B, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141C, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141D, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141E, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141F, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1420, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1421, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1422, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1423, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1424, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1425, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1426, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1427, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1428, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1429, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142A, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142B, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142C, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142D, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142E, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142F, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1430, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1431, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1432, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1433, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1434, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1435, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1436, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1437, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1438, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1439, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143A, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143B, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143C, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143D, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143E, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143F, 0xff, 0xff, 0xff) }, + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_9508) }, + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V640) }, /* Novatel Merlin V640/XV620 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V620) }, /* Novatel Merlin V620/S620 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V740) }, /* Novatel Merlin EX720/V740/X720 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V720) }, /* Novatel Merlin V720/S720/PC720 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U730) }, /* Novatel U730/U740 (VF version) */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U740) }, /* Novatel U740 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U870) }, /* Novatel U870 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_XU870) }, /* Novatel Merlin XU870 HSDPA/3G */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_X950D) }, /* Novatel X950D */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EV620) }, /* Novatel EV620/ES620 CDMA/EV-DO */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_ES720) }, /* Novatel ES620/ES720/U720/USB720 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_E725) }, /* Novatel E725/E726 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_ES620) }, /* Novatel Merlin ES620 SM Bus */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU730) }, /* Novatel EU730 and Vodafone EU740 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU740) }, /* Novatel non-Vodafone EU740 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EU870D) }, /* Novatel EU850D/EU860D/EU870D */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC950D) }, /* Novatel MC930D/MC950D */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC727) }, /* Novatel MC727/U727/USB727 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_U727) }, /* Novatel MC727/U727/USB727 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC760) }, /* Novatel MC760/U760/USB760 */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_FULLSPEED) }, /* Novatel HSPA product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_FULLSPEED) }, /* Novatel EVDO Embedded product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_FULLSPEED) }, /* Novatel HSPA Embedded product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_HIGHSPEED) }, /* Novatel EVDO product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_HIGHSPEED) }, /* Novatel HSPA product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_EVDO_EMBEDDED_HIGHSPEED) }, /* Novatel EVDO Embedded product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_HSPA_EMBEDDED_HIGHSPEED) }, /* Novatel HSPA Embedded product */ + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_GLOBAL) }, /* Novatel Global product */ + + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01) }, + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01A) }, + { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H02) }, + + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5500_MINICARD) }, /* Dell Wireless 5500 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5505_MINICARD) }, /* Dell Wireless 5505 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_EXPRESSCARD) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO ExpressCard == Novatel Merlin XV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5510_EXPRESSCARD) }, /* Dell Wireless 5510 Mobile Broadband HSDPA ExpressCard == Novatel Merlin XU870 HSDPA/3G */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD_SPRINT) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite E720 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5700_MINICARD_TELUS) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite ET620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_VZW) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_SPRINT) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5720_MINICARD_TELUS) }, /* Dell Wireless 5720 == Novatel EV620 CDMA/EV-DO */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_CINGULAR) }, /* Dell Wireless HSDPA 5520 == Novatel Expedite EU860D */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_GENERIC_L) }, /* Dell Wireless HSDPA 5520 */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5520_MINICARD_GENERIC_I) }, /* Dell Wireless 5520 Voda I Mobile Broadband (3G HSDPA) Minicard */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_SPRINT) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_TELUS) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE(DELL_VENDOR_ID, DELL_PRODUCT_5730_MINICARD_VZW) }, /* Dell Wireless 5730 Mobile Broadband EVDO/HSPA Mini-Card */ + { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_E100A) }, /* ADU-E100, ADU-310 */ { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_500A) }, + { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_620UW) }, + { USB_DEVICE(AXESSTEL_VENDOR_ID, AXESSTEL_PRODUCT_MV110H) }, + /*{ USB_DEVICE(ONDA_VENDOR_ID, ONDA_PRODUCT_MSA501HS) }, + { USB_DEVICE(ONDA_VENDOR_ID, ONDA_PRODUCT_ET502HS) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0003) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0004) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0005) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0006) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0007) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0008) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0009) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x000a) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x000b) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x000c) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x000d) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x000e) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x000f) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0010) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0011) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0012) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0013) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0014) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0015) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0016) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0017) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0018) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0019) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0020) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0021) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0022) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0023) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0024) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0025) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0026) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0027) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0028) }, + { USB_DEVICE(ONDA_VENDOR_ID, 0x0029) }, + { USB_DEVICE(ONDA_VENDOR_ID, ONDA_PRODUCT_MT503HS) }, */ + { USB_DEVICE(YISO_VENDOR_ID, YISO_PRODUCT_U893) }, { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_1) }, { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_2) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1004) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1005) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1006) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1007) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1008) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1009) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100A) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100B) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100C) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100D) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100E) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_100F) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1010) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1011) }, + { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_1012) }, + { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC650) }, + { USB_DEVICE(KYOCERA_VENDOR_ID, KYOCERA_PRODUCT_KPC680) }, + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6000)}, /* ZTE AC8700 */ + { USB_DEVICE(QUALCOMM_VENDOR_ID, 0x6613)}, /* Onda H600/ZTE MF330 */ + { USB_DEVICE(MAXON_VENDOR_ID, 0x6280) }, /* BP3-USB & BP3-EXT HSDPA */ + { USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_UC864E) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_MF622) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_MU350) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_MF626) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_AC560) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_MF628) }, + { USB_DEVICE(ZTE_VENDOR_ID, ZTE_PRODUCT_CDMA_TECH) }, + { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_H10) }, + { USB_DEVICE(DLINK_VENDOR_ID, DLINK_PRODUCT_DWM_652) }, + { USB_DEVICE(DTT_VENDOR_ID, DTT_PRODUCT_AIRCARD_901) }, + { USB_DEVICE(0x1da5, 0x4515) }, /* BenQ H20 */ + { USB_DEVICE(TOSHIBA_VENDOR_ID, TOSHIBA_PRODUCT_HSDPA_MINICARD ) }, /* Toshiba 3G HSDPA == Novatel Expedite EU870D MiniCard */ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, option_ids); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 4212cd0..ae66ace 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -231,6 +231,16 @@ config FB_S3C_LTS222QV ---help--- TBA +config FB_S3C_A070VW04 + bool "A070VW04" + ---help--- + TBA + +config FB_S3C_TD043MTEX + bool "TD043MTEX" + ---help--- + TBA + endchoice config FB_S3C_BPP diff --git a/drivers/video/s3c/Makefile b/drivers/video/s3c/Makefile index 3f52d72..cb6c218 100644 --- a/drivers/video/s3c/Makefile +++ b/drivers/video/s3c/Makefile @@ -9,4 +9,6 @@ obj-$(CONFIG_PLAT_S5PC1XX) += s5pfb_fimd5x.o obj-$(CONFIG_FB_S3C_LTS222QV) += s3cfb_lts222qv.o obj-$(CONFIG_FB_S3C_LTV350QV) += s3cfb_ltv350qv.o obj-$(CONFIG_FB_S3C_LTE480WV) += s3cfb_lte480wv.o +obj-$(CONFIG_FB_S3C_A070VW04) += s3cfb_a070vw04.o +obj-$(CONFIG_FB_S3C_TD043MTEX) += s3cfb_td043mtex.o diff --git a/drivers/video/s3c/colorbar.c b/drivers/video/s3c/colorbar.c new file mode 100644 index 0000000..69e7400 --- /dev/null +++ b/drivers/video/s3c/colorbar.c @@ -0,0 +1,423 @@ +/**************************************************************** + * $ID: colorbar.c Sun, 23 Dec 2007 21:01:11 +0800 mhfan $ * + * * + * Description: * + * * + * Maintainer: ·¶ÃÀ»Ô(MeiHui FAN) * + * * + * Copyright (C) 2007~2008 HHTech * + * www.hhcn.com, www.hhcn.org * + * All rights reserved. * + * * + * This file is free software; * + * you are free to modify and/or redistribute it * + * under the terms of the GNU General Public Licence (GPL). * + ****************************************************************/ + + +static inline void memset32(uint32_t* ptr, uint32_t val, int32_t siz) +{ + for (siz >>= 4; 0 < siz--; ptr += 4) { + ptr[0] = val; ptr[1] = val; ptr[2] = val; ptr[3] = val; + } +} + + +#define UYVY_WHITE (0xEB80EB80) +#define UYVY_YELLOW (0xD292D210) +#define UYVY_CYAN (0xAA10AAA6) +#define UYVY_GREEN (0x91229136) +#define UYVY_MAGENTA (0x6ADE6ACA) +#define UYVY_RED (0x51F0515A) +#define UYVY_BLUE (0x296E29F0) +#define UYVY_BLACK (0x10801080) + + +#ifdef CONFIG_ITU656_OUTPUT +#ifdef NTSC +#define FIELD1_VBL_START (1) +#define FIELD1_VBL_FINISH (20) +#define FIELD2_VBL_START (264) +#define FIELD2_VBL_FINISH (283) +#define FIELD1_START_LINE (4) +#define FIELD2_START_LINE (266) + +#define LINES_PER_VBL (20) // XXX: 19.5 +#define LINES_PER_FIELD (243) +#define LINES_PER_FRAME (525) + +#define LINE_BLANK_BYTES (268) +#define FRAMES_PER_SECOND (30) + +#elif defined(PAL) + +#define FIELD1_VBL_START (624) +#define FIELD1_VBL_FINISH (23) +#define FIELD2_VBL_START (311) +#define FIELD2_VBL_FINISH (336) +#define FIELD1_START_LINE (1) +#define FIELD2_START_LINE (313) + +#define LINES_PER_VBL (24) // XXX: 24.5 +#define LINES_PER_FIELD (288) +#define LINES_PER_FRAME (625) + +#define LINE_BLANK_BYTES (280) +#define FRAMES_PER_SECOND (25) + +#else +#error "Please define `NTSC' or `PAL'!" +#endif//NTSC + +#ifdef ACTIVE_VIDEO_ONLY +#undef LINES_PER_FRAME +#undef FIELD1_VBL_START +#undef FIELD2_VBL_START +#undef FIELD1_VBL_FINISH +#undef FIELD2_VBL_FINISH +#undef FIELD1_START_LINE +#undef FIELD2_START_LINE +#undef LINE_BLANK_BYTES +#undef LINES_PER_VBL + +#define FIELD1_VBL_START (0) +#define FIELD2_VBL_START (0) +#define FIELD1_VBL_FINISH (0) +#define FIELD2_VBL_FINISH (0) +#define FIELD1_START_LINE (1) +#define FIELD2_START_LINE (289) +#define LINE_BLANK_BYTES (0) +#define LINES_PER_VBL (0) + +#define LINES_PER_FRAME (FRAME_ACTIVE_LINES) +#define SAV_BYTES (0) +#define EAV_BYTES (0) + +#else// XXX: +#define SAV_BYTES (4) +#define EAV_BYTES (4) +#endif//ACTIVE_VIDEO_ONLY + +#define BITS_PER_PIXEL (16) +#define PIXELS_PER_LINE (720) + +#define FRAME_ACTIVE_LINES (LINES_PER_FIELD << 1) +#define LINE_ACTIVE_BYTES (PIXELS_PER_LINE << 1) +#define BYTES_PER_PIXEL ((BITS_PER_PIXEL + 7) >> 3) +#define LINE_CONTROL_BYTES (SAV_BYTES + LINE_BLANK_BYTES + EAV_BYTES) +#define BYTES_PER_LINE (LINE_CONTROL_BYTES + (PIXELS_PER_LINE << 1)) +#define BYTES_PER_FIELD (BYTES_PER_LINE * LINES_PER_FIELD) +#define BYTES_PER_FRAME (BYTES_PER_LINE * LINES_PER_FRAME) + +static inline unsigned* itu656_line(unsigned* ptr, int init) +{ +/* + * Offsets for ITU656/CCIR601 frame line definition: + * + * |<--EAV-->|<----BLANKING---->|<--SAV-->|<--------ACTIVE FIELD-------->| + * 4 268/280 4 1440 + */ + short i; +#ifdef ACTIVE_VIDEO_ONLY + short V = 0; +#else + static short L = 0, V = 1, F = 1; + + switch (++L) { + // F-digital field identification: + case FIELD1_START_LINE : F = 0; break; + case FIELD2_START_LINE : F = 1; break; + // V-digital field blanking: + case FIELD1_VBL_START : V = 1; break; + case FIELD1_VBL_FINISH : V = 0; break; + case FIELD2_VBL_START : V = 1; break; + case FIELD2_VBL_FINISH : V = 0; break; + // finished a frame: + case LINES_PER_FRAME : L = 0; break; + } + + // End Active Video (EAV): + *ptr++ = F ? (V ? 0xF10000FF : 0xDA0000FF) : + (V ? 0xB60000FF : 0x9D0000FF); + + // Blanking Code: + if (init) for (i = (LINE_BLANK_BYTES >> 2) + 1; --i; ) + *ptr++ = UYVY_BLACK; + else ptr += (LINE_BLANK_BYTES >> 2); + + // Start Active Video (SAV): + *ptr++ = F ? (V ? 0xEC0000FF : 0xC70000FF) : + (V ? 0xAB0000FF : 0x800000FF); +#endif//ACTIVE_VIDEO_ONLY + + if (init) { + if (V) { // Video Blanking Line: + for (i = (LINE_ACTIVE_BYTES >> 2) + 1; --i; ) + *ptr++ = UYVY_BLACK; + } else { // Active Video Line Code: + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_WHITE; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_YELLOW; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_CYAN; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_GREEN; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_MAGENTA; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_RED; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_BLUE; + for (i = ((LINE_ACTIVE_BYTES >> 2) >> 3) + 1; --i; ) + *ptr++ = UYVY_BLACK; + } + } else ptr += (LINE_ACTIVE_BYTES >> 2); + + return ptr; +} +#endif//CONFIG_ITU656_OUTPUT + +#ifdef CONFIG_FB_COLORBAR +void __init draw_colorbar(struct fb_info* fbi) +{ + u16 s, r, w, h; + u32* pixels = (u32*)fbi->screen_base; + struct fb_var_screeninfo* var = &fbi->var; + +#define HORIZON_LINES (2) +#define CURSOR_PIXELS (32) + + printk("var->nonstd:%d, var->bits_per_pixel:%d\n", var->nonstd, var->bits_per_pixel); + if(var->bits_per_pixel == 24) var->bits_per_pixel = 32; + if (var->nonstd/* == FB_PIXEL_YUV*/) { + for (s = (var->xres >> 1) + 1, h = HORIZON_LINES + 1; --h; ) + for (w = s; --w; ) *pixels++ = UYVY_BLUE; // margin + + s = (var->xres >> 4) + 1, r = ((var->xres - ((s - 1) << 4)) >> 1); + for (h = var->yres - (HORIZON_LINES << 1) + 1; --h; ) { + for (w = s; --w; ) *pixels++ = UYVY_RED; + for (w = s; --w; ) *pixels++ = UYVY_GREEN; + for (w = s; --w; ) *pixels++ = UYVY_BLUE; + for (w = s; --w; ) *pixels++ = UYVY_BLACK; + for (w = s; --w; ) *pixels++ = UYVY_WHITE; + for (w = s; --w; ) *pixels++ = UYVY_YELLOW; + for (w = s; --w; ) *pixels++ = UYVY_MAGENTA; + for (w = s; --w; ) *pixels++ = UYVY_CYAN; + pixels += r; + } + + for (s = (var->xres >> 1) + 1, h = HORIZON_LINES + 1; --h; ) + for (w = s; --w; ) *pixels++ = UYVY_YELLOW;// margin + } else + + if (var->bits_per_pixel < 9) /*dtrace*/; else + if (var->bits_per_pixel < 17) { + for (s = (var->xres >> 1) + 1, h = HORIZON_LINES + 1; --h; ) + for (w = s; --w; ) *pixels++ = 0x001f001f; // Blue margin + + s = (var->xres >> 4) + 1, r = ((var->xres - ((s - 1) << 4)) >> 1); + for (h = var->yres - (HORIZON_LINES << 1) + 1; --h; ) { + for (w = s; --w; ) *pixels++ = 0xf800f800; // Red + for (w = s; --w; ) *pixels++ = 0x07e007e0; // Green + for (w = s; --w; ) *pixels++ = 0x001f001f; // Blue + for (w = s; --w; ) *pixels++ = 0x00000000; // Black + for (w = s; --w; ) *pixels++ = 0xffffffff; // White + for (w = s; --w; ) *pixels++ = 0xffe0ffe0; // R + G + for (w = s; --w; ) *pixels++ = 0x07ff07ff; // G + B + for (w = s; --w; ) *pixels++ = 0xf81ff81f; // B + R + pixels += r; + } + + for (s = (var->xres >> 1) + 1, h = HORIZON_LINES + 1; --h; ) + for (w = s; --w; ) *pixels++ = 0xffe0ffe0; // Yellow + } else + + if (var->bits_per_pixel < 25) { + for (s = (var->xres >> 2) + 1, h = HORIZON_LINES + 1; --h; ) { + for (w = s; --w; ) *pixels++ = 0x00ff0000, // Blue margin + *pixels++ = 0x0000ff00, *pixels++ = 0xff0000ff; +#ifndef CONFIG_DELTA_LCD +continue; +#endif + --h; // even line: + for (w = s; --w; ) *pixels++ = 0x0000ff00, // Green + *pixels++ = 0xff0000ff, *pixels++ = 0x00ff0000; + } + + s = (var->xres >> 5) + 1; + r = (((var->xres - ((s - 1) << 5)) * 3) >> 2); + for (h = var->yres - (HORIZON_LINES << 1) + 1; --h; ) { +#if 0 + unsigned t = 0xff - h; + for (w = s; --w; ) *pixels++ = 0x01000001 * t, + *pixels++ = 0x00010000 * t, *pixels++ = 0x00000100 * t; + for (w = s; --w; ) *pixels++ = 0x00000100 * t, + *pixels++ = 0x01000001 * t, *pixels++ = 0x00010000 * t; + for (w = s; --w; ) *pixels++ = 0x00010000 * t, + *pixels++ = 0x00000100 * t, *pixels++ = 0x01000001 * t; + for (w = s; --w; ) *pixels++ = 0x00000000 * t, + *pixels++ = 0x00000000 * t, *pixels++ = 0x00000000 * t; + for (w = s; --w; ) *pixels++ = 0x01010101 * t, + *pixels++ = 0x01010101 * t, *pixels++ = 0x01010101 * t; + for (w = s; --w; ) *pixels++ = 0x01000101 * t, + *pixels++ = 0x01010001 * t, *pixels++ = 0x00010100 * t; + for (w = s; --w; ) *pixels++ = 0x00010100 * t, + *pixels++ = 0x01000101 * t, *pixels++ = 0x01010001 * t; + for (w = s; --w; ) *pixels++ = 0x01010001 * t, + *pixels++ = 0x00010100 * t, *pixels++ = 0x01000101 * t; +#else + for (w = s; --w; ) *pixels++ = 0xff0000ff, // Red + *pixels++ = 0x00ff0000, *pixels++ = 0x0000ff00; + for (w = s; --w; ) *pixels++ = 0x0000ff00, // Green + *pixels++ = 0xff0000ff, *pixels++ = 0x00ff0000; + for (w = s; --w; ) *pixels++ = 0x00ff0000, // Blue + *pixels++ = 0x0000ff00, *pixels++ = 0xff0000ff; + for (w = s; --w; ) *pixels++ = 0x00000000, // Black + *pixels++ = 0x00000000, *pixels++ = 0x00000000; + for (w = s; --w; ) *pixels++ = 0xffffffff, // White + *pixels++ = 0xffffffff, *pixels++ = 0xffffffff; + for (w = s; --w; ) *pixels++ = 0xff00ffff, // R + G + *pixels++ = 0xffff00ff, *pixels++ = 0x00ffff00; + for (w = s; --w; ) *pixels++ = 0x00ffff00, // G + B + *pixels++ = 0xff00ffff, *pixels++ = 0xffff00ff; + for (w = s; --w; ) *pixels++ = 0xffff00ff, // B + R + *pixels++ = 0x00ffff00, *pixels++ = 0xff00ffff; +#endif + pixels += r; +#ifndef CONFIG_DELTA_LCD +continue; // The colorbar sequence is according the first line. +#endif + --h; // even line: + +#if 0 + t = 0xff - h; + for (w = s; --w; ) *pixels++ = 0x00010000 * t, + *pixels++ = 0x00000100 * t, *pixels++ = 0x01000001 * t; + for (w = s; --w; ) *pixels++ = 0x01000001 * t, + *pixels++ = 0x00010000 * t, *pixels++ = 0x00000100 * t; + for (w = s; --w; ) *pixels++ = 0x00000100 * t, + *pixels++ = 0x01000001 * t, *pixels++ = 0x00010000 * t; + for (w = s; --w; ) *pixels++ = 0x00000000 * t, + *pixels++ = 0x00000000 * t, *pixels++ = 0x00000000 * t; + for (w = s; --w; ) *pixels++ = 0x01010101 * t, + *pixels++ = 0x01010101 * t, *pixels++ = 0x01010101 * t; + for (w = s; --w; ) *pixels++ = 0x01010001 * t, + *pixels++ = 0x00010100 * t, *pixels++ = 0x01000101 * t; + for (w = s; --w; ) *pixels++ = 0x01000101 * t, + *pixels++ = 0x01010001 * t, *pixels++ = 0x00010100 * t; + for (w = s; --w; ) *pixels++ = 0x00010100 * t, + *pixels++ = 0x01000101 * t, *pixels++ = 0x01010001 * t; +#else + for (w = s; --w; ) *pixels++ = 0x00ff0000, // Blue + *pixels++ = 0x0000ff00, *pixels++ = 0xff0000ff; + for (w = s; --w; ) *pixels++ = 0xff0000ff, // Red + *pixels++ = 0x00ff0000, *pixels++ = 0x0000ff00; + for (w = s; --w; ) *pixels++ = 0x0000ff00, // Green + *pixels++ = 0xff0000ff, *pixels++ = 0x00ff0000; + for (w = s; --w; ) *pixels++ = 0x00000000, // Black + *pixels++ = 0x00000000, *pixels++ = 0x00000000; + for (w = s; --w; ) *pixels++ = 0xffffffff, // White + *pixels++ = 0xffffffff, *pixels++ = 0xffffffff; + for (w = s; --w; ) *pixels++ = 0xffff00ff, // B + R + *pixels++ = 0x00ffff00, *pixels++ = 0xff00ffff; + for (w = s; --w; ) *pixels++ = 0xff00ffff, // R + G + *pixels++ = 0xffff00ff, *pixels++ = 0x00ffff00; + for (w = s; --w; ) *pixels++ = 0x00ffff00, // G + B + *pixels++ = 0xff00ffff, *pixels++ = 0xffff00ff; +#endif + pixels += r; + } + + for (s = (var->xres >> 2) + 1, h = HORIZON_LINES + 1; --h; ) { + for (w = s; --w; ) *pixels++ = 0xff00ffff, // Yellow + *pixels++ = 0xffff00ff, *pixels++ = 0x00ffff00; +#ifndef CONFIG_DELTA_LCD +continue; +#endif + --h; // even line: + for (w = s; --w; ) *pixels++ = 0xffff00ff, // B + R + *pixels++ = 0x00ffff00, *pixels++ = 0xff00ffff; + } + +#ifdef CONFIG_DELTA_LCD + /* + * For Delta LCD(e.g.: A025DL01), the RGB sequence is: + * R-G-B for odd line, and G-B-R for even line. + * + * For best visual effect, the recommented RGB sequence is: + * G-B-R for odd line, and B-R-G for even line. + */ + pixels = (u32*)fbi->screen_base; + for (s = (var->xres * 3) >> 2, h = var->yres + 1; --h; ) { u32 lva; +#if 0 + for (w = s, lva = *pixels; --w; ) { + u32 nva = pixels[1]; // shift the line data left: + *pixels++ = (lva >> 8) | (nva << 24); lva = nva; + } *pixels++ = lva >> 8; +#else + for (w = s + 1, lva = 0; --w; ) { + u32 nva =*pixels; // shift the line data right: + *pixels++ = (nva << 8) | (lva >> 24); lva = nva; + } +#endif + } +#endif//CONFIG_DELTA_LCD + +#if 0 +{ unsigned char* ptr = (unsigned char*)fbi->screen_base + + ((var->xres >> 1) + 10) * 3; + for (h = var->yres + 1; --h; ) { + if (h % 2) ptr[2] = 0xff, ptr[1] = 0x00, ptr[3] = 0x00; + else ptr[2] = 0x00, ptr[1] = 0x00, ptr[3] = 0xff; + ptr[37] = ptr[38] = ptr[39] = 0x00; + ptr += var->xres * 3; + } +} +#endif + } else if (var->bits_per_pixel < 33) { + for (s = (var->xres >> 0) + 1, h = HORIZON_LINES + 1; --h; ) + for (w = s; --w; ) *pixels++ = 0x000000ff; // Blue margin + + s = (var->xres >> 3) + 1, r = ((var->xres - ((s - 1) << 3)) >> 0); + for (h = var->yres - (HORIZON_LINES << 1) + 1; --h; ) { + for (w = s; --w; ) *pixels++ = 0x00ff0000; // Red + for (w = s; --w; ) *pixels++ = 0x0000ff00; // Green + for (w = s; --w; ) *pixels++ = 0x000000ff; // Blue + for (w = s; --w; ) *pixels++ = 0x00000000; // Black + for (w = s; --w; ) *pixels++ = 0xffffffff; // White + for (w = s; --w; ) *pixels++ = 0x00ffff00; // G + R + for (w = s; --w; ) *pixels++ = 0x0000ffff; // B + G + for (w = s; --w; ) *pixels++ = 0x00ff00ff; // R + B + pixels += r; + } + + for (s = (var->xres >> 0) + 1, h = HORIZON_LINES + 1; --h; ) + for (w = s; --w; ) *pixels++ = 0x00ffff00; // Yellow + } else { + unsigned size = fbi->var.yres * fbi->fix.line_length / 2; + + memset32(pixels, 0x00000000, size); // Black/ + pixels = (void*)((unsigned char*)pixels + size); + + memset32(pixels, 0xffffffff, size); // White/ + pixels = (void*)((unsigned char*)pixels + size); + + //dtrace; + } + + BUG_ON((char*)pixels != fbi->screen_base + + fbi->var.yres * fbi->fix.line_length); + + // draw a white cursor on top-left corner + pixels = (u32*)fbi->screen_base; + for (s = CURSOR_PIXELS; --s; ) { + memset32(pixels, 0xffffffff, CURSOR_PIXELS); + pixels = (u32*)((unsigned char*)pixels + fbi->fix.line_length); + } +} +#else +#define draw_colorbar(...) +#endif//CONFIG_FB_COLORBAR + +// vim:sts=4:ts=8: diff --git a/drivers/video/s3c/s3cfb.c b/drivers/video/s3c/s3cfb.c index d9b8196..19ab25f 100644 --- a/drivers/video/s3c/s3cfb.c +++ b/drivers/video/s3c/s3cfb.c @@ -40,6 +40,9 @@ #include "s3cfb.h" +//#define CONFIG_FB_COLORBAR 1 +#include "colorbar.c" + /* * Globals */ @@ -50,7 +53,7 @@ s3c_vsync_info_t s3c_vsync_info; s3c_vs_info_t s3c_vs_info; #endif -static int backlight_level = S3C_FB_DEFAULT_BACKLIGHT_LEVEL; +static int backlight_level = S3C_FB_MIN_BACKLIGHT_LEVEL; static int backlight_power = 1; static int lcd_power = 1; static int s3c_palette_win; @@ -63,13 +66,14 @@ static void s3cfb_set_lcd_power(int to) (s3c_fimd.set_lcd_power)(to); } -static void s3cfb_set_backlight_power(int to) +void s3cfb_set_backlight_power(int to) { backlight_power = to; if (s3c_fimd.set_backlight_power) (s3c_fimd.set_backlight_power)(to); } +EXPORT_SYMBOL(s3cfb_set_backlight_power); static void s3cfb_set_backlight_level(int to) { @@ -91,7 +95,7 @@ static int __init s3cfb_map_video_memory(s3c_fb_info_t *fbi) /* prevent initial garbage on screen */ printk("Window[%d] - FB1: map_video_memory: clear %p:%08x\n", fbi->win_id, fbi->map_cpu_f1, fbi->map_size_f1); - memset(fbi->map_cpu_f1, 0xf0, fbi->map_size_f1); + memset(fbi->map_cpu_f1, 0xff, fbi->map_size_f1); fbi->screen_dma_f1 = fbi->map_dma_f1; fbi->fb.screen_base = fbi->map_cpu_f1; @@ -198,7 +202,8 @@ static int s3cfb_set_par(struct fb_info *info) struct fb_var_screeninfo *var = &info->var; s3c_fb_info_t *fbi = (s3c_fb_info_t *) info; - if (var->bits_per_pixel == 16 || var->bits_per_pixel == 24) + if (var->bits_per_pixel == 16 || var->bits_per_pixel == 24 + || var->bits_per_pixel == 32) fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR; else fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; @@ -264,16 +269,16 @@ static int s3cfb_blank(int blank_mode, struct fb_info *info) switch (blank_mode) { case VESA_NO_BLANKING: /* lcd on, backlight on */ - s3cfb_set_lcd_power(1); - s3cfb_set_backlight_power(1); + if(!backlight_power) + s3cfb_set_backlight_power(1); break; case VESA_VSYNC_SUSPEND: /* lcd on, backlight off */ case VESA_HSYNC_SUSPEND: - s3cfb_set_lcd_power(1); s3cfb_set_backlight_power(0); break; + case VESA_POWERDOWN + 1: // XXX: compatible with X case VESA_POWERDOWN: /* lcd and backlight off */ s3cfb_set_lcd_power(0); s3cfb_set_backlight_power(0); @@ -472,6 +477,17 @@ static int s3cfb_set_bpp(s3c_fb_info_t *fbi, int bpp) return 0; } +void s3cfb_clear_lcd(int flag) +{ + if(flag) // clear the LCD screen to black + memset((s3c_fb_info[0].fb).screen_base, 0x0, + (s3c_fb_info[0].fb).var.bits_per_pixel / 8 * (s3c_fb_info[0].fb).var.xres * (s3c_fb_info[0].fb).var.yres); + else // clear the LCD screen to white + memset((s3c_fb_info[0].fb).screen_base, 0xff, + (s3c_fb_info[0].fb).var.bits_per_pixel / 8 * (s3c_fb_info[0].fb).var.xres * (s3c_fb_info[0].fb).var.yres); +} +EXPORT_SYMBOL(s3cfb_clear_lcd); + void s3cfb_stop_lcd(void) { unsigned long flags; @@ -494,8 +510,11 @@ void s3cfb_start_lcd(void) local_irq_save(flags); + do { tmp = readl(S3C_VIDCON0); writel(tmp | S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE, S3C_VIDCON0); + tmp = readl(S3C_VIDCON0) & (S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE); + } while(tmp != (S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE)); local_irq_restore(flags); } @@ -664,15 +683,15 @@ static int s3cfb_sysfs_store_backlight_level(struct device *dev, struct device_a return len; } -static DEVICE_ATTR(lcd_power, 0644, +static DEVICE_ATTR(lcd_power, 0666, s3cfb_sysfs_show_lcd_power, s3cfb_sysfs_store_lcd_power); -static DEVICE_ATTR(backlight_power, 0644, +static DEVICE_ATTR(backlight_power, 0666, s3cfb_sysfs_show_backlight_power, s3cfb_sysfs_store_backlight_power); -static DEVICE_ATTR(backlight_level, 0644, +static DEVICE_ATTR(backlight_level, 0666, s3cfb_sysfs_show_backlight_level, s3cfb_sysfs_store_backlight_level); @@ -686,7 +705,7 @@ struct fb_ops s3cfb_ops = { .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, - .fb_cursor = soft_cursor, + //.fb_cursor = soft_cursor, .fb_ioctl = s3cfb_ioctl, }; @@ -772,7 +791,6 @@ static int __init s3cfb_probe(struct platform_device *pdev) struct resource *res; struct fb_info *fbinfo; s3c_fb_info_t *info; - char driver_name[] = "s3cfb"; int index = 0, ret, size; @@ -812,9 +830,6 @@ static int __init s3cfb_probe(struct platform_device *pdev) } s3cfb_pre_init(); - s3cfb_set_backlight_power(1); - s3cfb_set_lcd_power(1); - s3cfb_set_backlight_level(S3C_FB_DEFAULT_BACKLIGHT_LEVEL); #if defined(CONFIG_PLAT_S5PC1XX) info->clk = clk_get(NULL, "hclkd1"); @@ -906,6 +921,10 @@ static int __init s3cfb_probe(struct platform_device *pdev) if (ret < 0) printk(KERN_WARNING "s3cfb: failed to add entries\n"); + s3cfb_set_par(&(s3c_fb_info[0].fb)); + mdelay(10); + //draw_colorbar(&(s3c_fb_info[0].fb)); + return 0; free_video_memory: diff --git a/drivers/video/s3c/s3cfb.h b/drivers/video/s3c/s3cfb.h index cf66ec4..fbff811 100644 --- a/drivers/video/s3c/s3cfb.h +++ b/drivers/video/s3c/s3cfb.h @@ -65,7 +65,9 @@ extern int s3c6410_timer_setup (int channel, int usec, unsigned long g_tcnt, uns #define S3C_FB_PALETTE_BUFF_CLEAR (0x80000000) /* entry is clear/invalid */ #define S3C_FB_COLOR_KEY_DIR_BG 0 #define S3C_FB_COLOR_KEY_DIR_FG 1 -#define S3C_FB_DEFAULT_BACKLIGHT_LEVEL 2 +#define S3C_FB_MIN_BACKLIGHT_LEVEL 0 +#define S3C_FB_MAX_BACKLIGHT_LEVEL 100 +#define S3C_FB_DEFAULT_BACKLIGHT_LEVEL 50 #define S3C_FB_MAX_DISPLAY_OFFSET 200 #define S3C_FB_DEFAULT_DISPLAY_OFFSET 100 #define S3C_FB_MAX_ALPHA_LEVEL 0xf @@ -476,6 +478,7 @@ extern int s3cfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg extern void s3cfb_activate_var(s3c_fb_info_t *fbi, struct fb_var_screeninfo *var); extern void s3cfb_set_fb_addr(s3c_fb_info_t *fbi); extern void s3cfb_init_hw(void); +extern void lcd_init_hw(void); extern irqreturn_t s3cfb_irq(int irqno, void *param); extern int s3cfb_init_registers(s3c_fb_info_t *fbi); extern int s3cfb_set_win_position(s3c_fb_info_t *fbi, int left_x, int top_y, int width, int height); diff --git a/drivers/video/s3c/s3cfb_a070vw04.c b/drivers/video/s3c/s3cfb_a070vw04.c new file mode 100644 index 0000000..58d2af1 --- /dev/null +++ b/drivers/video/s3c/s3cfb_a070vw04.c @@ -0,0 +1,263 @@ +/* + * drivers/video/s3c/s3cfb_a070vw04.c + * + * $Id: s3cfb_lte480wv.c,v 1.12 2008/06/05 02:13:24 jsgood Exp $ + * + * Copyright (C) 2008 Jinsung Yang + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * S3C Frame Buffer Driver + * based on skeletonfb.c, sa1100fb.h, s3c2410fb.c + */ + +#include +#include +#include +#include + +#include +#include + +#include "s3cfb.h" +#include + +#define S3C_FB_HFP 3 /* front porch */ +#define S3C_FB_HSW 10 /* Hsync width */ +#define S3C_FB_HBP 5 /* Back porch */ + +#define S3C_FB_VFP 1 /* front porch */ +#define S3C_FB_VSW 3 /* Vsync width */ +#define S3C_FB_VBP 20 /* Back porch */ + +#define S3C_FB_HRES 800 /* horizon pixel x resolition */ +#define S3C_FB_VRES 480 /* line cnt y resolution */ + +#define S3C_FB_HRES_VIRTUAL 800 /* horizon pixel x resolition */ +#define S3C_FB_VRES_VIRTUAL 480 /* line cnt y resolution */ + +#define S3C_FB_HRES_OSD 800 /* horizon pixel x resolition */ +#define S3C_FB_VRES_OSD 480 /* line cnt y resolution */ + +#define S3C_FB_VFRAME_FREQ 60 /* frame rate freq */ + +#define S3C_FB_PIXEL_CLOCK (S3C_FB_VFRAME_FREQ * (S3C_FB_HFP + S3C_FB_HSW + S3C_FB_HBP + S3C_FB_HRES) * (S3C_FB_VFP + S3C_FB_VSW + S3C_FB_VBP + S3C_FB_VRES)) + +#define LCD_SCEN S3C_GPM0 +#define LCD_SCL S3C_GPM1 +#define LCD_SDA S3C_GPM2 + +static void set_lcd_power(int val) +{ + if(val > 0) { + /* Display On */ + s3cfb_init_hw(); + s3cfb_start_lcd(); + } else { + /* Direct Off */ + s3cfb_stop_lcd(); + } +} + +#define WAITTIME (10 * HZ / 1000) // 10ms +static int old_display_brightness = S3C_FB_MIN_BACKLIGHT_LEVEL; + +static void __set_brightness(int val) +{ + int channel = 1; // must use channel-1 + int usec = 0; // don't care value + unsigned long tcnt=1000; + unsigned long tcmp=0; + + if(val == S3C_FB_MAX_BACKLIGHT_LEVEL) + gpio_direction_output(S3C_GPF15, 1); + else if(val == S3C_FB_MIN_BACKLIGHT_LEVEL) + gpio_direction_output(S3C_GPF15, 0); + else { + tcmp = val * 10; +#if defined(CONFIG_S3C6410_PWM) && defined(CONFIG_PWM) + s3c6410_timer_setup (channel, usec, tcnt, tcmp); +#endif + } +} + +static void set_brightness(int val) +{ + int old_val = old_display_brightness; + + if(val < 0) val = 0; + if(val > S3C_FB_MAX_BACKLIGHT_LEVEL) val = S3C_FB_MAX_BACKLIGHT_LEVEL; + + if(val > old_val) { + while((++old_val) < val) { + __set_brightness(old_val); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(WAITTIME); + } + } else { + while((--old_val) > val) { + __set_brightness(old_val); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(WAITTIME); + } + } + + __set_brightness(val); + old_display_brightness = val; +} + +static void set_backlight_power(int val) +{ + if(val > 0) + __set_brightness(old_display_brightness); + else + __set_brightness(S3C_FB_MIN_BACKLIGHT_LEVEL); +} + +static void s3cfb_set_fimd_info(void) +{ + s3c_fimd.vidcon1 = S3C_VIDCON1_IHSYNC_INVERT | S3C_VIDCON1_IVSYNC_INVERT | S3C_VIDCON1_IVDEN_NORMAL | S3C_VIDCON1_IVCLK_RISE_EDGE; + s3c_fimd.vidtcon0 = S3C_VIDTCON0_VBPD(S3C_FB_VBP - 1) | S3C_VIDTCON0_VFPD(S3C_FB_VFP - 1) | S3C_VIDTCON0_VSPW(S3C_FB_VSW - 1); + s3c_fimd.vidtcon1 = S3C_VIDTCON1_HBPD(S3C_FB_HBP - 1) | S3C_VIDTCON1_HFPD(S3C_FB_HFP - 1) | S3C_VIDTCON1_HSPW(S3C_FB_HSW - 1); + s3c_fimd.vidtcon2 = S3C_VIDTCON2_LINEVAL(S3C_FB_VRES - 1) | S3C_VIDTCON2_HOZVAL(S3C_FB_HRES - 1); + + s3c_fimd.vidosd0a = S3C_VIDOSDxA_OSD_LTX_F(0) | S3C_VIDOSDxA_OSD_LTY_F(0); + s3c_fimd.vidosd0b = S3C_VIDOSDxB_OSD_RBX_F(S3C_FB_HRES - 1) | S3C_VIDOSDxB_OSD_RBY_F(S3C_FB_VRES - 1); + + s3c_fimd.vidosd1a = S3C_VIDOSDxA_OSD_LTX_F(0) | S3C_VIDOSDxA_OSD_LTY_F(0); + s3c_fimd.vidosd1b = S3C_VIDOSDxB_OSD_RBX_F(S3C_FB_HRES_OSD - 1) | S3C_VIDOSDxB_OSD_RBY_F(S3C_FB_VRES_OSD - 1); + + s3c_fimd.width = S3C_FB_HRES; + s3c_fimd.height = S3C_FB_VRES; + s3c_fimd.xres = S3C_FB_HRES; + s3c_fimd.yres = S3C_FB_VRES; + +#if defined(CONFIG_FB_S3C_VIRTUAL_SCREEN) + s3c_fimd.xres_virtual = S3C_FB_HRES_VIRTUAL; + s3c_fimd.yres_virtual = S3C_FB_VRES_VIRTUAL; +#else + s3c_fimd.xres_virtual = S3C_FB_HRES; + s3c_fimd.yres_virtual = S3C_FB_VRES; +#endif + + s3c_fimd.osd_width = S3C_FB_HRES_OSD; + s3c_fimd.osd_height = S3C_FB_VRES_OSD; + s3c_fimd.osd_xres = S3C_FB_HRES_OSD; + s3c_fimd.osd_yres = S3C_FB_VRES_OSD; + + s3c_fimd.osd_xres_virtual = S3C_FB_HRES_OSD; + s3c_fimd.osd_yres_virtual = S3C_FB_VRES_OSD; + + s3c_fimd.pixclock = S3C_FB_PIXEL_CLOCK; + + s3c_fimd.hsync_len = S3C_FB_HSW; + s3c_fimd.vsync_len = S3C_FB_VSW; + s3c_fimd.left_margin = S3C_FB_HFP; + s3c_fimd.upper_margin = S3C_FB_VFP; + s3c_fimd.right_margin = S3C_FB_HBP; + s3c_fimd.lower_margin = S3C_FB_VBP; + + s3c_fimd.set_lcd_power = set_lcd_power; + s3c_fimd.set_backlight_power = set_backlight_power; + s3c_fimd.set_brightness = set_brightness; + s3c_fimd.backlight_min = S3C_FB_MIN_BACKLIGHT_LEVEL; + s3c_fimd.backlight_max = S3C_FB_MAX_BACKLIGHT_LEVEL; +} + +void s3cfb_init_hw(void) +{ + printk(KERN_INFO "LCD TYPE :: A070VW04 will be initialized\n"); + + s3cfb_set_fimd_info(); + s3cfb_set_gpio(); + lcd_init_hw(); + set_brightness(old_display_brightness); +} + +static void us_delay(unsigned int dly) +{ + udelay(dly); +} + +static void write_bit(unsigned char val) +{ + gpio_set_value(S3C_GPM1, 0); // SCL = 0 + gpio_set_value(S3C_GPM2, val); // SDA = val + us_delay(1000); + gpio_set_value(S3C_GPM1, 1); // SCL = 1 + us_delay(1000); +} + +static unsigned int read_bit() +{ + gpio_set_value(S3C_GPM1, 0); // SCL = 0 + unsigned int data = s3c2410_gpio_getpin(S3C_GPM2); // SDA + us_delay(1000); + gpio_set_value(S3C_GPM1, 1); // SCL = 1 + us_delay(1000); + return data; +} + +static void write_reg(unsigned char addr, unsigned short data) +{ + unsigned char i; + gpio_set_value(LCD_SCEN, 0); // CSB pin of LCD = 0 + /* transfer the register address bits (4 bit) */ + for(i=0; i<4; i++) { + if(addr & 0x8) + write_bit(1); + else + write_bit(0); + addr <<= 1; + } + /* transfer the write mode bit (1 bit) */ + write_bit(0); + /* transfer the data bits (11 bits) */ + write_bit(0); + for(i=0; i<10; i++) { + if(data & 0x200) + write_bit(1); + else + write_bit(0); + data <<= 1; + } + gpio_set_value(LCD_SCEN, 1); // CSB pin of LCD = 1 +} + +static unsigned int read_reg(unsigned char addr) +{ + unsigned char i; + unsigned int data = 0x0; + gpio_set_value(LCD_SCEN, 0); // CSB pin of LCD = 0 + /* transfer the register address bits (4 bit) */ + for(i=0; i<4; i++) { + if(addr & 0x8) + write_bit(1); + else + write_bit(0); + addr <<= 1; + } + /* transfer the read or read mode (1 bit) */ + write_bit(1); + /* transfer the data (11 bits) */ + write_bit(0); + for(i=0; i<10; i++) { + data |= (read_bit() << i); + } + gpio_set_value(LCD_SCEN, 1); // CSB pin of LCD = 1 + + return data; +} + +void lcd_init_hw(void) +{ + gpio_direction_output(LCD_SCEN, 1); // GPM0 <---> CSB pin of LCD + gpio_direction_output(S3C_GPM1, 1); // GPM1 <---> SCL pin of LCD + gpio_direction_output(S3C_GPM2, 1); // GPM2 <---> SDA pin of LCD + write_reg(0x0, 0x2d3); + //write_reg(0x1, 0x181); + write_reg(0x4, 0x19f); +} + diff --git a/drivers/video/s3c/s3cfb_fimd4x.c b/drivers/video/s3c/s3cfb_fimd4x.c index c2ff1e8..79d857b 100644 --- a/drivers/video/s3c/s3cfb_fimd4x.c +++ b/drivers/video/s3c/s3cfb_fimd4x.c @@ -126,7 +126,6 @@ s3c_fimd_info_t s3c_fimd = { .cmap_static = 1, }; -#if defined(CONFIG_S3C6410_PWM) void s3cfb_set_brightness(int val) { int channel = 1; /* must use channel-1 */ @@ -143,9 +142,11 @@ void s3cfb_set_brightness(int val) s3c_display_brightness = val; tcmp = val * 5; +#if defined(CONFIG_S3C6410_PWM) && defined(CONFIG_PWM) s3c6410_timer_setup (channel, usec, tcnt, tcmp); -} #endif +} +EXPORT_SYMBOL(s3cfb_set_brightness); #if defined(CONFIG_FB_S3C_DOUBLE_BUFFERING) @@ -554,6 +555,9 @@ void s3cfb_activate_var(s3c_fb_info_t *fbi, struct fb_var_screeninfo *var) break; } + s3c_fimd.vidtcon1 &= ~S3C_VIDTCON1_HBPD(0xff); + s3c_fimd.vidtcon1 |= S3C_VIDTCON1_HBPD(var->right_margin); + /* write new registers */ writel(s3c_fimd.wincon0, S3C_WINCON0); writel(s3c_fimd.wincon1, S3C_WINCON1); @@ -561,6 +565,7 @@ void s3cfb_activate_var(s3c_fb_info_t *fbi, struct fb_var_screeninfo *var) writel(s3c_fimd.wincon3, S3C_WINCON3); writel(s3c_fimd.wincon4, S3C_WINCON4); writel(s3c_fimd.wpalcon, S3C_WPALCON); + writel(s3c_fimd.vidtcon1, S3C_VIDTCON1); writel(s3c_fimd.wincon0 | S3C_WINCONx_ENWIN_F_ENABLE, S3C_WINCON0); writel(s3c_fimd.vidcon0 | S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE, S3C_VIDCON0); } @@ -779,9 +784,7 @@ int s3cfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) struct fb_var_screeninfo *var= &fbi->fb.var; unsigned int crt, alpha_level, alpha_mode; -#if defined(CONFIG_S3C6410_PWM) int brightness; -#endif #if defined(CONFIG_FB_S3C_DOUBLE_BUFFERING) unsigned int f_num_val; @@ -971,14 +974,12 @@ int s3cfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) return -EFAULT; break; -#if defined(CONFIG_S3C6410_PWM) case S3C_FB_SET_BRIGHTNESS: if (copy_from_user(&brightness, (int *) arg, sizeof(int))) return -EFAULT; s3cfb_set_brightness(brightness); break; -#endif #if defined(CONFIG_FB_S3C_VIRTUAL_SCREEN) case S3C_FB_VS_START: @@ -1053,6 +1054,9 @@ void s3cfb_pre_init(void) s3c_fimd.vidintcon0 |= S3C_VIDINTCON0_INTFRMEN_ENABLE; writel(s3c_fimd.vidintcon0, S3C_VIDINTCON0); + + /* open the power of LCD */ + gpio_direction_output(S3C_GPM3, 1); } void s3cfb_set_gpio(void) @@ -1103,9 +1107,6 @@ void s3cfb_set_gpio(void) gpio_set_pin(S3C_GPJ10, S3C_GPJ10_LCD_VDEN); gpio_set_pin(S3C_GPJ11, S3C_GPJ11_LCD_VCLK); - /* backlight ON */ - gpio_direction_output(S3C_GPF15, 1); - /* module reset */ gpio_direction_output(S3C_GPN5, 1); mdelay(100); @@ -1240,6 +1241,8 @@ static struct sleep_save s3c_lcd_save[] = { SAVE_ITEM(S3C_W4PDATA23), }; +extern void s3cfb_set_backlight_power(int to); +extern void s3cfb_clear_lcd(int flag); /* * Suspend */ @@ -1248,6 +1251,8 @@ int s3cfb_suspend(struct platform_device *dev, pm_message_t state) struct fb_info *fbinfo = platform_get_drvdata(dev); s3c_fb_info_t *info = fbinfo->par; + s3cfb_set_backlight_power(0); // close the backlight of LCD + s3cfb_clear_lcd(0); // clear the LCD screen to white s3cfb_stop_lcd(); s3c2410_pm_do_save(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save)); @@ -1272,9 +1277,13 @@ int s3cfb_resume(struct platform_device *dev) clk_enable(info->clk); msleep(1); s3c2410_pm_do_restore(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save)); + mdelay(1); s3cfb_init_hw(); + mdelay(10); + s3cfb_clear_lcd(1); // clear the LCD screen to black s3cfb_start_lcd(); + mdelay(10); return 0; } diff --git a/drivers/video/s3c/s3cfb_td043mtex.c b/drivers/video/s3c/s3cfb_td043mtex.c new file mode 100644 index 0000000..86aab2f --- /dev/null +++ b/drivers/video/s3c/s3cfb_td043mtex.c @@ -0,0 +1,279 @@ +/* + * drivers/video/s3c/s3cfb_a070vw04.c + * + * $Id: s3cfb_lte480wv.c,v 1.12 2008/06/05 02:13:24 jsgood Exp $ + * + * Copyright (C) 2008 Jinsung Yang + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * S3C Frame Buffer Driver + * based on skeletonfb.c, sa1100fb.h, s3c2410fb.c + */ + +#include +#include +#include +#include + +#include +#include + +#include "s3cfb.h" +#include + +#define S3C_FB_HFP 40 /* front porch */ +#define S3C_FB_HSW 1 /* Hsync width */ +#define S3C_FB_HBP 216 /* Back porch */ + +#define S3C_FB_VFP 10 /* front porch */ +#define S3C_FB_VSW 1 /* Vsync width */ +#define S3C_FB_VBP 35 /* Back porch */ + +#define S3C_FB_HRES 800 /* horizon pixel x resolition */ +#define S3C_FB_VRES 480 /* line cnt y resolution */ + +#define S3C_FB_HRES_VIRTUAL 800 /* horizon pixel x resolition */ +#define S3C_FB_VRES_VIRTUAL 960 /* line cnt y resolution */ + +#define S3C_FB_HRES_OSD 800 /* horizon pixel x resolition */ +#define S3C_FB_VRES_OSD 480 /* line cnt y resolution */ + +#define S3C_FB_VFRAME_FREQ 60 /* frame rate freq */ + +#define S3C_FB_PIXEL_CLOCK (S3C_FB_VFRAME_FREQ * (S3C_FB_HFP + S3C_FB_HSW + S3C_FB_HBP + S3C_FB_HRES) * (S3C_FB_VFP + S3C_FB_VSW + S3C_FB_VBP + S3C_FB_VRES)) + +static void set_lcd_power(int val) +{ + if(val > 0) { + /* Display On */ + s3cfb_start_lcd(); + } else { + /* Direct Off */ + s3cfb_stop_lcd(); + } +} + +#define WAITTIME (10 * HZ / 1000) // 10ms +static int old_display_brightness = S3C_FB_MIN_BACKLIGHT_LEVEL; + +static void __set_brightness(int val) +{ + int channel = 1; // must use channel-1 + int usec = 0; // don't care value + unsigned long tcnt=1000; + unsigned long tcmp=0; + + if(val == S3C_FB_MAX_BACKLIGHT_LEVEL) + gpio_direction_output(S3C_GPF15, 1); + else if(val == S3C_FB_MIN_BACKLIGHT_LEVEL) + gpio_direction_output(S3C_GPF15, 0); + else { + tcmp = val * 10; +#if defined(CONFIG_S3C6410_PWM) && defined(CONFIG_PWM) + s3c6410_timer_setup (channel, usec, tcnt, tcmp); +#endif + } +} + +static void set_brightness(int val) +{ + int old_val = old_display_brightness; + + if(val < 0) val = 0; + if(val > S3C_FB_MAX_BACKLIGHT_LEVEL) val = S3C_FB_MAX_BACKLIGHT_LEVEL; + + if(val > old_val) { + while((++old_val) < val) { + __set_brightness(old_val); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(WAITTIME); + } + } else { + while((--old_val) > val) { + __set_brightness(old_val); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(WAITTIME); + } + } + + __set_brightness(val); + old_display_brightness = val; +} + +static void set_backlight_power(int val) +{ + if(val > 0) + __set_brightness(old_display_brightness); + else + __set_brightness(S3C_FB_MIN_BACKLIGHT_LEVEL); +} + +static void s3cfb_set_fimd_info(void) +{ + s3c_fimd.vidcon1 = S3C_VIDCON1_IHSYNC_INVERT | S3C_VIDCON1_IVSYNC_INVERT |S3C_VIDCON1_IVDEN_INVERT; + s3c_fimd.vidtcon0 = S3C_VIDTCON0_VBPD(S3C_FB_VBP - 1) | S3C_VIDTCON0_VFPD(S3C_FB_VFP - 1) | S3C_VIDTCON0_VSPW(S3C_FB_VSW - 1); + s3c_fimd.vidtcon1 = S3C_VIDTCON1_HBPD(S3C_FB_HBP - 1) | S3C_VIDTCON1_HFPD(S3C_FB_HFP - 1) | S3C_VIDTCON1_HSPW(S3C_FB_HSW - 1); + s3c_fimd.vidtcon2 = S3C_VIDTCON2_LINEVAL(S3C_FB_VRES - 1) | S3C_VIDTCON2_HOZVAL(S3C_FB_HRES - 1); + + s3c_fimd.vidosd0a = S3C_VIDOSDxA_OSD_LTX_F(0) | S3C_VIDOSDxA_OSD_LTY_F(0); + s3c_fimd.vidosd0b = S3C_VIDOSDxB_OSD_RBX_F(S3C_FB_HRES - 1) | S3C_VIDOSDxB_OSD_RBY_F(S3C_FB_VRES - 1); + + s3c_fimd.vidosd1a = S3C_VIDOSDxA_OSD_LTX_F(0) | S3C_VIDOSDxA_OSD_LTY_F(0); + s3c_fimd.vidosd1b = S3C_VIDOSDxB_OSD_RBX_F(S3C_FB_HRES_OSD - 1) | S3C_VIDOSDxB_OSD_RBY_F(S3C_FB_VRES_OSD - 1); + + s3c_fimd.width = S3C_FB_HRES; + s3c_fimd.height = S3C_FB_VRES; + s3c_fimd.xres = S3C_FB_HRES; + s3c_fimd.yres = S3C_FB_VRES; + +#if defined(CONFIG_FB_S3C_VIRTUAL_SCREEN) + s3c_fimd.xres_virtual = S3C_FB_HRES_VIRTUAL; + s3c_fimd.yres_virtual = S3C_FB_VRES_VIRTUAL; +#else + s3c_fimd.xres_virtual = S3C_FB_HRES; + s3c_fimd.yres_virtual = S3C_FB_VRES; +#endif + + s3c_fimd.osd_width = S3C_FB_HRES_OSD; + s3c_fimd.osd_height = S3C_FB_VRES_OSD; + s3c_fimd.osd_xres = S3C_FB_HRES_OSD; + s3c_fimd.osd_yres = S3C_FB_VRES_OSD; + + s3c_fimd.osd_xres_virtual = S3C_FB_HRES_OSD; + s3c_fimd.osd_yres_virtual = S3C_FB_VRES_OSD; + + s3c_fimd.pixclock = S3C_FB_PIXEL_CLOCK; + + s3c_fimd.hsync_len = S3C_FB_HSW; + s3c_fimd.vsync_len = S3C_FB_VSW; + s3c_fimd.left_margin = S3C_FB_HFP; + s3c_fimd.upper_margin = S3C_FB_VFP; + s3c_fimd.right_margin = S3C_FB_HBP; + s3c_fimd.lower_margin = S3C_FB_VBP; + + s3c_fimd.set_lcd_power = set_lcd_power; + s3c_fimd.set_backlight_power = set_backlight_power; + s3c_fimd.set_brightness = set_brightness; + s3c_fimd.backlight_min = S3C_FB_MIN_BACKLIGHT_LEVEL; + s3c_fimd.backlight_max = S3C_FB_MAX_BACKLIGHT_LEVEL; +} + +#define LCD_SCEN S3C_GPM0 +#define LCD_SCL S3C_GPM1 +#define LCD_SDA S3C_GPM2 +#define S3C_GPIO_OUTP 1 +#define S3C_GPIO_INP 0 + +static int lcd_write(unsigned char,unsigned char); +static void lcd_spi_stop(void); +static void lcd_spi_start(void); +static void lcd_spi_start(void) +{ + //set scen output 0 + s3c_gpio_cfgpin(LCD_SCEN,S3C_GPIO_OUTP); + s3c_gpio_setpin(LCD_SCEN,0); +} + +static void lcd_spi_stop(void) +{ + //set scen output 1 + s3c_gpio_cfgpin(LCD_SCEN,S3C_GPIO_OUTP); + s3c_gpio_setpin(LCD_SCEN,1); + s3c_gpio_setpin(LCD_SCL,0); + s3c_gpio_setpin(LCD_SDA,0); + s3c_gpio_cfgpin(LCD_SCL,S3C_GPIO_INP); + s3c_gpio_cfgpin(LCD_SDA,S3C_GPIO_INP); +} +static int lcd_write(unsigned char addr,unsigned char data) +{ + unsigned char myaddr,mydata,i; + myaddr = (addr&0x3f)<<1 ; + myaddr <<= 1; + myaddr |= 0x1; + lcd_spi_start(); + + s3c_gpio_cfgpin(LCD_SCL,S3C_GPIO_OUTP); + s3c_gpio_setpin(LCD_SCL,0); + s3c_gpio_cfgpin(LCD_SDA,S3C_GPIO_OUTP); + for(i=0;i<8;i++){ + s3c_gpio_setpin(LCD_SCL,0); + udelay(1); + s3c_gpio_setpin(LCD_SDA,(myaddr&0x80)>>7); + myaddr <<= 1 ; + udelay(1); + s3c_gpio_setpin(LCD_SCL,1); + udelay(1); + } //8nd is null turn + mydata = data; + for(i=0;i<8;i++){ + s3c_gpio_setpin(LCD_SCL,0); + udelay(1); + s3c_gpio_setpin(LCD_SDA,(mydata&0x80)>>7); + mydata <<= 1; + udelay(1); + s3c_gpio_setpin(LCD_SCL,1); + udelay(1); + } + + lcd_spi_stop(); + + return 0; +} + +void s3cfb_init_hw(void) +{ + printk(KERN_INFO "LCD TYPE :: TD043MTEX will be initialized\n"); + + s3cfb_set_fimd_info(); + s3cfb_set_gpio(); + lcd_init_hw(); +} + +void lcd_init_hw(void) +{ +#if 1 + lcd_spi_stop(); + + lcd_write(0x02,0x07); + lcd_write(0x03,0x5f); + lcd_write(0x04,0x17); + + lcd_write(0x05,0x20); + lcd_write(0x06,0x08); + + lcd_write(0x07,0x20); + lcd_write(0x08,0x20); + lcd_write(0x09,0x20); + lcd_write(0x0a,0x20); + + lcd_write(0x0b,0x20); + lcd_write(0x0c,0x20); + lcd_write(0x0d,0x22); + + lcd_write(0x0e,0x10); + lcd_write(0x0f,0x10); + lcd_write(0x10,0x10); + + lcd_write(0x11,0x15); + lcd_write(0x12,0xaa); + lcd_write(0x13,0xff); + lcd_write(0x14,0x86); + lcd_write(0x15,0x89); + lcd_write(0x16,0xc6); + lcd_write(0x17,0xea); + lcd_write(0x18,0x0c); + lcd_write(0x19,0x33); + lcd_write(0x1a,0x5e); + lcd_write(0x1b,0xd0); + lcd_write(0x1c,0x33); + lcd_write(0x1d,0x7e); + lcd_write(0x1e,0xb3); + lcd_write(0x1f,0xff); + lcd_write(0x20,0xf0); + lcd_write(0x21,0xf0); + lcd_write(0x22,0x08); +#endif +} diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 5d1c15f..abd77f7 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -153,6 +153,21 @@ static int s3c2410wdt_start(void) return 0; } +int s3c2410wdt_reboot(void) +{ + writel(0, wdt_base + S3C2410_WTCON); /* disable watchdog, to be safe */ + + /* put initial values into count and data */ + writel(0x100, wdt_base + S3C2410_WTCNT); + writel(0x100, wdt_base + S3C2410_WTDAT); + + /* set the watchdog to go and reset... */ + writel(S3C2410_WTCON_ENABLE|S3C2410_WTCON_DIV16|S3C2410_WTCON_RSTEN | + S3C2410_WTCON_PRESCALE(0x20), wdt_base + S3C2410_WTCON); + return 0; +} +EXPORT_SYMBOL(s3c2410wdt_reboot); + static int s3c2410wdt_set_heartbeat(int timeout) { unsigned int freq = clk_get_rate(wdt_clock); diff --git a/fs/Kconfig b/fs/Kconfig index 781b47d..7049712 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -1196,6 +1196,13 @@ config EFS_FS To compile the EFS file system support as a module, choose M here: the module will be called efs. + +# Patched by YAFFS +source "fs/yaffs2/Kconfig" + +# Patched by AUFS +source "fs/aufs/Kconfig" + config JFFS2_FS tristate "Journalling Flash File System v2 (JFFS2) support" select CRC32 diff --git a/fs/Makefile b/fs/Makefile index 500cf15..0dc75db 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -118,3 +118,9 @@ obj-$(CONFIG_HPPFS) += hppfs/ obj-$(CONFIG_DEBUG_FS) += debugfs/ obj-$(CONFIG_OCFS2_FS) += ocfs2/ obj-$(CONFIG_GFS2_FS) += gfs2/ + +# Patched by YAFFS +obj-$(CONFIG_YAFFS_FS) += yaffs2/ + +# Patched by AUFS +obj-$(CONFIG_AUFS) += aufs/ diff --git a/fs/aufs/Kconfig b/fs/aufs/Kconfig new file mode 100644 index 0000000..f609923 --- /dev/null +++ b/fs/aufs/Kconfig @@ -0,0 +1,283 @@ +config AUFS + tristate "Another unionfs" + help + Aufs is a stackable unification filesystem such as Unionfs, + which unifies several directories and provides a merged single + directory. + In the early days, aufs was entirely re-designed and + re-implemented Unionfs Version 1.x series. After many original + ideas, approaches and improvements, it becomes totally + different from Unionfs while keeping the basic features. + See Unionfs for the basic features. +if AUFS +comment "These options are for 2.6.24.7" +choice + prompt "Maximum number of branches" + default AUFS_BRANCH_MAX_127 + help + Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance. +config AUFS_BRANCH_MAX_127 + bool "127" + help + Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance. +config AUFS_BRANCH_MAX_511 + bool "511" + help + Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance. +config AUFS_BRANCH_MAX_1023 + bool "1023" + help + Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance. +config AUFS_BRANCH_MAX_32767 + bool "32767" + help + Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance. +endchoice +config AUFS_SYSAUFS + bool "Use /fs/aufs" + depends on SYSFS + default y + help + Aufs creates some files under sysfs for various purposes. + If the number of your branches is large or their path is long + and you meet the limitation of mount(8) or /etc/mtab, you need + to enable this option and set aufs module parameter brs=1. + See detail in aufs.5. +comment "SYSFS and AUFS_SYSAUFS are disabled" + depends on SYSFS = n +config AUFS_HINOTIFY + bool "Use inotify to detect actions on a branch" + depends on INOTIFY + help + If you want to modify files on branches directly, eg. bypassing aufs, + and want aufs to detect the changes of them fully, then enable this + option and use 'udba=inotify' mount option. + It will have a negative impact to the performance. + See detail in aufs.5. +comment "INOTIFY and AUFS_HINOTIFY are disabled" + depends on INOTIFY = n +config AUFS_EXPORT + bool "NFS-exportable aufs" + depends on (AUFS = y && EXPORTFS = y) || (AUFS = m && EXPORTFS) + help + If you want to export your mounted aufs, then enable this + option. There are several requirements for this configuration. + See detail in aufs.5. +comment "EXPORTFS and AUFS_EXPORT are disabled" + depends on EXPORTFS = n +comment "AUFS_EXPORT is disabled since EXPORTFS is a module but AUFS" + depends on EXPORTFS = m && AUFS = y +config AUFS_INO_T_64 + bool + depends on 64BIT && !(ALPHA || S390) + default y +config AUFS_ROBR + bool "Aufs as an readonly branch of another aufs mount" + help + If you want make your aufs to be a part of another aufs, then + enable this option. In other words, you can specify your aufs + path in 'br:' mount option for another aufs, but cannot + specify 'rw' as the branch permission. + It will have a negative impact to the performance. + See detail in aufs.5. +config AUFS_DLGT + bool "Delegate the internal branch access the kernel thread" + help + If you want aufs to delegate + the internal access to the branches which is made by aufs, to + the kernel thread, in order to hide the access issued by your + application from your LSM or something or make your + application to be traced strictly by the task I/O accounting, + then enable this option and use 'dlgt' mount option. + It will have a negative impact to the performance. + See detail in aufs.5. +config AUFS_HIN_OR_DLGT + bool + depends on AUFS_HINOTIFY || AUFS_DLGT + default y + help + Automatic configuration for internal use. +config AUFS_SHWH + bool "Show whiteouts" + help + If you want to make the whiteouts in aufs visible, then enable + this option and specify 'shwh' mount option. Although it may + sounds like philosophy or something, but in technically it + simply shows the name of whiteout with keeping its behaviour. +config AUFS_RR_SQUASHFS + bool "Make squashfs branch RR (real readonly) by default" + default y + help + If you use squashfs or LZMA patched squashfs as an aufs branch + and want to set '=rr' to it by default, then enable this + configuration. + 'rr' stands for real readonly and it optimizes some aspects of + 'ro.' + See detail in aufs.5. +config AUFS_SEC_PERM_PATCH + bool "sec_perm-2.6.24.patch was applied or not" + depends on AUFS = m + depends on SECURITY + help + If you build aufs as a module and enabled CONFIG_SECURITY, + then you need to apply the patch + 'CVS_TREE/aufs/patch/sec_perm-2.6.24.patch' to your kernel + source, and enable this configuration. + The sec_perm-2.6.24.patch exports a kernel function + security_inode_permission() to modules. +comment "SECURITY and AUFS_SEC_PERM_PATCH are disabled" + depends on SECURITY = n +config AUFS_SPLICE_PATCH + bool "splice.patch for sendfile(2) and splice(2)" + help + If you use 'loopback mount' on a fs-image file, or use + splice(2) or sendfile(2) systemcall in aufs, then you need to + apply the patch 'CVS_TREE/aufs/patch/splice.patch' to your + kernel source, and enable this configuration. + The splice.patch makes the kernel function do_splice_to/from() + global and exports them to modules. +config AUFS_LHASH_PATCH + bool "lhash.patch for NFS branch" + depends on NFS_FS + help + If you use mounted NFS as an aufs branch filesystem, then you + need to apply the patch 'CVS_TREE/aufs/patch/lhash.patch' (or + lhash-2.6.22.patch for linux-2.6.22 and later) to your kernel + source, and enable this configuration. + The patch file makes the kernel function __lookup_hash() global + and exports it to modules. +comment "NFS_FS and AUFS_LHASH_PATCH are disabled" + depends on NFS_FS = n +config AUFS_PUT_FILP_PATCH + bool "put_filp.patch for 'atomic open'" + depends on AUFS = m && NFS_V4 + help + If you build aufs as a module and use a filesystem which + operates 'atomic open' (for instance NFSv4) as an aufs branch + filesystem, then you need to apply the patch + 'CVS_TREE/aufs/patch/put_filp.patch' to your kernel source, + and enable this configuration. + The put_filp.patch exports a kernel function put_filp() to + modules. +comment "NFS_V4 and AUFS_PUT_FILP_PATCH are disabled" + depends on NFS_V4 = n +config AUFS_BR_NFS + bool + depends on NFS_FS + depends on AUFS_LHASH_PATCH + default y + help + Automatic configuration for internal use. + When aufs supports NFS branch, enabled automatically. +config AUFS_BR_NFS_V4 + bool + depends on NFS_V4 && AUFS_BR_NFS + depends on AUFS = y || (AUFS = m && AUFS_PUT_FILP_PATCH) + default y + help + Automatic configuration for internal use. + When aufs supports a branch filesystem which operates + 'atomic_open', for instance NFSv4, this configuration is enabled + automatically. +config AUFS_BR_XFS + bool + depends on XFS_FS + default y + help + Automatic configuration for internal use. + When aufs supports XFS branch, enabled automatically. +config AUFS_FSYNC_SUPER_PATCH + bool "fsync_super-2.6.xx.patch was applied or not" + depends on AUFS = m + help + If you build aufs as a module and want to flush everything for + branch filesystems which are not marked as 'rr' nor 'rr+wh' at + umount or remount time, then you need to apply the patch + 'CVS_TREE/aufs/patch/fsync_super-2.6.16.patch' or + '...-2.6.19.patch' to your kernel source, and enable this + configuration. + It may be helpful at shutdown time in case of your aufs is a + root filesystem. But this behaviour will not guarantee the + consistency of branch filesystems. To guarantee it, try the + approach described in the aufs manual, and do not forget + installing auplink script. + The fsync_super-2.6.xx.patch does nothing but exports a kernel + function fsync_super() to modules. +config AUFS_DENY_WRITE_ACCESS_PATCH + bool "deny_write_access.patch was applied or not" + depends on AUFS = m + help + A security enhancement to deny writing to a running executable + which exists on an aufs branch filesystem and executed through + aufs. If you applied + 'CVS_TREE/aufs/patch/deny_write_access.patch' to your kernel + and you are compiling aufs as a module, then enable this + option. + The write_deny_access.patch does nothing but export the + function. +config AUFS_WORKAROUND_FUSE + bool "Special handling for FUSE-based filesystem" + depends on FUSE_FS + help + A FUSE-based filesystem may not initialize its inode + attributes and the FUSE developer thinks the inode attributes + in a positive dentry which is returned by VFS lookup operation + are not reliable. + If you use a FUSE-based filesystem as an aufs branch, and it + customizes the inode attribute on it without overriding + fuse_lowlevel_ops.lookup, probably you need to enable this + configuration. + If you enable this configuration, aufs calls getattr operation + in every lookup and revalidate operation for the FUSE-based + filesystem branch. + It will have a negative impact to the performance even if you do + not use a FUSE-based filesystem branch. +config AUFS_GETATTR + bool + depends on AUFS_HINOTIFY || AUFS_WORKAROUND_FUSE || AUFS_BR_NFS + default y + help + Automatic configuration for internal use. +config AUFS_DEBUG + bool "Debug aufs" + default y + help + Enable this to compile aufs internal debug code. + It will have a negative impact to the performance. +config AUFS_MAGIC_SYSRQ + bool + depends on AUFS_DEBUG && MAGIC_SYSRQ + depends on AUFS_SYSAUFS + default y + help + Automatic configuration for internal use. + When aufs supports Magic SysRq, enabled automatically. +config AUFS_DEBUG_LOCK + bool "Show lock status in Magic SysRq" + depends on AUFS_MAGIC_SYSRQ + help + For developers only. Sometimes it is useful when a deadlock + occurs in aufs. Trace every aufs lock acquire and release, and + print them when Magic SysRq- key is pressed. + It will have a huge negative impact to the performance. +config AUFS_COMPAT + bool "Compatibility with Unionfs (obsolete)" + help + This makes aufs compatible with unionfs-style mount options and some + behaviours. + The dirs= mount option and =nfsro branch permission flag are always + interpreted as br: mount option and =ro flag respectively. The + 'debug', 'delete' and 'imap' mount options are ignored. + If you disable this option, you will get, + - aufs issues a warning about the ignored mount options + - the default branch permission flag is set. RW for the first branch, + and RO for the rests. + - the name of a internal file which represents the directory is + 'opaque', becomes '.wh..wh..opq' + - the 'diropq=w' mount option is set by default +config AUFS_UNIONFS23_PATCH + bool "Unionfs-2.3 or later patch is applied or not (obsolete)" + select AUFS_SPLICE_PATCH + help + Unionfs version 2.3 (and later) patch introduces some changes in VFS layer which has an impact to aufs. If you have applied such patch to your kernel, you need to enable this configuration even if you disabled CONFIG_UNIONFS. +endif diff --git a/fs/aufs/Makefile b/fs/aufs/Makefile new file mode 100644 index 0000000..f529b6f --- /dev/null +++ b/fs/aufs/Makefile @@ -0,0 +1,70 @@ +# AUFS Makefile for the Linux 2.6.16 - 2.6.24 +# $Id: Makefile,v 1.49 2008/09/29 03:43:19 sfjro Exp $ + +# the environment variables are not inherited since 2.6.23 +ifdef AUFS_EXTRA_CFLAGS +ccflags-y += ${AUFS_EXTRA_CFLAGS} +endif + +######################################## + +ifdef CONFIG_AUFS_RR_SQUASHFS +# cf. squashfs3.2-r2 and sqlzma patch. +ccflags-y += -DSQUASHFS_MAGIC=0x73717368 +ccflags-y += -DSQUASHFS_MAGIC_SWAP=0x68737173 +ccflags-y += -DSQUASHFS_MAGIC_LZMA=0x71736873 +ccflags-y += -DSQUASHFS_MAGIC_LZMA_SWAP=0x73687371 +endif + +# defined in ${srctree}/fs/fuse/inode.c +ccflags-$(CONFIG_AUFS_WORKAROUND_FUSE) += -DFUSE_SUPER_MAGIC=0x65735546 + +# defined in ${srctree}/fs/xfs/xfs_sb.h +# tristate +ifdef CONFIG_XFS_FS +ccflags-y += -DXFS_SB_MAGIC=0x58465342 +endif + +# defined in ${srctree}/mm/shmem.c +# tristate +ifdef CONFIG_TMPFS +ccflags-y += -DTMPFS_MAGIC=0x01021994 +endif + +# defined in ${srctree}fs/sysfs/mount.c +# bool +ccflags-$(CONFIG_SYSFS) += -DSYSFS_MAGIC=0x62656572 + +-include $(dir $(lastword ${MAKEFILE_LIST}))priv.mk +EXTRA_CFLAGS += ${ccflags-y} +#$(warning ${EXTRA_CFLAGS}) + +######################################## + +obj-$(CONFIG_AUFS) += aufs.o +aufs-y := module.o super.o sbinfo.o branch.o xino.o opts.o \ + wkq.o vfsub.o dcsub.o \ + cpup.o whout.o plink.o wbr_policy.o \ + dentry.o dinfo.o \ + file.o f_op.o finfo.o \ + dir.o vdir.o \ + inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o iinfo.o \ + misc.o + +aufs-$(CONFIG_AUFS_SYSAUFS) += sysaufs.o +ifeq ($(strip $(shell test ${SUBLEVEL} -ge 19 && echo t)),t) +aufs-$(CONFIG_AUFS_BR_NFS) += br_nfs.o +endif +aufs-$(CONFIG_AUFS_BR_XFS) += br_xfs.o +aufs-$(CONFIG_AUFS_WORKAROUND_FUSE) += br_fuse.o +aufs-$(CONFIG_AUFS_DLGT) += dlgt.o +aufs-$(CONFIG_AUFS_HINOTIFY) += hinotify.o +aufs-$(CONFIG_AUFS_HIN_OR_DLGT) += hin_or_dlgt.o +aufs-$(CONFIG_AUFS_GETATTR) += getattr.o +aufs-$(CONFIG_AUFS_EXPORT) += export.o +aufs-$(CONFIG_AUFS_ROBR) += robr.o +# reserved for future use +#aufs-$(CONFIG_AUFS_XATTR) += xattr.o +#aufs-$(CONFIG_DEBUGFS) += dbgfs.o +aufs-$(CONFIG_AUFS_DEBUG) += debug.o +aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o diff --git a/fs/aufs/aufs.h b/fs/aufs/aufs.h new file mode 100644 index 0000000..ab271b0 --- /dev/null +++ b/fs/aufs/aufs.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * main header files + * + * $Id: aufs.h,v 1.51 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_H__ +#define __AUFS_H__ + +#ifdef __KERNEL__ + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 24) +#error you got wrong version +#endif + +/* ---------------------------------------------------------------------- */ + +/* limited support before 2.6.16, curretly 2.6.15 only. */ +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) +#define timespec_to_ns(ts) ({ (long long)(ts)->tv_sec; }) +#define D_CHILD d_child +#else +#define D_CHILD d_u.d_child +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 17) +#include +typedef unsigned long blkcnt_t; +#endif + +/* introduced linux-2.6.17 */ +#include +#ifndef FMODE_EXEC +#define FMODE_EXEC 0 +#endif + +/* introduced in linux-2.6.21 */ +#include +#ifndef __packed +#define __packed __attribute__((packed)) +#endif +#ifndef __aligned +#define __aligned(x) __attribute__((aligned(x))) +#endif + +/* introduced in linux-2.6.25 */ +#ifndef noinline_for_stack +#define noinline_for_stack /* */ +#endif + +/* introduced in linux-2.6.27 */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) +#define WARN_ONCE(cond, fmt ...) WARN_ON(cond) +#endif + +/* ---------------------------------------------------------------------- */ + +#include "debug.h" + +#include "branch.h" +#include "cpup.h" +#include "dcsub.h" +#include "dentry.h" +#include "dir.h" +#include "file.h" +#include "hinode.h" +#include "inode.h" +#include "misc.h" +#include "module.h" +#include "opts.h" +#include "super.h" +#include "sysaufs.h" +#include "vfsub.h" +#include "whout.h" +#include "wkq.h" +/* reserved for future use */ +/* #include "xattr.h" */ + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_MODULE + +/* call ksize() or not */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22) \ + && !defined(CONFIG_AUFS_KSIZE_PATCH) +#define ksize(p) (0U) +#endif + +#endif /* CONFIG_AUFS_MODULE */ + +#endif /* __KERNEL__ */ +#endif /* __AUFS_H__ */ diff --git a/fs/aufs/br_fuse.c b/fs/aufs/br_fuse.c new file mode 100644 index 0000000..9b614b6 --- /dev/null +++ b/fs/aufs/br_fuse.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * special handling for inode attributes on FUSE branch + * + * $Id: br_fuse.c,v 1.7 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* h_mnt can be NULL, is it safe? */ +int au_update_fuse_h_inode(struct vfsmount *h_mnt, struct dentry *h_dentry) +{ + int err; + struct kstat st; + + LKTRTrace("%.*s\n", AuDLNPair(h_dentry)); + + err = 0; + if (h_dentry->d_inode + /* && atomic_read(&h_dentry->d_inode->i_count) */ + && au_test_fuse(h_dentry->d_sb)) { + err = vfsub_getattr(h_mnt, h_dentry, &st, /*dlgt*/0); + if (unlikely(err)) { + AuDbg("err %d\n", err); + au_debug_on(); + AuDbgDentry(h_dentry); + au_debug_off(); + WARN_ON(err); + } + } + return err; +} + +#if 0 /* temp */ +/* + * This function was born after a discussion with the FUSE developer. + * The inode attributes on a filesystem who defines i_op->getattr() + * is unreliable since such fs may not maintain the attributes at lookup. + * This function doesn't want the result of stat, instead wants the side-effect + * which refreshes the attributes. + * Hmm, there seems to be no such filesystem except fuse. + */ +int vfsub_i_attr(struct vfsmount *mnt, struct dentry *dentry, int dlgt) +{ + int err; + struct inode *inode; + struct inode_operations *op; + struct kstat st; + + inode = dentry->d_inode; + AuDebugOn(!inode); + + err = 0; + op = inode->i_op; + if (op && op->getattr && !au_test_aufs(dentry->d_sb)) { + err = security_inode_getattr(mnt, dentry); + if (!err) + err = op->getattr(mnt, dentry, &st); + } + AuTraceErr(err); + return err; +} +#endif diff --git a/fs/aufs/br_nfs.c b/fs/aufs/br_nfs.c new file mode 100644 index 0000000..74e34f2 --- /dev/null +++ b/fs/aufs/br_nfs.c @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * lookup functions for NFS branch in linux-2.6.19 and later + * + * $Id: br_nfs.c,v 1.8 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) \ + || !defined(CONFIG_AUFS_BR_NFS) +#error mis-configuraion or Makefile +#endif + +/* ---------------------------------------------------------------------- */ + +static struct file *au_find_h_intent(struct au_hdentry *hd, struct file *file) +{ + struct file *h_file, *hf; + struct au_hdintent *hdi, *tmp, *do_free; + + LKTRTrace("%.*s\n", AuDLNPair(hd->hd_dentry)); + + h_file = NULL; + do_free = NULL; + spin_lock(&hd->hd_lock); + list_for_each_entry_safe(hdi, tmp, hd->hd_intent_list, hdi_list) { + hf = hdi->hdi_file[AuIntent_BRANCH]; + if (hdi->hdi_file[AuIntent_AUFS] == file + && hf->f_dentry == hd->hd_dentry) { + h_file = hf; + do_free = hdi; + list_del(&hdi->hdi_list); + break; + } + } + spin_unlock(&hd->hd_lock); + kfree(do_free); + + return h_file; +} + +struct file *au_h_intent(struct dentry *dentry, aufs_bindex_t bindex, + struct file *file) +{ + struct file *h_file; + struct au_hdentry *hd = au_di(dentry)->di_hdentry + bindex; + + LKTRTrace("%.*s, b%d, f %p\n", AuDLNPair(dentry), bindex, file); + DiMustAnyLock(dentry); + AuDebugOn(bindex < au_di(dentry)->di_bstart + || bindex > au_di(dentry)->di_bend); + + h_file = NULL; + if (!hd->hd_intent_list || !file) + return h_file; /* success */ + + /* AuDebugOn(au_test_wkq(current)); */ + h_file = au_find_h_intent(hd, file); + return h_file; +} + +static int au_set_h_intent(struct dentry *dentry, aufs_bindex_t bindex, + struct file *file, struct file *h_file) +{ + int err; + struct au_hdentry *hd = au_di(dentry)->di_hdentry + bindex; + struct au_hdintent *hdi; + struct file *hf; + + LKTRTrace("%.*s, b%d, f %p\n", AuDLNPair(dentry), bindex, file); + /* d_revalidate() holds read_lock */ + /* DiMustWriteLock(dentry); */ + AuDebugOn(bindex < au_di(dentry)->di_bstart + || bindex > au_di(dentry)->di_bend + || !file + || !h_file + /* || au_test_wkq(current) */); + + err = -ENOMEM; + if (hd->hd_intent_list) { + while (1) { + hf = au_find_h_intent(hd, file); + if (!hf) + break; + fput(hf); + AuWarn("freed hfile %.*s b%d left\n", + AuDLNPair(dentry), bindex); + } + } else { + spin_lock(&hd->hd_lock); + if (!hd->hd_intent_list) { + hd->hd_intent_list + = kmalloc(sizeof(*hd->hd_intent_list), + GFP_ATOMIC); + if (unlikely(!hd->hd_intent_list)) { + spin_unlock(&hd->hd_lock); + goto out; + } + INIT_LIST_HEAD(hd->hd_intent_list); + } + spin_unlock(&hd->hd_lock); + } + + hdi = kmalloc(sizeof(*hdi), GFP_NOFS); + if (unlikely(!hdi)) + goto out; + + err = 0; + /* hdi->hdi_pid = current->pid; */ + hdi->hdi_file[AuIntent_AUFS] = file; + hdi->hdi_file[AuIntent_BRANCH] = h_file; + spin_lock(&hd->hd_lock); + list_add(&hdi->hdi_list, hd->hd_intent_list); + spin_unlock(&hd->hd_lock); + + out: + AuTraceErr(err); + return err; +} + +int au_br_nfs_h_intent(struct file *nd_file, struct dentry *dentry, + aufs_bindex_t bindex, struct nameidata *nd) +{ + int err; + + AuTraceEnter(); + + err = 0; + if (!nd_file) + goto out; + + AuDebugOn(!nd); + err = au_set_h_intent(dentry, bindex, nd->intent.open.file, nd_file); + if (unlikely(err)) { + fput(nd_file); + au_set_h_dptr(dentry, bindex, NULL); + /* todo: update bstart and bend? */ + } + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +void au_hintent_put(struct au_hdentry *hd, int do_free) +{ + struct au_hdintent *hdi, *tmp; + struct file *hf; + + if (hd->hd_intent_list) { + /* no spin lock */ + list_for_each_entry_safe(hdi, tmp, hd->hd_intent_list, + hdi_list) { + LKTRTrace("hdi %p\n", hdi); + hf = hdi->hdi_file[AuIntent_BRANCH]; + if (hf) + fput(hf); + /* list_del(&hdi->hdi_list); */ + kfree(hdi); + } + if (do_free) + kfree(hd->hd_intent_list); + } +} + +/* ---------------------------------------------------------------------- */ + +#if 0 +/* subset of nameidata */ +struct au_ndsub { + struct dentry *dentry; + struct vfsmount *mnt; + unsigned int flags; + + union { + struct open_intent open; + } intent; +}; + +static void au_ndsub_restore(struct nameidata *nd, struct au_ndsub *save) +{ + nd->dentry = save->dentry; + nd->mnt = save->mnt; + nd->flags = save->flags; + nd->intent = save->intent; +} +#endif + +int au_fake_intent(/* struct au_ndsub *save, */struct nameidata *nd, + struct au_branch *br) +{ + int err; + + LKTRTrace("perm %d\n", br->br_perm); + + err = 0; +#if 0 + save->dentry = nd->dentry; + save->mnt = nd->mnt; + save->flags = nd->flags; + save->intent = nd->intent; +#endif + + nd->intent.open.file = NULL; + if (nd->flags & LOOKUP_OPEN) { + if (au_test_fs_intent(br->br_mnt->mnt_sb)) { + err = -ENFILE; + nd->intent.open.file = get_empty_filp(); + if (unlikely(!nd->intent.open.file)) + goto out; + err = 0; + } + if (!au_br_writable(br->br_perm)) { + nd->intent.open.flags = FMODE_READ + | au_file_roflags(nd->intent.open.flags); + nd->flags &= ~LOOKUP_CREATE; + } + } + + out: + AuTraceErr(err); + return err; +} + +static void au_put_filp(struct file *file) +{ +#if !defined(CONFIG_AUFS_MODULE) || defined(CONFIG_AUFS_PUT_FILP_PATCH) + if (unlikely(file)) + put_filp(file); +#else + WARN_ONCE(file, "unexpected put_fillp() call"); +#endif +} + +int au_hin_after_reval(struct nameidata *nd, struct dentry *dentry, + aufs_bindex_t bindex, struct file *file) +{ + int err; + + LKTRTrace("nd %p, %.*s, b%d, f %d\n", + nd, AuDLNPair(dentry), bindex, !!file); + + err = 0; + if ((nd->flags & LOOKUP_OPEN) + && nd->intent.open.file + && !IS_ERR(nd->intent.open.file)) { + if (nd->intent.open.file->f_dentry) { + err = au_set_h_intent(dentry, bindex, file, + nd->intent.open.file); + if (!err) + nd->intent.open.file = NULL; + } + au_put_filp(nd->intent.open.file); + } + + return err; +} + +#ifdef CONFIG_AUFS_DLGT +struct au_lookup_hash_args { + struct dentry **errp; + struct qstr *name; + struct dentry *base; + struct nameidata *nd; +}; + +static void au_call_lookup_hash(void *args) +{ + struct au_lookup_hash_args *a = args; + *a->errp = vfsub__lookup_hash(a->name, a->base, a->nd); +} + +static struct dentry * +au_lkup_hash_dlgt(struct qstr *this, struct dentry *parent, + struct nameidata *nd, unsigned int flags) +{ + struct dentry *dentry; + int dirperm1; + + dirperm1 = au_ftest_ndx(flags, DIRPERM1); + if (!dirperm1 && !au_ftest_ndx(flags, DLGT)) + dentry = vfsub__lookup_hash(this, parent, nd); + else { + int wkq_err; + struct au_lookup_hash_args args = { + .errp = &dentry, + .name = this, + .base = parent, + .nd = nd + }; + wkq_err = au_wkq_wait(au_call_lookup_hash, &args, + /*dlgt*/!dirperm1); + if (unlikely(wkq_err)) + dentry = ERR_PTR(wkq_err); + } + + AuTraceErrPtr(dentry); + return dentry; +} +#else +static struct dentry * +au_lkup_hash_dlgt(struct qstr *this, struct dentry *parent, + struct nameidata *nd, unsigned int flags) +{ + return vfsub__lookup_hash(this, parent, nd); +} +#endif /* CONFIG_AUFS_DLGT */ + +struct dentry *au_lkup_hash(const char *name, struct dentry *parent, + int len, struct au_ndx *ndx) +{ + struct dentry *dentry; + char *p; + unsigned long hash; + struct qstr this; + unsigned int c; + struct nameidata tmp_nd, *ndo; + int err; + + LKTRTrace("%.*s/%.*s\n", AuDLNPair(parent), len, name); + + /* todo: export and call __lookup_one_len() in fs/namei.c? */ + dentry = ERR_PTR(-EACCES); + this.name = name; + this.len = len; + if (unlikely(!len)) + goto out; + + p = (void *)name; + hash = init_name_hash(); + while (len--) { + c = *p++; + if (unlikely(c == '/' || c == '\0')) + goto out; + hash = partial_name_hash(c, hash); + } + this.hash = end_name_hash(hash); + + ndo = ndx->nd; + if (ndo) { + tmp_nd = *ndo; + err = au_fake_intent(&tmp_nd, ndx->br); + dentry = ERR_PTR(err); + if (unlikely(err)) + goto out_intent; + } else + memset(&tmp_nd, 0, sizeof(tmp_nd)); + + tmp_nd.dentry = dget(parent); + tmp_nd.mnt = mntget(ndx->nfsmnt); + dentry = au_lkup_hash_dlgt(&this, parent, &tmp_nd, ndx->flags); + if (!IS_ERR(dentry)) { + /* why negative dentry for a new dir was unhashed? */ + if (unlikely(d_unhashed(dentry))) + d_rehash(dentry); + if (tmp_nd.intent.open.file + && tmp_nd.intent.open.file->f_dentry) { + ndx->nd_file = tmp_nd.intent.open.file; + tmp_nd.intent.open.file = NULL; + /* au_br_get(ndx->br); */ + } + } + path_release(&tmp_nd); + + out_intent: + au_put_filp(tmp_nd.intent.open.file); + out: + AuTraceErrPtr(dentry); + return dentry; +} diff --git a/fs/aufs/br_xfs.c b/fs/aufs/br_xfs.c new file mode 100644 index 0000000..b4f7421 --- /dev/null +++ b/fs/aufs/br_xfs.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * special handling inode attributes on XFS branch in linux-2.6.24 and later + * + * $Id: br_xfs.c,v 1.4 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) \ + || (!defined(CONFIG_XFS_FS) && !defined(CONFIG_XFS_FS_MODULE)) +#error mis-configuraion or Makefile +#endif + +/* h_mnt can be NULL, is it safe? */ +dev_t au_h_rdev(struct inode *h_inode, struct vfsmount *h_mnt, + struct dentry *h_dentry) +{ + dev_t rdev; + int err; + struct kstat st; + + LKTRTrace("hi%lu\n", h_inode->i_ino); + if (h_dentry) + LKTRTrace("%.*s\n", AuDLNPair(h_dentry)); + + rdev = h_inode->i_rdev; + if (!rdev || !au_test_xfs(h_inode->i_sb)) + goto out; + + rdev = 0; + if (!h_dentry) { + err = 0; + h_dentry = d_find_alias(h_inode); + if (unlikely(!h_dentry)) + goto failure; + err = PTR_ERR(h_dentry); + if (IS_ERR(h_dentry)) { + h_dentry = NULL; + goto failure; + } + LKTRTrace("%.*s\n", AuDLNPair(h_dentry)); + } else + dget(h_dentry); + + err = vfsub_getattr(h_mnt, h_dentry, &st, /*dlgt*/0); + dput(h_dentry); + if (!err) { + rdev = st.rdev; + goto out; /* success */ + } + + failure: + AuIOErr("failed rdev for XFS inode, hi%lu, %d\n", h_inode->i_ino, err); + out: + return rdev; +} diff --git a/fs/aufs/branch.c b/fs/aufs/branch.c new file mode 100644 index 0000000..bcf1a3d --- /dev/null +++ b/fs/aufs/branch.c @@ -0,0 +1,1036 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * branch management + * + * $Id: branch.c,v 1.90 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include +#include +#include +#include "aufs.h" + +static void au_br_do_free(struct au_branch *br) +{ + int i; + struct au_wbr *wbr; + + AuTraceEnter(); + + if (br->br_xino.xi_file) + fput(br->br_xino.xi_file); + mutex_destroy(&br->br_xino.xi_nondir_mtx); + wbr = br->br_wbr; + if (wbr) + for (i = 0; i < AuBrWh_Last; i++) + dput(wbr->wbr_wh[i]); + /* do not call au_br_nfs_lockdep_off() here */ + if (br->br_mnt && !au_test_nfs(br->br_mnt->mnt_sb)) + mntput(br->br_mnt); + else { + lockdep_off(); + mntput(br->br_mnt); + lockdep_on(); + } + AuDebugOn(au_br_count(br)); + if (wbr) { + AuDebugOn(atomic_read(&wbr->wbr_wh_running)); + au_rwsem_destroy(&wbr->wbr_wh_rwsem); + } + sysaufs_br_put(br); + kfree(wbr); + kfree(br); +} + +/* + * frees all branches + */ +void au_br_free(struct au_sbinfo *sbinfo) +{ + aufs_bindex_t bmax; + struct au_branch **br; + + AuTraceEnter(); + bmax = sbinfo->si_bend + 1; + br = sbinfo->si_branch; + while (bmax--) + au_br_do_free(*br++); +} + +/* + * find the index of a branch which is specified by @br_id. + */ +int au_br_index(struct super_block *sb, aufs_bindex_t br_id) +{ + aufs_bindex_t bindex, bend; + + AuTraceEnter(); + + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) + if (au_sbr_id(sb, bindex) == br_id) + return bindex; + return -1; +} + +/* + * test if the @h_sb is real-readonly. + */ +int au_test_def_rr(struct super_block *h_sb) +{ + switch (h_sb->s_magic) { +#ifdef CONFIG_AUFS_RR_SQUASHFS + case SQUASHFS_MAGIC_LZMA: + case SQUASHFS_MAGIC: + case SQUASHFS_MAGIC_LZMA_SWAP: + case SQUASHFS_MAGIC_SWAP: + return 1; /* real readonly */ +#endif + +#if defined(CONFIG_ISO9660_FS) || defined(CONFIG_ISO9660_FS_MODULE) + case ISOFS_SUPER_MAGIC: + return 1; +#endif + +#if defined(CONFIG_CRAMFS) || defined(CONFIG_CRAMFS_MODULE) + case CRAMFS_MAGIC: + return 1; +#endif + +#if defined(CONFIG_ROMFS_FS) || defined(CONFIG_ROMFS_FS_MODULE) + case ROMFS_MAGIC: + return 1; +#endif + + default: + return 0; + } +} + +/* ---------------------------------------------------------------------- */ + +/* + * test if two hidden_dentries have overlapping branches. + */ +static int do_test_overlap(struct super_block *sb, struct dentry *h_d1, + struct dentry *h_d2) +{ + struct dentry *d; + + LKTRTrace("%.*s, %.*s\n", AuDLNPair(h_d1), AuDLNPair(h_d2)); + + d = au_test_subdir(h_d1, h_d2); + if (unlikely(d)) { + AuDbgDentry(h_d1); + AuDbgDentry(h_d2); + } + return !!d; +} + +static int test_overlap_loopback(struct super_block *sb, struct dentry *h_d1, + struct dentry *h_d2) +{ +#if defined(CONFIG_BLK_DEV_LOOP) || defined(CONFIG_BLK_DEV_LOOP_MODULE) + struct inode *h_inode; + struct loop_device *l; + + LKTRTrace("%.*s, %.*s\n", AuDLNPair(h_d1), AuDLNPair(h_d2)); + AuDbgDentry(h_d1); + AuDbgDentry(h_d2); + AuDbgSb(h_d1->d_sb); + AuDbgSb(h_d2->d_sb); + + h_inode = h_d1->d_inode; + if (MAJOR(h_inode->i_sb->s_dev) != LOOP_MAJOR) + return 0; + + l = h_inode->i_sb->s_bdev->bd_disk->private_data; + h_d1 = l->lo_backing_file->f_dentry; + /* h_d1 can be local NFS. in this case aufs cannot detect the loop */ + AuDbgDentry(h_d1); + AuDbgDentry(h_d2); + AuDbgSb(h_d1->d_sb); + AuDbgSb(h_d2->d_sb); + if (unlikely(h_d1->d_sb == sb)) + return 1; + return do_test_overlap(sb, h_d1, h_d2); +#else + return 0; +#endif +} + +static int test_overlap(struct super_block *sb, struct dentry *h_d1, + struct dentry *h_d2) +{ + LKTRTrace("d1 %.*s, d2 %.*s\n", AuDLNPair(h_d1), AuDLNPair(h_d2)); + + if (unlikely(h_d1 == h_d2)) { + AuDbgDentry(h_d1); + AuDbgDentry(h_d2); + return 1; + } + return do_test_overlap(sb, h_d1, h_d2) + || do_test_overlap(sb, h_d2, h_d1) + || test_overlap_loopback(sb, h_d1, h_d2) + || test_overlap_loopback(sb, h_d2, h_d1); +} + +/* ---------------------------------------------------------------------- */ + +static int au_br_init_wh(struct super_block *sb, aufs_bindex_t bindex, + struct au_branch *br, int new_perm, + struct dentry *h_root, struct vfsmount *h_mnt) +{ + int err, old_perm; + struct inode *h_dir; + struct au_wbr *wbr; + + LKTRTrace("b%d, new_perm %d\n", bindex, new_perm); + SiMustWriteLock(sb); + + wbr = br->br_wbr; + h_dir = h_root->d_inode; + old_perm = br->br_perm; + vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT); + if (wbr) + wbr_wh_write_lock(wbr); + br->br_perm = new_perm; + err = au_wh_init(h_root, br, h_mnt, sb, bindex); + br->br_perm = old_perm; + if (wbr) + wbr_wh_write_unlock(wbr); + vfsub_i_unlock(h_dir); + if (!err && wbr && !au_br_writable(new_perm)) { + AuDebugOn(wbr->wbr_whbase); + AuDebugOn(wbr->wbr_plink); + AuDebugOn(wbr->wbr_tmp); + kfree(wbr); + br->br_wbr = NULL; + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* + * returns a newly allocated branch. @new_nbranch is a number of branches + * after adding a branch. + */ +static struct au_branch *alloc_addbr(struct super_block *sb, int new_nbranch, + int perm) +{ + struct au_branch **branchp, *add_branch; + int sz; + void *p; + struct dentry *root; + struct inode *inode; + struct au_hinode *hinodep; + struct au_hdentry *hdentryp; + + LKTRTrace("new_nbranch %d\n", new_nbranch); + SiMustWriteLock(sb); + root = sb->s_root; + DiMustWriteLock(root); + inode = root->d_inode; + IiMustWriteLock(inode); + + add_branch = kmalloc(sizeof(*add_branch), GFP_NOFS); + if (unlikely(!add_branch)) + goto out; + add_branch->br_sabr = sysaufs_br_alloc(); + if (unlikely(!add_branch->br_sabr)) + goto out; + add_branch->br_wbr = NULL; + if (au_br_writable(perm)) { + add_branch->br_wbr = kmalloc(sizeof(*add_branch->br_wbr), + GFP_NOFS); + if (unlikely(!add_branch->br_wbr)) + goto out_sabr; + } + + sz = sizeof(*branchp) * (new_nbranch - 1); + if (unlikely(!sz)) + sz = sizeof(*branchp); + p = au_sbi(sb)->si_branch; + branchp = au_kzrealloc(p, sz, sizeof(*branchp) * new_nbranch, GFP_NOFS); + if (unlikely(!branchp)) + goto out_wbr; + au_sbi(sb)->si_branch = branchp; + + sz = sizeof(*hdentryp) * (new_nbranch - 1); + if (unlikely(!sz)) + sz = sizeof(*hdentryp); + p = au_di(root)->di_hdentry; + hdentryp = au_kzrealloc(p, sz, sizeof(*hdentryp) * new_nbranch, + GFP_NOFS); + if (unlikely(!hdentryp)) + goto out_wbr; + au_di(root)->di_hdentry = hdentryp; + + sz = sizeof(*hinodep) * (new_nbranch - 1); + if (unlikely(!sz)) + sz = sizeof(*hinodep); + p = au_ii(inode)->ii_hinode; + hinodep = au_kzrealloc(p, sz, sizeof(*hinodep) * new_nbranch, GFP_NOFS); + if (unlikely(!hinodep)) + goto out_wbr; + au_ii(inode)->ii_hinode = hinodep; + return add_branch; /* success */ + + out_wbr: + kfree(add_branch->br_wbr); + out_sabr: + sysaufs_br_put(add_branch); + out: + kfree(add_branch); + AuTraceErr(-ENOMEM); + return ERR_PTR(-ENOMEM); +} + +/* + * test if the branch permission is legal or not. + */ +static int test_br(struct super_block *sb, struct inode *inode, int brperm, + char *path) +{ + int err; + + err = 0; + if (unlikely(au_br_writable(brperm) && IS_RDONLY(inode))) { + AuErr("write permission for readonly fs or inode, %s\n", path); + err = -EINVAL; + } + + AuTraceErr(err); + return err; +} + +static int au_unsupported_fs(struct super_block *sb) +{ + return sb->s_magic == PROC_SUPER_MAGIC +#ifdef SYSFS_MAGIC + || sb->s_magic == SYSFS_MAGIC +#endif + || !strcmp(au_sbtype(sb), "unionfs"); +} + +/* + * returns: + * 0: success, the caller will add it + * plus: success, it is already unified, the caller should ignore it + * minus: error + */ +static int test_add(struct super_block *sb, struct au_opt_add *add, int remount) +{ + int err; + struct dentry *root; + struct inode *inode, *h_inode; + aufs_bindex_t bend, bindex; + + LKTRTrace("%s, remo%d\n", add->path, remount); + + root = sb->s_root; + bend = au_sbend(sb); + if (unlikely(bend >= 0 && au_find_dbindex(root, add->nd.dentry) >= 0)) { + err = 1; + if (!remount) { + err = -EINVAL; + AuErr("%s duplicated\n", add->path); + } + goto out; + } + + err = -ENOSPC; /* -E2BIG; */ + if (unlikely(AUFS_BRANCH_MAX <= add->bindex + || AUFS_BRANCH_MAX - 1 <= bend)) { + AuErr("number of branches exceeded %s\n", add->path); + goto out; + } + + err = -EDOM; + if (unlikely(add->bindex < 0 || bend + 1 < add->bindex)) { + AuErr("bad index %d\n", add->bindex); + goto out; + } + + inode = add->nd.dentry->d_inode; + AuDebugOn(!inode || !S_ISDIR(inode->i_mode)); + err = -ENOENT; + if (unlikely(!inode->i_nlink)) { + AuErr("no existence %s\n", add->path); + goto out; + } + + err = -EINVAL; + if (unlikely(inode->i_sb == sb)) { + AuErr("%s must be outside\n", add->path); + goto out; + } + + if (unlikely(au_test_nested(inode->i_sb))) { + AuErr("nested " AUFS_NAME " %s\n", add->path); + goto out; + } + + if (unlikely(au_unsupported_fs(inode->i_sb))) { + AuErr("unsupported filesystem, %s\n", add->path); + goto out; + } + + if (unlikely(au_test_unsupported_nfs(inode->i_sb))) { + AuErr(AuNoNfsBranchMsg " %s\n", add->path); + goto out; + } + + if (unlikely(au_test_unsupported_nfs4(inode->i_sb))) { + AuErr(AuNoNfsv4BranchMsg " %s\n", add->path); + goto out; + } + + err = test_br(sb, add->nd.dentry->d_inode, add->perm, add->path); + if (unlikely(err)) + goto out; + + if (bend < 0) + return 0; /* success */ + + err = -EINVAL; + for (bindex = 0; bindex <= bend; bindex++) + if (unlikely(test_overlap(sb, add->nd.dentry, + au_h_dptr(root, bindex)))) { + AuErr("%s is overlapped\n", add->path); + goto out; + } + + err = 0; + h_inode = au_h_dptr(root, 0)->d_inode; + if (unlikely(au_opt_test(au_mntflags(sb), WARN_PERM) + && ((h_inode->i_mode & S_IALLUGO) + != (inode->i_mode & S_IALLUGO) + || h_inode->i_uid != inode->i_uid + || h_inode->i_gid != inode->i_gid))) + AuWarn("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n", + add->path, + inode->i_uid, inode->i_gid, (inode->i_mode & S_IALLUGO), + h_inode->i_uid, h_inode->i_gid, + (h_inode->i_mode & S_IALLUGO)); + + out: + AuTraceErr(err); + return err; +} + +static int au_wbr_init(struct au_branch *br, struct super_block *sb, + int perm, struct dentry *dentry, struct vfsmount *mnt) +{ + int err; + struct au_wbr *wbr; + + AuTraceEnter(); + wbr = br->br_wbr; + AuDebugOn(!wbr); + + au_rw_init_nolock(&wbr->wbr_wh_rwsem); + memset(wbr->wbr_wh, 0, sizeof(wbr->wbr_wh)); + atomic_set(&wbr->wbr_wh_running, 0); + wbr->wbr_bytes = 0; + + err = au_br_init_wh(sb, /*bindex*/-1, br, perm, dentry, mnt); + + AuTraceErr(err); + return err; +} + +static int au_br_init(struct au_branch *br, struct super_block *sb, + struct au_opt_add *add) +{ + int err; + unsigned int mnt_flags; + + AuTraceEnter(); + + err = 0; + br->br_mnt = NULL; + br->br_xino.xi_file = NULL; + mutex_init(&br->br_xino.xi_nondir_mtx); + atomic_set(&br->br_xino_running, 0); + atomic_set(&br->br_count, 0); + + if (au_br_writable(add->perm)) { + err = au_wbr_init(br, sb, add->perm, add->nd.dentry, + add->nd.mnt); + if (unlikely(err)) + goto out; + } + + br->br_mnt = mntget(add->nd.mnt); + mnt_flags = au_mntflags(sb); + if (au_opt_test(mnt_flags, XINO)) { + err = au_xino_br(sb, br, add->nd.dentry->d_inode->i_ino, + au_sbr(sb, 0)->br_xino.xi_file, /*do_test*/1); + if (unlikely(err)) { + AuDebugOn(br->br_xino.xi_file); + goto out; + } +#if 0 /* reserved for future use */ + } else if (au_opt_test(mnt_flags, XINODIR)) { + err = au_xinodir_br(sb, br, add->nd.dentry->d_inode->i_ino, + /*do_test*/1); + if (unlikely(err)) { + AuDebugOn(br->br_xino.xi_file); + goto out; + } +#endif + } + + br->br_id = au_new_br_id(sb); + br->br_perm = add->perm; + br->br_xino_upper = AUFS_XINO_TRUNC_INIT; + br->br_generation = au_sigen(sb); + /* smp_mb(); */ /* atomic_set */ + + out: + AuTraceErr(err); + return err; +} + +int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount) +{ + int err, amount; + aufs_bindex_t bend, add_bindex; + struct dentry *root, *dentry; + struct au_iinfo *iinfo; + struct au_sbinfo *sbinfo; + struct au_dinfo *dinfo; + struct inode *root_inode; + unsigned long long maxb; + struct au_branch **branchp, *add_branch; + struct au_hdentry *hdentryp; + struct au_hinode *hinodep; + + dentry = add->nd.dentry; + LKTRTrace("b%d, %s, 0x%x, %.*s\n", + add->bindex, add->path, add->perm, AuDLNPair(dentry)); + SiMustWriteLock(sb); + root = sb->s_root; + DiMustWriteLock(root); + root_inode = root->d_inode; + IMustLock(root_inode); + IiMustWriteLock(root_inode); + + err = test_add(sb, add, remount); + if (unlikely(err < 0)) + goto out; + if (err) { + err = 0; + goto out; /* success */ + } + + bend = au_sbend(sb); + add_branch = alloc_addbr(sb, bend + 2, add->perm); + err = PTR_ERR(add_branch); + if (IS_ERR(add_branch)) + goto out; + err = au_br_init(add_branch, sb, add); + if (unlikely(err)) { + au_br_do_free(add_branch); + goto out; + } + + add_bindex = add->bindex; + if (remount) + sysaufs_brs_del(sb); + + sbinfo = au_sbi(sb); + dinfo = au_di(root); + iinfo = au_ii(root_inode); + + amount = bend + 1 - add_bindex; + branchp = sbinfo->si_branch + add_bindex; + memmove(branchp + 1, branchp, sizeof(*branchp) * amount); + *branchp = add_branch; + hdentryp = dinfo->di_hdentry + add_bindex; + memmove(hdentryp + 1, hdentryp, sizeof(*hdentryp) * amount); + au_h_dentry_init(hdentryp); + hinodep = iinfo->ii_hinode + add_bindex; + memmove(hinodep + 1, hinodep, sizeof(*hinodep) * amount); + hinodep->hi_inode = NULL; + au_hin_init(hinodep, NULL); + + sbinfo->si_bend++; + dinfo->di_bend++; + iinfo->ii_bend++; + if (unlikely(bend < 0)) { + sbinfo->si_bend = 0; + dinfo->di_bstart = 0; + iinfo->ii_bstart = 0; + } + au_set_h_dptr(root, add_bindex, dget(dentry)); + au_set_h_iptr(root_inode, add_bindex, au_igrab(dentry->d_inode), 0); + if (!add_bindex) + au_cpup_attr_all(root_inode, /*force*/1); + else + au_add_nlink(root_inode, dentry->d_inode); + maxb = dentry->d_sb->s_maxbytes; + if (sb->s_maxbytes < maxb) + sb->s_maxbytes = maxb; + + if (remount) + sysaufs_brs_add(sb); + + /* safe d_parent reference */ + if (!au_xino_def_br(sbinfo) + && add_branch->br_xino.xi_file + && add_branch->br_xino.xi_file->f_dentry->d_parent == dentry) + au_xino_def_br_set(add_branch, sbinfo); + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +#define AuVerbose(do_info, fmt, args...) do { \ + if (!do_info) \ + LKTRTrace(fmt, ##args); \ + else \ + AuInfo(fmt, ##args); \ +} while (0) + +/* + * test if the branch is deletable or not. + */ +static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex, + au_gen_t sigen) +{ + int err, i, j, ndentry; + struct au_dcsub_pages dpages; + struct au_dpage *dpage; + struct dentry *d; + aufs_bindex_t bstart, bend; + unsigned char verbose; + struct inode *inode; + + LKTRTrace("b%d, gen%d\n", bindex, sigen); + SiMustWriteLock(root->d_sb); + + err = au_dpages_init(&dpages, GFP_NOFS); + if (unlikely(err)) + goto out; + err = au_dcsub_pages(&dpages, root, NULL, NULL); + if (unlikely(err)) + goto out_dpages; + + verbose = !!au_opt_test(au_mntflags(root->d_sb), VERBOSE); + for (i = 0; !err && i < dpages.ndpage; i++) { + dpage = dpages.dpages + i; + ndentry = dpage->ndentry; + for (j = 0; !err && j < ndentry; j++) { + d = dpage->dentries[j]; + AuDebugOn(!atomic_read(&d->d_count)); + inode = d->d_inode; + AuDebugOn(!inode); + if (au_digen(d) == sigen + && au_iigen(inode) == sigen) + di_read_lock_child(d, AuLock_IR); + else { + di_write_lock_child(d); + err = au_reval_dpath(d, sigen); + if (!err) + di_downgrade_lock(d, AuLock_IR); + else { + di_write_unlock(d); + break; + } + } + + bstart = au_dbstart(d); + bend = au_dbend(d); + if (bstart <= bindex + && bindex <= bend + && au_h_dptr(d, bindex) + && (!S_ISDIR(d->d_inode->i_mode) + || bstart == bend)) { + err = -EBUSY; + AuVerbose(verbose, "busy %.*s\n", AuDLNPair(d)); + } + di_read_unlock(d, AuLock_IR); + } + } + + out_dpages: + au_dpages_free(&dpages); + out: + AuTraceErr(err); + return err; +} + +static int test_inode_busy(struct super_block *sb, aufs_bindex_t bindex, + au_gen_t sigen) +{ + int err; + struct inode *i; + aufs_bindex_t bstart, bend; + unsigned char verbose; + + LKTRTrace("b%d, gen%d\n", bindex, sigen); + SiMustWriteLock(sb); + + err = 0; + verbose = !!au_opt_test(au_mntflags(sb), VERBOSE); + list_for_each_entry(i, &sb->s_inodes, i_sb_list) { + AuDebugOn(!atomic_read(&i->i_count)); + if (!list_empty(&i->i_dentry)) + continue; + + if (au_iigen(i) == sigen) + ii_read_lock_child(i); + else { + ii_write_lock_child(i); + err = au_refresh_hinode_self(i); + if (!err) + ii_downgrade_lock(i); + else { + ii_write_unlock(i); + break; + } + } + + bstart = au_ibstart(i); + bend = au_ibend(i); + if (bstart <= bindex + && bindex <= bend + && au_h_iptr(i, bindex) + && (!S_ISDIR(i->i_mode) || bstart == bend)) { + err = -EBUSY; + AuVerbose(verbose, "busy i%lu\n", i->i_ino); + ii_read_unlock(i); + break; + } + ii_read_unlock(i); + } + + AuTraceErr(err); + return err; +} + +static int test_children_busy(struct dentry *root, aufs_bindex_t bindex) +{ + int err; + au_gen_t sigen; + + LKTRTrace("b%d\n", bindex); + SiMustWriteLock(root->d_sb); + DiMustWriteLock(root); + /* dont trust BKL */ + AuDebugOn(!kernel_locked()); + + sigen = au_sigen(root->d_sb); + DiMustNoWaiters(root); + IiMustNoWaiters(root->d_inode); + di_write_unlock(root); + err = test_dentry_busy(root, bindex, sigen); + if (!err) + err = test_inode_busy(root->d_sb, bindex, sigen); + di_write_lock_child(root); /* aufs_write_lock() calls ..._child() */ + + AuTraceErr(err); + return err; +} + +int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount) +{ + int err, rerr, i; + aufs_bindex_t bindex, bend, br_id; + unsigned char do_wh, verbose; + struct au_sbinfo *sbinfo; + struct au_dinfo *dinfo; + struct au_iinfo *iinfo; + struct au_branch *br, **brp; + struct au_wbr *wbr; + struct au_hdentry *hdp; + struct au_hinode *hip; + + LKTRTrace("%s, %.*s\n", del->path, AuDLNPair(del->h_root)); + SiMustWriteLock(sb); + DiMustWriteLock(sb->s_root); + IiMustWriteLock(sb->s_root->d_inode); + + err = 0; + bindex = au_find_dbindex(sb->s_root, del->h_root); + if (bindex < 0) { + if (remount) + goto out; /* success */ + err = -ENOENT; + AuErr("%s no such branch\n", del->path); + goto out; + } + LKTRTrace("bindex b%d\n", bindex); + + err = -EBUSY; + verbose = !!au_opt_test(au_mntflags(sb), VERBOSE); + bend = au_sbend(sb); + if (unlikely(!bend)) { + AuVerbose(verbose, "no more branches left\n"); + goto out; + } + br = au_sbr(sb, bindex); + if (unlikely(au_br_count(br))) { + AuVerbose(verbose, "%d file(s) opened\n", au_br_count(br)); + goto out; + } + + wbr = br->br_wbr; + do_wh = wbr && (wbr->wbr_whbase || wbr->wbr_plink || wbr->wbr_tmp); + if (do_wh) { +#if 0 /* reserved for future use */ + /* remove whiteout base */ + err = au_br_init_wh(sb, bindex, br, AuBr_RO, del->h_root, + br->br_mnt); + if (unlikely(err)) + goto out; +#else + for (i = 0; i < AuBrWh_Last; i++) { + dput(wbr->wbr_wh[i]); + wbr->wbr_wh[i] = NULL; + } +#endif + } + + err = test_children_busy(sb->s_root, bindex); + if (unlikely(err)) { + if (do_wh) + goto out_wh; + goto out; + } + + err = 0; + if (remount) + sysaufs_brs_del(sb); + sbinfo = au_sbi(sb); + dinfo = au_di(sb->s_root); + iinfo = au_ii(sb->s_root->d_inode); + + dput(au_h_dptr(sb->s_root, bindex)); + au_hiput(iinfo->ii_hinode + bindex); + br_id = br->br_id; + au_br_do_free(br); + + /* todo: realloc and shrink memory? */ + if (bindex < bend) { + const aufs_bindex_t n = bend - bindex; + + brp = sbinfo->si_branch + bindex; + memmove(brp, brp + 1, sizeof(*brp) * n); + hdp = dinfo->di_hdentry + bindex; + memmove(hdp, hdp + 1, sizeof(*hdp) * n); + hip = iinfo->ii_hinode + bindex; + memmove(hip, hip + 1, sizeof(*hip) * n); + } + sbinfo->si_branch[0 + bend] = NULL; + dinfo->di_hdentry[0 + bend].hd_dentry = NULL; + iinfo->ii_hinode[0 + bend].hi_inode = NULL; + au_hin_init(iinfo->ii_hinode + bend, NULL); + + sbinfo->si_bend--; + dinfo->di_bend--; + iinfo->ii_bend--; + if (!bindex) + au_cpup_attr_all(sb->s_root->d_inode, /*force*/1); + else + au_sub_nlink(sb->s_root->d_inode, del->h_root->d_inode); + if (au_opt_test(au_mntflags(sb), PLINK)) + au_plink_half_refresh(sb, br_id); + + if (sb->s_maxbytes == del->h_root->d_sb->s_maxbytes) { + bend--; + sb->s_maxbytes = 0; + for (bindex = 0; bindex <= bend; bindex++) { + unsigned long long maxb; + maxb = au_sbr_sb(sb, bindex)->s_maxbytes; + if (sb->s_maxbytes < maxb) + sb->s_maxbytes = maxb; + } + } + if (remount) + sysaufs_brs_add(sb); + + if (au_xino_def_br(sbinfo) == br) + au_xino_def_br_set(NULL, sbinfo); + goto out; /* success */ + + out_wh: + /* revert */ + rerr = au_br_init_wh(sb, bindex, br, br->br_perm, del->h_root, + br->br_mnt); + if (rerr) + AuWarn("failed re-creating base whiteout, %s. (%d)\n", + del->path, rerr); + out: + AuTraceErr(err); + return err; +} + +static int do_need_sigen_inc(int a, int b) +{ + return au_br_whable(a) && !au_br_whable(b); +} + +static int need_sigen_inc(int old, int new) +{ + return do_need_sigen_inc(old, new) + || do_need_sigen_inc(new, old); +} + +static int au_br_mod_files_ro(struct super_block *sb, aufs_bindex_t bindex) +{ + int err; + struct file *file, *hf; + + AuTraceEnter(); + SiMustWriteLock(sb); + + /* no need file_list_lock() since sbinfo is locked */ + err = 0; + list_for_each_entry(file, &sb->s_files, f_u.fu_list) { + LKTRTrace("%.*s\n", AuDLNPair(file->f_dentry)); + if (!au_test_aufs_file(file)) + continue; + + fi_read_lock(file); + if (!S_ISREG(file->f_dentry->d_inode->i_mode) + || !(file->f_mode & FMODE_WRITE) + || au_fbstart(file) != bindex) { + FiMustNoWaiters(file); + fi_read_unlock(file); + continue; + } + + if (unlikely(au_test_mmapped(file))) { + err = -EBUSY; + FiMustNoWaiters(file); + fi_read_unlock(file); + break; + } + + /* todo: already flushed? */ + hf = au_h_fptr(file, au_fbstart(file)); + hf->f_flags = au_file_roflags(hf->f_flags); + hf->f_mode &= ~FMODE_WRITE; + put_write_access(hf->f_dentry->d_inode); + FiMustNoWaiters(file); + fi_read_unlock(file); + } + + AuTraceErr(err); + return err; +} + +int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount, + int *do_update) +{ + int err; + struct dentry *root; + aufs_bindex_t bindex; + struct au_branch *br; + struct inode *hidden_dir; + + LKTRTrace("%s, %.*s, 0x%x\n", + mod->path, AuDLNPair(mod->h_root), mod->perm); + SiMustWriteLock(sb); + root = sb->s_root; + DiMustWriteLock(root); + IiMustWriteLock(root->d_inode); + + bindex = au_find_dbindex(root, mod->h_root); + if (bindex < 0) { + if (remount) + return 0; /* success */ + err = -ENOENT; + AuErr("%s no such branch\n", mod->path); + goto out; + } + LKTRTrace("bindex b%d\n", bindex); + + hidden_dir = mod->h_root->d_inode; + err = test_br(sb, hidden_dir, mod->perm, mod->path); + if (unlikely(err)) + goto out; + + br = au_sbr(sb, bindex); + if (br->br_perm == mod->perm) + return 0; /* success */ + + if (au_br_writable(br->br_perm)) { +#if 1 + /* remove whiteout base */ + err = au_br_init_wh(sb, bindex, br, mod->perm, mod->h_root, + br->br_mnt); + if (unlikely(err)) + goto out; +#else /* reserved for future use */ + struct au_wbr *wbr; + wbr = br->wbr; + if (wbr) + for (i = 0; i < AuBrWh_Last; i++) { + dput(wbr->wbr_wh[i]); + wbr->wbr_wh[i] = NULL; + } +#endif + + if (!au_br_writable(mod->perm)) { + /* rw --> ro, file might be mmapped */ + +#if 1 /* todo: test more? */ + DiMustNoWaiters(root); + IiMustNoWaiters(root->d_inode); + di_write_unlock(root); + err = au_br_mod_files_ro(sb, bindex); + /* aufs_write_lock() calls ..._child() */ + di_write_lock_child(root); +#endif + } + } else if (au_br_writable(mod->perm)) { + /* ro --> rw */ + err = -ENOMEM; + br->br_wbr = kmalloc(sizeof(*br->br_wbr), GFP_NOFS); + if (br->br_wbr) { + err = au_wbr_init(br, sb, mod->perm, mod->h_root, + br->br_mnt); + if (unlikely(err)) { + kfree(br->br_wbr); + br->br_wbr = NULL; + } + } + } + + if (!err) { + *do_update |= need_sigen_inc(br->br_perm, mod->perm); + br->br_perm = mod->perm; + } + + out: + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/branch.h b/fs/aufs/branch.h new file mode 100644 index 0000000..ece9b51 --- /dev/null +++ b/fs/aufs/branch.h @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * branch filesystems and xino for them + * + * $Id: branch.h,v 1.64 2009/01/26 06:23:47 sfjro Exp $ + */ + +#ifndef __AUFS_BRANCH_H__ +#define __AUFS_BRANCH_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include "misc.h" +#include "super.h" + +/* ---------------------------------------------------------------------- */ + +/* an entry in a xino file */ +struct au_xino_entry { + ino_t ino; + /* __u32 h_gen; */ /* reserved for future use */ +} __packed; + +/* reserved for future use */ +/* #define AuXino_INVALID_HGEN (-1) */ + +/* a xino file */ +struct au_xino_file { + struct file *xi_file; + struct mutex xi_nondir_mtx; + + /* reserved for future use */ +#if 0 + struct file **xi_file; + + /* array management */ + unsigned long long xi_limit; /* Max xino file size */ + unsigned long long xi_size; /* s_maxbytes */ + + /* truncation */ + unsigned long long xi_upper; /* watermark in bytes */ + unsigned long long xi_step; /* to next watermark in bytes */ + + /* truncation */ + blkcnt_t xi_upper; /* watermark in blocks */ + atomic_t xi_running; +#endif +}; + +/* members for writable branch only */ +enum {AuBrWh_BASE, AuBrWh_PLINK, AuBrWh_TMP, AuBrWh_Last}; +struct au_wbr { + struct au_rwsem wbr_wh_rwsem; + struct dentry *wbr_wh[AuBrWh_Last]; + atomic_t wbr_wh_running; +#define wbr_whbase wbr_wh[AuBrWh_BASE] /* whiteout base */ +#define wbr_plink wbr_wh[AuBrWh_PLINK] /* pseudo-link dir */ +#define wbr_tmp wbr_wh[AuBrWh_TMP] /* temporary dir */ + + /* mfs mode */ + unsigned long long wbr_bytes; +}; + +/* protected by superblock rwsem */ +struct sysaufs_br; +struct au_branch { + struct au_xino_file br_xino; + + aufs_bindex_t br_id; + + int br_perm; + struct vfsmount *br_mnt; + atomic_t br_count; + + struct au_wbr *br_wbr; + +#if 1 /* reserved for future use */ + /* xino truncation */ + blkcnt_t br_xino_upper; /* watermark in blocks */ + atomic_t br_xino_running; +#endif + + + au_gen_t br_generation; + + /* an entry under sysfs per mount-point */ + struct sysaufs_br *br_sabr; +}; + +/* ---------------------------------------------------------------------- */ + +/* branch permission and attribute */ +enum { + AuBrPerm_RW, /* writable, linkable wh */ + AuBrPerm_RO, /* readonly, no wh */ + AuBrPerm_RR, /* natively readonly, no wh */ + + AuBrPerm_RWNoLinkWH, /* un-linkable whiteouts */ + + AuBrPerm_ROWH, + AuBrPerm_RRWH, /* whiteout-able */ + + AuBrPerm_Last +}; + +static inline int au_br_writable(int brperm) +{ + return brperm == AuBrPerm_RW || brperm == AuBrPerm_RWNoLinkWH; +} + +static inline int au_br_whable(int brperm) +{ + return brperm == AuBrPerm_RW + || brperm == AuBrPerm_ROWH + || brperm == AuBrPerm_RRWH; +} + +#if 0 /* reserved for future use */ +static inline int au_br_linkable_wh(int brperm) +{ + return brperm == AuBrPerm_RW; +} +#endif + +static inline int au_br_hinotifyable(int brperm) +{ +#ifdef CONFIG_AUFS_HINOTIFY + return brperm != AuBrPerm_RR && brperm != AuBrPerm_RRWH; +#else + return 0; +#endif +} + +/* ---------------------------------------------------------------------- */ + +/* branch.c */ +struct au_sbinfo; +void au_br_free(struct au_sbinfo *sinfo); +int au_test_def_rr(struct super_block *h_sb); +int au_br_index(struct super_block *sb, aufs_bindex_t br_id); +struct au_opt_add; +int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount); +struct au_opt_del; +int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount); +struct au_opt_mod; +int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount, + int *do_update); + +/* xino.c */ +#ifndef LLONG_MAX /* before linux-2.6.18 */ +#define LLONG_MAX ((long long)(~0ULL >> 1)) +#endif +#define Au_LOFF_MAX ((loff_t)LLONG_MAX) +int au_xib_trunc(struct super_block *sb); +ssize_t xino_fread(au_readf_t func, struct file *file, void *buf, size_t size, + loff_t *pos); +ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size, + loff_t *pos); +struct file *au_xino_create(struct super_block *sb, char *fname, int silent); +struct file *au_xino_create2(struct super_block *sb, struct file *base_file, + struct file *copy_src); +ino_t au_xino_new_ino(struct super_block *sb); +int au_xino_write0(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + ino_t ino); +int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + struct au_xino_entry *xinoe); +int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + struct au_xino_entry *xinoe); +int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t hino, + struct file *base_file, int do_test); +int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex); + +struct au_opt_xino; +int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount); +void au_xino_clr(struct super_block *sb); +struct file *au_xino_def(struct super_block *sb); + +struct au_opt_xinodir; +#if 0 /* def CONFIG_AUFS_EXPORT */ /* reserved for future use */ +/* export.c */ +int au_xinodir_br(struct super_block *sb, struct au_branch *br, ino_t hino, + int do_test); +int au_xinodir_set(struct super_block *sb, struct au_opt_xinodir *xinodir, + int remount); +#else +static inline +int au_xinodir_br(struct super_block *sb, struct au_branch *br, ino_t hino, + int do_test) +{ + return 0; +} + +static inline +int au_xinodir_set(struct super_block *sb, struct au_opt_xinodir *xinodir, + int remount) +{ + return 0; +} +#endif + +/* ---------------------------------------------------------------------- */ + +/* todo: memory barrier? */ +static inline int au_br_count(struct au_branch *br) +{ + return atomic_read(&br->br_count); +} + +static inline int au_br_get(struct au_branch *br) +{ + return atomic_inc_return(&br->br_count); +} + +static inline int au_br_put(struct au_branch *br) +{ + return atomic_dec_return(&br->br_count); +} + +static inline au_gen_t au_br_gen(struct au_branch *br) +{ + return br->br_generation; +} + +/* + * test if the @br is readonly or not. + */ +static inline int au_br_rdonly(struct au_branch *br) +{ + return ((br->br_mnt->mnt_sb->s_flags & MS_RDONLY) + || !au_br_writable(br->br_perm)) + ? -EROFS : 0; +} + +/* ---------------------------------------------------------------------- */ + +/* Superblock to branch */ +static inline +aufs_bindex_t au_sbr_id(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_sbr(sb, bindex)->br_id; +} + +static inline +struct vfsmount *au_sbr_mnt(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_sbr(sb, bindex)->br_mnt; +} + +static inline +struct super_block *au_sbr_sb(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_sbr_mnt(sb, bindex)->mnt_sb; +} + +#if 0 /* reserved for future use */ +static inline int au_sbr_count(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_br_count(au_sbr(sb, bindex)); +} + +static inline void au_sbr_get(struct super_block *sb, aufs_bindex_t bindex) +{ + au_br_get(au_sbr(sb, bindex)); +} +#endif + +static inline void au_sbr_put(struct super_block *sb, aufs_bindex_t bindex) +{ + au_br_put(au_sbr(sb, bindex)); +} + +static inline int au_sbr_perm(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_sbr(sb, bindex)->br_perm; +} + +static inline int au_sbr_whable(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_br_whable(au_sbr_perm(sb, bindex)); +} + +static inline int au_br_want_write(struct au_branch *br) +{ + int err; + + AuDebugOn(!au_br_writable(br->br_perm)); + err = au_mnt_want_write(br->br_mnt); + AuTraceErr(err); + return err; +} + +static inline void au_br_drop_write(struct au_branch *br) +{ + AuDebugOn(!au_br_writable(br->br_perm)); + au_mnt_drop_write(br->br_mnt); +} + +static inline int au_test_trunc_xino(struct super_block *sb) +{ + return au_test_tmpfs(sb); +} + +/* temporary support for i#1 in cramfs */ +static inline int au_test_unique_ino(struct dentry *h_dentry, ino_t h_ino) +{ +#if defined(CONFIG_CRAMFS) || defined(CONFIG_CRAMFS_MODULE) + if (unlikely(h_dentry->d_sb->s_magic == CRAMFS_MAGIC)) + return h_ino != 1; +#endif + return 1; +} + +static inline struct vfsmount *au_do_nfsmnt(struct vfsmount *h_mnt) +{ + if (!au_test_nfs(h_mnt->mnt_sb) + && !au_test_ecryptfs(h_mnt->mnt_sb)) + return NULL; + return h_mnt; +} + +static inline void au_br_nfs_lockdep_off(struct super_block *sb) +{ + if (au_test_nfs(sb)) + lockdep_off(); +} + +static inline void au_br_nfs_lockdep_on(struct super_block *sb) +{ + /* hoping this condition will be optimized... */ + if (au_test_nfs(sb)) + lockdep_on(); +} + +#ifdef CONFIG_AUFS_BR_NFS +static inline int au_test_unsupported_nfs(struct super_block *h_sb) +{ + return 0; +} + +/* it doesn't mntget() */ +static inline +struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex) +{ + return au_do_nfsmnt(au_sbr_mnt(sb, bindex)); +} + +#define AuNoNfsBranchMsg "dummy" + +#else +static inline int au_test_unsupported_nfs(struct super_block *h_sb) +{ + return h_sb->s_magic == NFS_SUPER_MAGIC; +} + +/* it doesn't mntget() */ +static inline +struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex) +{ + return NULL; +} + +#define _AuNoNfsBranchMsg "NFS branch is not supported" +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) +#define AuNoNfsBranchMsg _AuNoNfsBranchMsg "." +#else //if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#define AuNoNfsBranchMsg _AuNoNfsBranchMsg \ + ", try some configurations and patches included in aufs source CVS." +#endif + +#endif /* CONFIG_AUFS_BR_NFS */ + +#ifdef CONFIG_AUFS_BR_NFS_V4 +static inline int au_test_unsupported_nfs4(struct super_block *h_sb) +{ + return 0; +} + +#define AuNoNfsv4BranchMsg "dummy" + +#else +static inline int au_test_unsupported_nfs4(struct super_block *h_sb) +{ + return h_sb->s_magic == NFS_SUPER_MAGIC + && !strcmp(h_sb->s_type->name, "nfs4"); +} + +#define AuNoNfsv4BranchMsg "NFSv4 branch is not supported" \ + ", try some configurations and patches included in aufs source CVS." + +#endif /* CONFIG_AUFS_BR_NFS_v4 */ + +/* support atomic open */ +static inline int au_test_fs_intent(struct super_block *h_sb) +{ + return au_test_nfs4(h_sb) /*|| au_test_fuse(h_sb)*/; +} + +/* ---------------------------------------------------------------------- */ + +/* + * br_wh_read_lock, br_wh_write_lock + * br_wh_read_unlock, br_wh_write_unlock, br_wh_downgrade_lock + */ +AuSimpleRwsemFuncs(wbr_wh, struct au_wbr *wbr, wbr->wbr_wh_rwsem); + +/* to debug easier, do not make them inlined functions */ +#define WbrWhMustReadLock(wbr) do { \ + /* SiMustAnyLock(sb); */ \ + AuRwMustReadLock(&(wbr)->wbr_wh_rwsem); \ +} while (0) + +#define WbrWhMustWriteLock(wbr) do { \ + /* SiMustAnyLock(sb); */ \ + AuRwMustWriteLock(&(wbr)->wbr_wh_rwsem); \ +} while (0) + +#define WbrWhMustAnyLock(br) do { \ + /* SiMustAnyLock(sb); */ \ + AuRwMustAnyLock(&(wbr)->wbr_wh_rwsem); \ +} while (0) + +#endif /* __KERNEL__ */ +#endif /* __AUFS_BRANCH_H__ */ diff --git a/fs/aufs/cpup.c b/fs/aufs/cpup.c new file mode 100644 index 0000000..60e55e0 --- /dev/null +++ b/fs/aufs/cpup.c @@ -0,0 +1,1138 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * copy-up functions, see wbr_policy.c for copy-down + * + * $Id: cpup.c,v 1.79 2009/01/26 06:23:51 sfjro Exp $ + */ + +#include "aufs.h" + +/* todo? violent cpup_attr_*() functions don't care inode lock */ + +void au_cpup_attr_timesizes(struct inode *inode) +{ + struct inode *h_inode; + + LKTRTrace("i%lu\n", inode->i_ino); + /* todo? IMustLock(inode); */ + h_inode = au_h_iptr(inode, au_ibstart(inode)); + AuDebugOn(!h_inode); + /* todo? IMustLock(!h_inode); */ + + inode->i_atime = h_inode->i_atime; + inode->i_mtime = h_inode->i_mtime; + inode->i_ctime = h_inode->i_ctime; + spin_lock(&inode->i_lock); + i_size_write(inode, i_size_read(h_inode)); + inode->i_blocks = h_inode->i_blocks; + spin_unlock(&inode->i_lock); +} + +void au_cpup_attr_nlink(struct inode *inode, int force) +{ + struct inode *h_inode; + struct super_block *sb; + aufs_bindex_t bindex, bend; + + LKTRTrace("i%lu\n", inode->i_ino); + /* todo? IMustLock(inode); */ + AuDebugOn(!inode->i_mode); + + sb = inode->i_sb; + bindex = au_ibstart(inode); + h_inode = au_h_iptr(inode, bindex); + + if (!force + && !S_ISDIR(h_inode->i_mode) + && au_opt_test(au_mntflags(sb), PLINK) + && au_plink_test(sb, inode)) + return; + + inode->i_nlink = h_inode->i_nlink; + + /* + * fewer nlink makes find(1) noisy, but larger nlink doesn't. + * it may includes whplink directory. + */ + if (S_ISDIR(h_inode->i_mode)) { + bend = au_ibend(inode); + for (bindex++; bindex <= bend; bindex++) { + h_inode = au_h_iptr(inode, bindex); + if (h_inode) + au_add_nlink(inode, h_inode); + } + } +} + +void au_cpup_attr_changeable(struct inode *inode) +{ + struct inode *h_inode; + + LKTRTrace("i%lu\n", inode->i_ino); + /* todo? IMustLock(inode); */ + h_inode = au_h_iptr(inode, au_ibstart(inode)); + AuDebugOn(!h_inode); + + inode->i_mode = h_inode->i_mode; + inode->i_uid = h_inode->i_uid; + inode->i_gid = h_inode->i_gid; + au_cpup_attr_timesizes(inode); + au_cpup_attr_flags(inode, h_inode); +} + +void au_cpup_igen(struct inode *inode, struct inode *h_inode) +{ + struct au_iinfo *iinfo = au_ii(inode); + iinfo->ii_higen = h_inode->i_generation; + iinfo->ii_hsb1 = h_inode->i_sb; +} + +void au_cpup_attr_all(struct inode *inode, int force) +{ + struct inode *h_inode; + + LKTRTrace("i%lu\n", inode->i_ino); + /* todo? IMustLock(inode); */ + h_inode = au_h_iptr(inode, au_ibstart(inode)); + AuDebugOn(!h_inode); + + au_cpup_attr_changeable(inode); + if (inode->i_nlink > 0) + au_cpup_attr_nlink(inode, force); + + switch (inode->i_mode & S_IFMT) { + case S_IFBLK: + case S_IFCHR: + inode->i_rdev = au_h_rdev(h_inode, /*h_mnt*/NULL, + /*h_dentry*/NULL); + } + inode->i_blkbits = h_inode->i_blkbits; + au_cpup_attr_blksize(inode, h_inode); + au_cpup_igen(inode, h_inode); +} + +/* ---------------------------------------------------------------------- */ + +/* Note: dt_dentry and dt_hidden_dentry are not dget/dput-ed */ + +/* keep the timestamps of the parent dir when cpup */ +void au_dtime_store(struct au_dtime *dt, struct dentry *dentry, + struct dentry *h_dentry, struct au_hinode *hinode, + struct au_hinode *hdir) +{ + struct inode *h_inode; + + LKTRTrace("%.*s, hdir %d\n", AuDLNPair(dentry), !!hdir); + AuDebugOn(!dentry || !h_dentry || !h_dentry->d_inode); + + dt->dt_dentry = dentry; + dt->dt_h_dentry = h_dentry; + dt->dt_hinode = hinode; + dt->dt_hdir = hdir; + h_inode = h_dentry->d_inode; + dt->dt_atime = h_inode->i_atime; + dt->dt_mtime = h_inode->i_mtime; + /* smp_mb(); */ +} + +void au_dtime_revert(struct au_dtime *dt) +{ + struct iattr attr; + int err; + struct au_hin_ignore ign[2]; + struct vfsub_args vargs; + + LKTRTrace("%.*s\n", AuDLNPair(dt->dt_dentry)); + + attr.ia_atime = dt->dt_atime; + attr.ia_mtime = dt->dt_mtime; + attr.ia_valid = ATTR_FORCE | ATTR_MTIME | ATTR_MTIME_SET + | ATTR_ATIME | ATTR_ATIME_SET; + + vfsub_args_init(&vargs, ign, + au_test_dlgt(au_mntflags(dt->dt_dentry->d_sb)), 0); + /* + * IN_ATTRIB should be divided into + * IN_ATTRIB_ATIME, IN_ATTRIB_MTIME ..., + * and define all ORed new IN_ATTRIB macro. + */ + vfsub_ign_hinode(&vargs, IN_ATTRIB, dt->dt_hinode); + vfsub_ign_hinode(&vargs, IN_ATTRIB, dt->dt_hdir); + err = vfsub_notify_change(dt->dt_h_dentry, &attr, &vargs); + if (unlikely(err)) + AuWarn("restoring timestamps failed(%d). ignored\n", err); +} + +/* ---------------------------------------------------------------------- */ + +static noinline_for_stack +int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src, + struct au_hinode *hdir, struct vfsub_args *vargs) +{ + int err, sbits; + struct dentry *h_dst; + struct iattr ia; + struct inode *h_isrc, *h_idst; + + h_dst = au_h_dptr(dst, bindex); + LKTRTrace("%.*s\n", AuDLNPair(h_dst)); + h_idst = h_dst->d_inode; + /* todo? IMustLock(h_idst); */ + h_isrc = h_src->d_inode; + /* todo? IMustLock(h_isrc); */ + + ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID + | ATTR_ATIME | ATTR_MTIME + | ATTR_ATIME_SET | ATTR_MTIME_SET; + ia.ia_mode = h_isrc->i_mode; + ia.ia_uid = h_isrc->i_uid; + ia.ia_gid = h_isrc->i_gid; + ia.ia_atime = h_isrc->i_atime; + ia.ia_mtime = h_isrc->i_mtime; + sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID)); + au_cpup_attr_flags(h_idst, h_isrc); + + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_ATTRIB, hdir); + err = vfsub_notify_change(h_dst, &ia, vargs); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + /* is this nfs only? */ + if (!err && sbits && au_test_nfs(h_dst->d_sb)) { + ia.ia_valid = ATTR_FORCE | ATTR_MODE; + ia.ia_mode = h_isrc->i_mode; + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_ATTRIB, hdir); + err = vfsub_notify_change(h_dst, &ia, vargs); + } +#endif + + AuTraceErr(err); + return err; +} + +/* + * to support a sparse file which is opened with O_APPEND, + * we need to close the file. + */ +static noinline_for_stack +int cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc, + loff_t len, struct au_hinode *hdir, struct vfsub_args *vargs) +{ + int err, i; + struct super_block *sb; + struct inode *h_inode; + enum { SRC, DST }; + struct { + aufs_bindex_t bindex; + unsigned int flags; + struct dentry *dentry; + struct file *file; + void *label, *label_file; + } *h, hidden[] = { + { + .bindex = bsrc, + .flags = O_RDONLY | O_NOATIME | O_LARGEFILE, + .file = NULL, + .label = &&out, + .label_file = &&out_src_file + }, + { + .bindex = bdst, + .flags = O_WRONLY | O_NOATIME | O_LARGEFILE, + .file = NULL, + .label = &&out_src_file, + .label_file = &&out_dst_file + } + }; + + LKTRTrace("dentry %.*s, bdst %d, bsrc %d, len %lld\n", + AuDLNPair(dentry), bdst, bsrc, len); + AuDebugOn(bsrc <= bdst); + AuDebugOn(!len); + sb = dentry->d_sb; + AuDebugOn(au_test_ro(sb, bdst, dentry->d_inode)); + /* bsrc branch can be ro/rw. */ + + h = hidden; + for (i = 0; i < 2; i++, h++) { + h->dentry = au_h_dptr(dentry, h->bindex); + AuDebugOn(!h->dentry); + h_inode = h->dentry->d_inode; + AuDebugOn(!h_inode || !S_ISREG(h_inode->i_mode)); + h->file = au_h_open(dentry, h->bindex, h->flags, /*file*/NULL); + err = PTR_ERR(h->file); + if (IS_ERR(h->file)) + goto *h->label; + err = -EINVAL; + if (unlikely(!h->file->f_op)) + goto *h->label_file; + } + + /* stop updating while we copyup */ + IMustLock(hidden[SRC].dentry->d_inode); + err = au_copy_file(hidden[DST].file, hidden[SRC].file, len, hdir, sb, + vargs); + + out_dst_file: + fput(hidden[DST].file); + au_sbr_put(sb, hidden[DST].bindex); + out_src_file: + fput(hidden[SRC].file); + au_sbr_put(sb, hidden[SRC].bindex); + out: + AuTraceErr(err); + return err; +} + +static int au_do_cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, + aufs_bindex_t bsrc, loff_t len, + struct au_hinode *hdir, struct dentry *h_dst, + struct vfsub_args *vargs) +{ + int err, rerr; + loff_t l; + + AuTraceEnter(); + + err = 0; + l = i_size_read(au_h_iptr(dentry->d_inode, bsrc)); + if (len == -1 || l < len) + len = l; + if (len) + err = cpup_regular(dentry, bdst, bsrc, len, hdir, vargs); + if (!err) + goto out; /* success */ + + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_DELETE, hdir); + rerr = vfsub_unlink(hdir->hi_inode, h_dst, vargs); + if (rerr) { + AuIOErr("failed unlinking cpup-ed %.*s(%d, %d)\n", + AuDLNPair(h_dst), err, rerr); + err = -EIO; + } + + out: + AuTraceErr(err); + return err; +} + +static int au_do_cpup_symlink(struct dentry *h_dst, struct dentry *h_src, + struct inode *h_dir, umode_t mode, + struct vfsub_args *vargs) +{ + int err, symlen; + char *sym; + mm_segment_t old_fs; + + AuTraceEnter(); + + err = -ENOMEM; + sym = __getname(); + if (unlikely(!sym)) + goto out; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + symlen = h_src->d_inode->i_op->readlink(h_src, (char __user *)sym, + PATH_MAX); + err = symlen; + set_fs(old_fs); + + if (symlen > 0) { + sym[symlen] = 0; + err = vfsub_symlink(h_dir, h_dst, sym, mode, vargs); + } + __putname(sym); + + out: + AuTraceErr(err); + return err; +} + +/* return with hidden dst inode is locked */ +static noinline_for_stack +int cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc, + loff_t len, unsigned int flags, struct dentry *dst_parent, + struct vfsub_args *vargs) +{ + int err; + unsigned char isdir, hinotify; + struct dentry *h_src, *h_dst, *h_parent, *gparent; + struct inode *h_inode, *h_dir; + struct au_dtime dt; + umode_t mode; + struct super_block *sb; + struct au_hinode *hgdir, *hdir; + unsigned int mnt_flags; + const int do_dt = au_ftest_cpup(flags, DTIME); + + LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %lld, dtime %u\n", + AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len, + do_dt); + sb = dentry->d_sb; + AuDebugOn(bdst >= bsrc || au_test_ro(sb, bdst, NULL)); + /* bsrc branch can be ro/rw. */ + + h_src = au_h_dptr(dentry, bsrc); + AuDebugOn(!h_src); + h_inode = h_src->d_inode; + AuDebugOn(!h_inode); + AuDebugOn(h_inode != au_h_iptr(dentry->d_inode, bsrc)); + + /* stop referencing while we are creating */ + h_dst = au_h_dptr(dentry, bdst); + AuDebugOn(h_dst && h_dst->d_inode); + h_parent = h_dst->d_parent; /* dir inode is locked */ + h_dir = h_parent->d_inode; + IMustLock(h_dir); + AuDebugOn(h_parent != h_dst->d_parent); + + hdir = NULL; + mnt_flags = au_mntflags(sb); + hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY); + if (hinotify) { + hdir = au_hi(dst_parent->d_inode, bdst); + AuDebugOn(hdir->hi_inode != h_dir); + } + + if (do_dt) { + hgdir = NULL; + if (hinotify && !IS_ROOT(dst_parent)) { + gparent = dget_parent(dst_parent); + hgdir = au_hi(gparent->d_inode, bdst); + IMustLock(hgdir->hi_inode); + dput(gparent); + } + au_dtime_store(&dt, dst_parent, h_parent, hdir, hgdir); + } + + isdir = 0; + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_CREATE, hdir); + mode = h_inode->i_mode; + switch (mode & S_IFMT) { + case S_IFREG: + /* stop updating while we are referencing */ + IMustLock(h_inode); + err = au_h_create(h_dir, h_dst, mode | S_IWUSR, vargs, NULL, + au_nfsmnt(sb, bdst)); + if (!err) + err = au_do_cpup_regular(dentry, bdst, bsrc, len, + hdir, h_dst, vargs); + break; + case S_IFDIR: + isdir = 1; + err = vfsub_mkdir(h_dir, h_dst, mode, vargs); + if (!err) { + /* setattr case: dir is not locked */ + if (0 && au_ibstart(dst_parent->d_inode) == bdst) + au_cpup_attr_nlink(dst_parent->d_inode, + /*force*/1); + au_cpup_attr_nlink(dentry->d_inode, /*force*/1); + } + break; + case S_IFLNK: + err = au_do_cpup_symlink(h_dst, h_src, h_dir, mode, vargs); + break; + case S_IFCHR: + case S_IFBLK: + AuDebugOn(!capable(CAP_MKNOD)); + /*FALLTHROUGH*/ + case S_IFIFO: + case S_IFSOCK: + err = vfsub_mknod(h_dir, h_dst, mode, + au_h_rdev(h_inode, /*h_mnt*/NULL, h_src), + vargs); + break; + default: + AuIOErr("Unknown inode type 0%o\n", mode); + err = -EIO; + } + + if (hinotify + && !isdir + && au_opt_test_xino(mnt_flags) + && h_inode->i_nlink == 1 + /* todo: unnecessary? */ + /* && dentry->d_inode->i_nlink == 1 */ + && bdst < bsrc + && !au_ftest_cpup(flags, KEEPLINO)) + au_xino_write0(sb, bsrc, h_inode->i_ino, /*ino*/0); + /* ignore this error */ + + if (do_dt) + au_dtime_revert(&dt); + AuTraceErr(err); + return err; +} + +/* + * copyup the @dentry from @bsrc to @bdst. + * the caller must set the both of hidden dentries. + * @len is for truncating when it is -1 copyup the entire file. + */ +static int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst, + aufs_bindex_t bsrc, loff_t len, unsigned int flags, + struct dentry *dst_parent, struct vfsub_args *vargs) +{ + int err, rerr; + unsigned int mnt_flags; + aufs_bindex_t old_ibstart; + unsigned char isdir, plink, hinotify; + struct au_dtime dt; + struct dentry *h_src, *h_dst, *h_parent, *gparent; + struct inode *dst_inode, *h_dir, *inode; + struct super_block *sb; + struct au_hinode *hgdir, *hdir; + + LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %lld, flags 0x%x\n", + AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len, + flags); + sb = dentry->d_sb; + AuDebugOn(bsrc <= bdst); + h_dst = au_h_dptr(dentry, bdst); + AuDebugOn(!h_dst || h_dst->d_inode); + h_parent = h_dst->d_parent; /* dir inode is locked */ + h_dir = h_parent->d_inode; + IMustLock(h_dir); + h_src = au_h_dptr(dentry, bsrc); + AuDebugOn(!h_src || !h_src->d_inode); + inode = dentry->d_inode; + IiMustWriteLock(inode); + if (!dst_parent) + dst_parent = dget_parent(dentry); + else + dget(dst_parent); + + mnt_flags = au_mntflags(sb); + plink = !!au_opt_test(mnt_flags, PLINK); + hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY); + hdir = NULL; + if (hinotify) + hdir = au_hi(dst_parent->d_inode, bdst); + dst_inode = au_h_iptr(inode, bdst); + if (dst_inode) { + if (unlikely(!plink)) { + err = -EIO; + AuIOErr("i%lu exists on a upper branch " + "but plink is disabled\n", inode->i_ino); + goto out; + } + + if (dst_inode->i_nlink) { + const int do_dt = au_ftest_cpup(flags, DTIME); + + h_src = au_plink_lkup(sb, bdst, inode); + err = PTR_ERR(h_src); + if (IS_ERR(h_src)) + goto out; + if (unlikely(!h_src->d_inode)) { + err = -EIO; + AuIOErr("i%lu exists on a upper branch " + "but plink is broken\n", inode->i_ino); + dput(h_src); + goto out; + } + + if (do_dt) { + hgdir = NULL; + if (hinotify && !IS_ROOT(dst_parent)) { + gparent = dget_parent(dst_parent); + hgdir = au_hi(gparent->d_inode, bdst); + IMustLock(hgdir->hi_inode); + dput(gparent); + } + au_dtime_store(&dt, dst_parent, h_parent, hdir, + hgdir); + } + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_CREATE, hdir); + err = vfsub_link(h_src, h_dir, h_dst, vargs); + if (do_dt) + au_dtime_revert(&dt); + dput(h_src); + goto out; + } else + /* todo: cpup_wh_file? */ + /* udba work */ + au_update_brange(inode, 1); + } + + old_ibstart = au_ibstart(inode); + err = cpup_entry(dentry, bdst, bsrc, len, flags, dst_parent, vargs); + if (unlikely(err)) + goto out; + dst_inode = h_dst->d_inode; + vfsub_i_lock_nested(dst_inode, AuLsc_I_CHILD2); + + /* todo: test dlgt? */ + err = cpup_iattr(dentry, bdst, h_src, hdir, vargs); +#if 0 /* reserved for future use */ + if (0 && !err) + err = cpup_xattrs(h_src, h_dst); +#endif + isdir = S_ISDIR(dst_inode->i_mode); + if (!err) { + if (bdst < old_ibstart) + au_set_ibstart(inode, bdst); + au_set_h_iptr(inode, bdst, au_igrab(dst_inode), + au_hi_flags(inode, isdir)); + vfsub_i_unlock(dst_inode); + if (!isdir + && h_src->d_inode->i_nlink > 1 + && plink) + au_plink_append(sb, inode, h_dst, bdst); + goto out; /* success */ + } + + /* revert */ + vfsub_i_unlock(dst_inode); + hgdir = NULL; + if (au_opt_test(mnt_flags, UDBA_INOTIFY) && !IS_ROOT(dst_parent)) { + gparent = dget_parent(dst_parent); + hgdir = au_hi(gparent->d_inode, bdst); + dput(gparent); + } + au_dtime_store(&dt, dst_parent, h_parent, hdir, hgdir); + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_DELETE, hdir); + if (!isdir) + rerr = vfsub_unlink(h_dir, h_dst, vargs); + else + rerr = vfsub_rmdir(h_dir, h_dst, vargs); + au_dtime_revert(&dt); + if (rerr) { + AuIOErr("failed removing broken entry(%d, %d)\n", err, rerr); + err = -EIO; + } + + out: + dput(dst_parent); + AuTraceErr(err); + return err; +} + +struct au_cpup_single_args { + int *errp; + struct dentry *dentry; + aufs_bindex_t bdst, bsrc; + loff_t len; + unsigned int flags; + struct dentry *dst_parent; + struct vfsub_args *vargs; +}; + +static void au_call_cpup_single(void *args) +{ + struct au_cpup_single_args *a = args; + *a->errp = au_cpup_single(a->dentry, a->bdst, a->bsrc, a->len, + a->flags, a->dst_parent, a->vargs); +} + +int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst, + aufs_bindex_t bsrc, loff_t len, unsigned int flags, + struct dentry *dst_parent) +{ + int err, wkq_err; + struct dentry *h_dentry; + umode_t mode; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %lld, flags 0x%x\n", + AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len, + flags); + + vfsub_args_init(&vargs, &ign, au_test_dlgt(au_mntflags(dentry->d_sb)), + /*force_unlink*/0); + h_dentry = au_h_dptr(dentry, bsrc); + mode = h_dentry->d_inode->i_mode & S_IFMT; + if ((mode != S_IFCHR && mode != S_IFBLK) + || capable(CAP_MKNOD)) + err = au_cpup_single(dentry, bdst, bsrc, len, flags, + dst_parent, &vargs); + else { + struct au_cpup_single_args args = { + .errp = &err, + .dentry = dentry, + .bdst = bdst, + .bsrc = bsrc, + .len = len, + .flags = flags, + .dst_parent = dst_parent, + .vargs = &vargs + }; + vfsub_fclr(vargs.flags, DLGT); + wkq_err = au_wkq_wait(au_call_cpup_single, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } + + AuTraceErr(err); + return err; +} + +/* + * copyup the @dentry from the first active hidden branch to @bdst, + * using au_cpup_single(). + */ +static int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len, + unsigned int flags, struct vfsub_args *vargs) +{ + int err; + struct inode *inode; + aufs_bindex_t bsrc, bend; + + LKTRTrace("%.*s, bdst %d, len %lld, flags 0x%x\n", + AuDLNPair(dentry), bdst, len, flags); + inode = dentry->d_inode; + AuDebugOn(!S_ISDIR(inode->i_mode) && au_dbstart(dentry) < bdst); + + bend = au_dbend(dentry); + for (bsrc = bdst + 1; bsrc <= bend; bsrc++) + if (au_h_dptr(dentry, bsrc)) + break; + AuDebugOn(!au_h_dptr(dentry, bsrc)); + + err = au_lkup_neg(dentry, bdst); + if (!err) { + err = au_cpup_single(dentry, bdst, bsrc, len, flags, NULL, + vargs); + if (!err) + return 0; /* success */ + + /* revert */ + au_set_h_dptr(dentry, bdst, NULL); + au_set_dbstart(dentry, bsrc); + } + + AuTraceErr(err); + return err; +} + +struct au_cpup_simple_args { + int *errp; + struct dentry *dentry; + aufs_bindex_t bdst; + loff_t len; + unsigned int flags; + struct vfsub_args *vargs; +}; + +static void au_call_cpup_simple(void *args) +{ + struct au_cpup_simple_args *a = args; + *a->errp = au_cpup_simple(a->dentry, a->bdst, a->len, a->flags, + a->vargs); +} + +int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len, + unsigned int flags) +{ + int err, wkq_err; + unsigned char do_sio, dlgt; + struct dentry *parent; + struct inode *h_dir, *dir; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + LKTRTrace("%.*s, b%d, len %lld, flags 0x%x\n", + AuDLNPair(dentry), bdst, len, flags); + + parent = dget_parent(dentry); + dir = parent->d_inode; + h_dir = au_h_iptr(dir, bdst); + dlgt = !!au_test_dlgt(au_mntflags(dir->i_sb)); + do_sio = !!au_test_h_perm_sio(h_dir, MAY_EXEC | MAY_WRITE, dlgt); + if (!do_sio) { + /* + * testing CAP_MKNOD is for generic fs, + * but CAP_FSETID is for xfs only, currently. + */ + umode_t mode = dentry->d_inode->i_mode; + do_sio = (((mode & (S_IFCHR | S_IFBLK)) + && !capable(CAP_MKNOD)) + || ((mode & (S_ISUID | S_ISGID)) + && !capable(CAP_FSETID))); + } + vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0); + if (!do_sio) + err = au_cpup_simple(dentry, bdst, len, flags, &vargs); + else { + struct au_cpup_simple_args args = { + .errp = &err, + .dentry = dentry, + .bdst = bdst, + .len = len, + .flags = flags, + .vargs = &vargs + }; + vfsub_fclr(vargs.flags, DLGT); + wkq_err = au_wkq_wait(au_call_cpup_simple, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } + + dput(parent); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int au_do_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, + struct dentry *wh_dentry, struct file *file, + loff_t len, struct vfsub_args *vargs) +{ + int err; + struct au_dinfo *dinfo; + aufs_bindex_t bstart; + struct dentry *h_d_bdst, *h_d_bstart; + + AuTraceEnter(); + + dinfo = au_di(dentry); + bstart = dinfo->di_bstart; + h_d_bdst = dinfo->di_hdentry[0 + bdst].hd_dentry; + dinfo->di_bstart = bdst; + dinfo->di_hdentry[0 + bdst].hd_dentry = wh_dentry; + h_d_bstart = dinfo->di_hdentry[0 + bstart].hd_dentry; + if (file) + dinfo->di_hdentry[0 + bstart].hd_dentry + = au_h_fptr(file, au_fbstart(file))->f_dentry; + err = au_cpup_single(dentry, bdst, bstart, len, !AuCpup_DTIME, + /*h_parent*/NULL, vargs); + if (!err && file) { + err = au_reopen_nondir(file); + dinfo->di_hdentry[0 + bstart].hd_dentry = h_d_bstart; + } + dinfo->di_hdentry[0 + bdst].hd_dentry = h_d_bdst; + dinfo->di_bstart = bstart; + + AuTraceErr(err); + return err; +} + +/* + * copyup the deleted file for writing. + */ +static int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len, + struct file *file) +{ + int err; + unsigned char dlgt; + struct dentry *parent, *h_parent, *wh_dentry; + struct super_block *sb; + unsigned int mnt_flags; + struct au_dtime dt; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct au_hinode *hgdir, *hdir; + struct au_ndx ndx = { + .nd = NULL, + .flags = 0, + /* .br = NULL */ + }; + + LKTRTrace("%.*s, bdst %d, len %llu\n", AuDLNPair(dentry), bdst, len); + AuDebugOn(S_ISDIR(dentry->d_inode->i_mode) + || (file && !(file->f_mode & FMODE_WRITE))); + DiMustWriteLock(dentry); + + parent = dget_parent(dentry); + IiMustAnyLock(parent->d_inode); + h_parent = au_h_dptr(parent, bdst); + AuDebugOn(!h_parent); + + sb = parent->d_sb; + mnt_flags = au_mntflags(sb); + dlgt = 0; + ndx.nfsmnt = au_nfsmnt(sb, bdst); + if (au_test_dlgt(mnt_flags)) { + dlgt = 1; + au_fset_ndx(ndx.flags, DLGT); + } + wh_dentry = au_whtmp_lkup(h_parent, &dentry->d_name, &ndx); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out; + + hdir = NULL; + hgdir = NULL; + if (au_opt_test(mnt_flags, UDBA_INOTIFY)) { + hdir = au_hi(parent->d_inode, bdst); + if (!IS_ROOT(parent)) { + struct dentry *gparent; + gparent = dget_parent(parent); + hgdir = au_hi(gparent->d_inode, bdst); + dput(gparent); + } + } + au_dtime_store(&dt, parent, h_parent, hdir, hgdir); + vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0); + err = au_do_cpup_wh(dentry, bdst, wh_dentry, file, len, &vargs); + if (unlikely(err)) + goto out_wh; + + AuDebugOn(!d_unhashed(dentry)); + /* dget first to force sillyrename on nfs */ + dget(wh_dentry); + vfsub_args_reinit(&vargs); + vfsub_ign_hinode(&vargs, IN_DELETE, hdir); + err = vfsub_unlink(h_parent->d_inode, wh_dentry, &vargs); + if (unlikely(err)) { + AuIOErr("failed remove copied-up tmp file %.*s(%d)\n", + AuDLNPair(wh_dentry), err); + err = -EIO; + } + au_dtime_revert(&dt); + au_set_hi_wh(dentry->d_inode, bdst, wh_dentry); + + out_wh: + dput(wh_dentry); + out: + dput(parent); + AuTraceErr(err); + return err; +} + +struct au_cpup_wh_args { + int *errp; + struct dentry *dentry; + aufs_bindex_t bdst; + loff_t len; + struct file *file; +}; + +static void au_call_cpup_wh(void *args) +{ + struct au_cpup_wh_args *a = args; + *a->errp = au_cpup_wh(a->dentry, a->bdst, a->len, a->file); +} + +int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len, + struct file *file) +{ + int err, wkq_err; + struct dentry *parent, *h_tmp, *h_parent, *h_dentry; + struct inode *dir, *h_dir, *h_tmpdir, *h_inode; + struct au_wbr *wbr; + + AuTraceEnter(); + parent = dget_parent(dentry); + dir = parent->d_inode; + IiMustAnyLock(dir); + + h_tmp = NULL; + h_parent = NULL; + h_dir = au_igrab(au_h_iptr(dir, bdst)); + h_tmpdir = h_dir; + if (!h_dir->i_nlink) { + DiMustWriteLock(parent); + wbr = au_sbr(dentry->d_sb, bdst)->br_wbr; + AuDebugOn(!wbr); + h_tmp = wbr->wbr_tmp; + + h_parent = dget(au_h_dptr(parent, bdst)); + au_set_h_dptr(parent, bdst, NULL); + au_set_h_dptr(parent, bdst, dget(h_tmp)); + h_tmpdir = h_tmp->d_inode; + au_set_h_iptr(dir, bdst, NULL, 0); + au_set_h_iptr(dir, bdst, au_igrab(h_tmpdir), /*flags*/0); + + /* this temporary unlock is safe */ + if (file) + h_dentry = au_h_fptr(file, au_fbstart(file))->f_dentry; + else + h_dentry = au_h_dptr(dentry, au_dbstart(dentry)); + h_inode = h_dentry->d_inode; + IMustLock(h_inode); + vfsub_i_unlock(h_inode); + vfsub_i_lock_nested(h_tmpdir, AuLsc_I_PARENT3); + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + } + + if (!au_test_h_perm_sio + (h_tmpdir, MAY_EXEC | MAY_WRITE, + au_test_dlgt(au_mntflags(dentry->d_sb)))) + err = au_cpup_wh(dentry, bdst, len, file); + else { + struct au_cpup_wh_args args = { + .errp = &err, + .dentry = dentry, + .bdst = bdst, + .len = len, + .file = file + }; + wkq_err = au_wkq_wait(au_call_cpup_wh, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } + + /* todo: is this restore safe? */ + if (h_tmp) { + vfsub_i_unlock(h_tmpdir); + au_set_h_iptr(dir, bdst, NULL, 0); + au_set_h_iptr(dir, bdst, au_igrab(h_dir), /*flags*/0); + au_set_h_dptr(parent, bdst, NULL); + au_set_h_dptr(parent, bdst, h_parent); + } + iput(h_dir); + dput(parent); + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* + * generic routine for both of copy-up and copy-down. + * Although I've tried building a path by dcsub, I gave up this approach. + * Since the ancestor directory may be moved/renamed during copy. + */ +/* cf. revalidate function in file.c */ +int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, + int (*cp)(struct dentry *dentry, aufs_bindex_t bdst, + struct dentry *h_parent, void *arg), + void *arg) +{ + int err; + unsigned char pin_flags; + struct au_pin pin; + struct super_block *sb; + struct dentry *d, *parent, *h_parent, *real_parent; + + LKTRTrace("%.*s, b%d, parent i%lu\n", + AuDLNPair(dentry), bdst, (unsigned long)parent_ino(dentry)); + sb = dentry->d_sb; + AuDebugOn(au_test_ro(sb, bdst, NULL)); + err = 0; + parent = dget_parent(dentry); + IiMustWriteLock(parent->d_inode); + if (IS_ROOT(parent)) + goto out; + + pin_flags = AuPin_MNT_WRITE; + if (au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) + au_fset_pin(pin_flags, DO_GPARENT); + au_pin_init(&pin, dentry, bdst, AuLsc_DI_PARENT3, AuLsc_I_PARENT2, + pin_flags); + + /* do not use au_dpage */ + real_parent = parent; + while (1) { + dput(parent); + parent = dget_parent(dentry); + h_parent = au_h_dptr(parent, bdst); + if (h_parent) + goto out; /* success */ + + /* find top dir which is needed to cpup */ + do { + d = parent; + dput(parent); + parent = dget_parent(d); + di_read_lock_parent3(parent, !AuLock_IR); + h_parent = au_h_dptr(parent, bdst); + di_read_unlock(parent, !AuLock_IR); + } while (!h_parent); + + if (d != real_parent) + di_write_lock_child3(d); + + /* somebody else might create while we were sleeping */ + if (!au_h_dptr(d, bdst) || !au_h_dptr(d, bdst)->d_inode) { + if (au_h_dptr(d, bdst)) + au_update_dbstart(d); + + au_pin_do_set_dentry(pin.pin + AuPin_PARENT, d); + err = au_do_pin(pin.pin + AuPin_PARENT, + au_pin_gp(&pin)); + if (!err) { + err = cp(d, bdst, h_parent, arg); + au_unpin(&pin); + } + } + + if (d != real_parent) + di_write_unlock(d); + if (unlikely(err)) + break; + } + + out: + dput(parent); + AuTraceErr(err); + return err; +} + +static int au_cpup_dir(struct dentry *dentry, aufs_bindex_t bdst, + struct dentry *h_parent, void *arg) +{ + int err; + + err = au_sio_cpup_simple(dentry, bdst, -1, AuCpup_DTIME); + + AuTraceErr(err); + return err; +} + +int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst) +{ + int err; + + err = au_cp_dirs(dentry, bdst, au_cpup_dir, NULL); + + AuTraceErr(err); + return err; +} + +int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst) +{ + int err; + struct dentry *parent; + struct inode *dir; + + parent = dget_parent(dentry); + dir = parent->d_inode; + LKTRTrace("%.*s, b%d, parent i%lu\n", + AuDLNPair(dentry), bdst, dir->i_ino); + DiMustReadLock(parent); + IiMustReadLock(dir); + + err = 0; + if (au_h_iptr(dir, bdst)) + goto out; + + di_read_unlock(parent, AuLock_IR); + di_write_lock_parent(parent); + /* someone else might change our inode while we were sleeping */ + if (!au_h_iptr(dir, bdst)) + err = au_cpup_dirs(dentry, bdst); + di_downgrade_lock(parent, AuLock_IR); + + out: + dput(parent); + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/cpup.h b/fs/aufs/cpup.h new file mode 100644 index 0000000..2ae810d --- /dev/null +++ b/fs/aufs/cpup.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * copy-up/down functions + * + * $Id: cpup.h,v 1.36 2009/01/26 06:23:51 sfjro Exp $ + */ + +#ifndef __AUFS_CPUP_H__ +#define __AUFS_CPUP_H__ + +#ifdef __KERNEL__ + +#include +#include +#include + +static inline +void au_cpup_attr_blksize(struct inode *inode, struct inode *h_inode) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) + inode->i_blksize = h_inode->i_blksize; +#endif +} + +void au_cpup_attr_timesizes(struct inode *inode); +void au_cpup_attr_nlink(struct inode *inode, int force); +void au_cpup_attr_changeable(struct inode *inode); +void au_cpup_igen(struct inode *inode, struct inode *h_inode); +void au_cpup_attr_all(struct inode *inode, int force); + +/* ---------------------------------------------------------------------- */ + +/* cpup flags */ +#define AuCpup_DTIME 1 /* do dtime_store/revert */ +#define AuCpup_KEEPLINO (1 << 1) /* do not clear the lower xino, + for link(2) */ +#define au_ftest_cpup(flags, name) ((flags) & AuCpup_##name) +#define au_fset_cpup(flags, name) { (flags) |= AuCpup_##name; } +#define au_fclr_cpup(flags, name) { (flags) &= ~AuCpup_##name; } + +int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst, + aufs_bindex_t bsrc, loff_t len, unsigned int flags, + struct dentry *dst_parent); +int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len, + unsigned int flags); +int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len, + struct file *file); + +int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, + int (*cp)(struct dentry *dentry, aufs_bindex_t bdst, + struct dentry *h_parent, void *arg), + void *arg); +int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst); +int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst); + +/* ---------------------------------------------------------------------- */ + +/* keep timestamps when copyup */ +struct au_dtime { + struct dentry *dt_dentry, *dt_h_dentry; + struct au_hinode *dt_hinode, *dt_hdir; + struct timespec dt_atime, dt_mtime; +}; +void au_dtime_store(struct au_dtime *dt, struct dentry *dentry, + struct dentry *h_dentry, struct au_hinode *hinode, + struct au_hinode *hdir); +void au_dtime_revert(struct au_dtime *dt); + +/* ---------------------------------------------------------------------- */ + +static inline void au_cpup_attr_flags(struct inode *inode, + struct inode *h_inode) +{ + inode->i_flags |= h_inode->i_flags & ~(S_DEAD | S_PRIVATE); +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_CPUP_H__ */ diff --git a/fs/aufs/dcsub.c b/fs/aufs/dcsub.c new file mode 100644 index 0000000..184eced --- /dev/null +++ b/fs/aufs/dcsub.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sub-routines for dentry cache + * + * $Id: dcsub.c,v 1.16 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +static void au_dpage_free(struct au_dpage *dpage) +{ + int i; + + AuTraceEnter(); + AuDebugOn(!dpage); + + for (i = 0; i < dpage->ndentry; i++) + dput(dpage->dentries[i]); + free_page((unsigned long)dpage->dentries); +} + +int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp) +{ + int err; + void *p; + + AuTraceEnter(); + + err = -ENOMEM; + dpages->dpages = kmalloc(sizeof(*dpages->dpages), gfp); + if (unlikely(!dpages->dpages)) + goto out; + p = (void *)__get_free_page(gfp); + if (unlikely(!p)) + goto out_dpages; + dpages->dpages[0].ndentry = 0; + dpages->dpages[0].dentries = p; + dpages->ndpage = 1; + return 0; /* success */ + + out_dpages: + kfree(dpages->dpages); + out: + AuTraceErr(err); + return err; +} + +void au_dpages_free(struct au_dcsub_pages *dpages) +{ + int i; + + AuTraceEnter(); + + for (i = 0; i < dpages->ndpage; i++) + au_dpage_free(dpages->dpages + i); + kfree(dpages->dpages); +} + +static int au_dpages_append(struct au_dcsub_pages *dpages, + struct dentry *dentry, gfp_t gfp) +{ + int err, sz; + struct au_dpage *dpage; + void *p; + + /* AuTraceEnter(); */ + + dpage = dpages->dpages + dpages->ndpage - 1; + AuDebugOn(!dpage); + sz = PAGE_SIZE / sizeof(dentry); + if (unlikely(dpage->ndentry >= sz)) { + LKTRLabel(new dpage); + err = -ENOMEM; + sz = dpages->ndpage * sizeof(*dpages->dpages); + p = au_kzrealloc(dpages->dpages, sz, + sz + sizeof(*dpages->dpages), gfp); + if (unlikely(!p)) + goto out; + dpages->dpages = p; + dpage = dpages->dpages + dpages->ndpage; + p = (void *)__get_free_page(gfp); + if (unlikely(!p)) + goto out; + dpage->ndentry = 0; + dpage->dentries = p; + dpages->ndpage++; + } + + dpage->dentries[dpage->ndentry++] = dget(dentry); + return 0; /* success */ + + out: + /* AuTraceErr(err); */ + return err; +} + +int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root, + au_dpages_test test, void *arg) +{ + int err; + struct dentry *this_parent = root; + struct list_head *next; + struct super_block *sb = root->d_sb; + + AuTraceEnter(); + + err = 0; + spin_lock(&dcache_lock); + repeat: + next = this_parent->d_subdirs.next; + resume: + if (this_parent->d_sb == sb + && !IS_ROOT(this_parent) + && atomic_read(&this_parent->d_count) + && this_parent->d_inode + && (!test || test(this_parent, arg))) { + err = au_dpages_append(dpages, this_parent, GFP_ATOMIC); + if (unlikely(err)) + goto out; + } + + while (next != &this_parent->d_subdirs) { + struct list_head *tmp = next; + struct dentry *dentry = list_entry(tmp, struct dentry, D_CHILD); + next = tmp->next; + if (/*d_unhashed(dentry) || */!dentry->d_inode) + continue; + if (!list_empty(&dentry->d_subdirs)) { + this_parent = dentry; + goto repeat; + } + if (dentry->d_sb == sb + && atomic_read(&dentry->d_count) + && (!test || test(dentry, arg))) { + err = au_dpages_append(dpages, dentry, GFP_ATOMIC); + if (unlikely(err)) + goto out; + } + } + + if (this_parent != root) { + next = this_parent->D_CHILD.next; + this_parent = this_parent->d_parent; /* dcache_lock is locked */ + goto resume; + } + out: + spin_unlock(&dcache_lock); +#if 0 /* debug */ + if (!err) { + int i, j; + j = 0; + for (i = 0; i < dpages->ndpage; i++) { + if ((dpages->dpages + i)->ndentry) + AuDbg("%d: %d\n", + i, (dpages->dpages + i)->ndentry); + j += (dpages->dpages + i)->ndentry; + } + if (j) + AuDbg("ndpage %d, %d\n", dpages->ndpage, j); + } +#endif + AuTraceErr(err); + return err; +} + +int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, + int do_include, au_dpages_test test, void *arg) +{ + int err; + + AuTraceEnter(); + + err = 0; + spin_lock(&dcache_lock); + if (do_include && (!test || test(dentry, arg))) { + err = au_dpages_append(dpages, dentry, GFP_ATOMIC); + if (unlikely(err)) + goto out; + } + while (!IS_ROOT(dentry)) { + dentry = dentry->d_parent; /* dcache_lock is locked */ + if (!test || test(dentry, arg)) { + err = au_dpages_append(dpages, dentry, GFP_ATOMIC); + if (unlikely(err)) + break; + } + } + + out: + spin_unlock(&dcache_lock); + + AuTraceErr(err); + return err; +} + +struct dentry *au_test_subdir(struct dentry *d1, struct dentry *d2) +{ + struct dentry *trap, **dentries; + int err, i, j; + struct au_dcsub_pages dpages; + struct au_dpage *dpage; + + LKTRTrace("%.*s, %.*s\n", AuDLNPair(d1), AuDLNPair(d2)); + + trap = ERR_PTR(-ENOMEM); + err = au_dpages_init(&dpages, GFP_NOFS); + if (unlikely(err)) + goto out; + err = au_dcsub_pages_rev(&dpages, d1, /*do_include*/1, NULL, NULL); + if (unlikely(err)) + goto out_dpages; + + trap = d1; + for (i = 0; !err && i < dpages.ndpage; i++) { + dpage = dpages.dpages + i; + dentries = dpage->dentries; + for (j = 0; !err && j < dpage->ndentry; j++) { + struct dentry *d; + d = dentries[j]; + err = (d == d2); + if (!err) + trap = d; + } + } + if (!err) + trap = NULL; + + out_dpages: + au_dpages_free(&dpages); + out: + AuTraceErrPtr(trap); + return trap; +} diff --git a/fs/aufs/dcsub.h b/fs/aufs/dcsub.h new file mode 100644 index 0000000..c437986 --- /dev/null +++ b/fs/aufs/dcsub.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sub-routines for dentry cache + * + * $Id: dcsub.h,v 1.9 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_DCSUB_H__ +#define __AUFS_DCSUB_H__ + +#ifdef __KERNEL__ + +#include + +struct au_dpage { + int ndentry; + struct dentry **dentries; +}; + +struct au_dcsub_pages { + int ndpage; + struct au_dpage *dpages; +}; + +/* ---------------------------------------------------------------------- */ + +int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp); +void au_dpages_free(struct au_dcsub_pages *dpages); +typedef int (*au_dpages_test)(struct dentry *dentry, void *arg); +int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root, + au_dpages_test test, void *arg); +int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, + int do_include, au_dpages_test test, void *arg); +struct dentry *au_test_subdir(struct dentry *d1, struct dentry *d2); + +#endif /* __KERNEL__ */ +#endif /* __AUFS_DCSUB_H__ */ diff --git a/fs/aufs/debug.c b/fs/aufs/debug.c new file mode 100644 index 0000000..2591694 --- /dev/null +++ b/fs/aufs/debug.c @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * debug print functions + * + * $Id: debug.c,v 1.59 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include "aufs.h" + +atomic_t au_cond = ATOMIC_INIT(0); + +char *au_plevel = KERN_DEBUG; +#define dpri(fmt, arg...) do { \ + if (LktrCond) \ + printk("%s" fmt, au_plevel, ##arg); \ +} while (0) + +/* ---------------------------------------------------------------------- */ + +void au_dpri_whlist(struct au_nhash *whlist) +{ + int i; + struct hlist_head *head; + struct au_vdir_wh *tpos; + struct hlist_node *pos; + + for (i = 0; i < AuSize_NHASH; i++) { + head = whlist->heads + i; + hlist_for_each_entry(tpos, pos, head, wh_hash) + dpri("b%d, %.*s, %d\n", + tpos->wh_bindex, + tpos->wh_str.len, tpos->wh_str.name, + tpos->wh_str.len); + } +} + +void au_dpri_vdir(struct au_vdir *vdir) +{ + int i; + union au_vdir_deblk_p p; + unsigned char *o; + + if (!vdir || IS_ERR(vdir)) { + dpri("err %ld\n", PTR_ERR(vdir)); + return; + } + + dpri("nblk %d, deblk %p, last{%d, %p}, ver %lu\n", + vdir->vd_nblk, vdir->vd_deblk, + vdir->vd_last.i, vdir->vd_last.p.p, vdir->vd_version); + for (i = 0; i < vdir->vd_nblk; i++) { + p.deblk = vdir->vd_deblk[i]; + o = p.p; + dpri("[%d]: %p\n", i, o); +#if 0 // verbose + int j; + for (j = 0; j < 8; j++) { + dpri("%p(+%d) {%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x}\n", + p.p, p.p - o, + p.p[0], p.p[1], p.p[2], p.p[3], + p.p[4], p.p[5], p.p[6], p.p[7], + p.p[8], p.p[9], p.p[10], p.p[11], + p.p[12], p.p[13], p.p[14], p.p[15]); + p.p += 16; + } +#endif + } +} + +static int do_pri_inode(aufs_bindex_t bindex, struct inode *inode, + struct dentry *wh) +{ + char *n = NULL; + int l = 0, ntfy = 0; + + if (!inode || IS_ERR(inode)) { + dpri("i%d: err %ld\n", bindex, PTR_ERR(inode)); + return -1; + } + + /* the type of i_blocks depends upon CONFIG_LSF */ + BUILD_BUG_ON(sizeof(inode->i_blocks) != sizeof(unsigned long) + && sizeof(inode->i_blocks) != sizeof(u64)); + if (wh) { + n = (void *)wh->d_name.name; + l = wh->d_name.len; + } + + ntfy = au_test_inotify(inode); + dpri("i%d: i%lu, %s, cnt %d, nl %u, 0%o, ntfy %d, sz %llu, blk %llu," + " ct %lld, np %lu, st 0x%lx, f 0x%x, g %x%s%.*s\n", + bindex, + inode->i_ino, inode->i_sb ? au_sbtype(inode->i_sb) : "??", + atomic_read(&inode->i_count), inode->i_nlink, inode->i_mode, + ntfy, + i_size_read(inode), (unsigned long long)inode->i_blocks, + (long long)timespec_to_ns(&inode->i_ctime) & 0x0ffff, + inode->i_mapping ? inode->i_mapping->nrpages : 0, + inode->i_state, inode->i_flags, inode->i_generation, + l ? ", wh " : "", l, n); + return 0; +} + +void au_dpri_inode(struct inode *inode) +{ + struct au_iinfo *iinfo; + aufs_bindex_t bindex; + int err; + + err = do_pri_inode(-1, inode, NULL); + if (err || !au_test_aufs(inode->i_sb)) + return; + + iinfo = au_ii(inode); + if (!iinfo) + return; + dpri("i-1: bstart %d, bend %d, gen %d\n", + iinfo->ii_bstart, iinfo->ii_bend, au_iigen(inode)); + if (iinfo->ii_bstart < 0) + return; + for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; bindex++) + do_pri_inode(bindex, iinfo->ii_hinode[0 + bindex].hi_inode, + iinfo->ii_hinode[0 + bindex].hi_whdentry); +} + +static int do_pri_dentry(aufs_bindex_t bindex, struct dentry *dentry, + struct list_head *intent) +{ + struct dentry *wh = NULL; + + if (!dentry || IS_ERR(dentry)) { + dpri("d%d: err %ld\n", bindex, PTR_ERR(dentry)); + return -1; + } + /* do not call dget_parent() here */ + dpri("d%d: %.*s?/%.*s, %s, cnt %d, flags 0x%x, intent %d\n", + bindex, + AuDLNPair(dentry->d_parent), AuDLNPair(dentry), + dentry->d_sb ? au_sbtype(dentry->d_sb) : "??", + atomic_read(&dentry->d_count), dentry->d_flags, !!intent); + if (bindex >= 0 && dentry->d_inode && au_test_aufs(dentry->d_sb)) { + struct au_iinfo *iinfo = au_ii(dentry->d_inode); + if (iinfo) + wh = iinfo->ii_hinode[0 + bindex].hi_whdentry; + } + do_pri_inode(bindex, dentry->d_inode, wh); + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) \ + && defined(CONFIG_AUFS_BR_NFS) +static struct list_head *au_dbg_h_intent(struct au_dinfo *dinfo, + aufs_bindex_t bindex) +{ + return dinfo->di_hdentry[0 + bindex].hd_intent_list; +} +#else +static struct list_head *au_dbg_h_intent(struct au_dinfo *dinfo, + aufs_bindex_t bindex) +{ + return NULL; +} +#endif + +void au_dpri_dentry(struct dentry *dentry) +{ + struct au_dinfo *dinfo; + aufs_bindex_t bindex; + int err; + + err = do_pri_dentry(-1, dentry, NULL); + if (err || !au_test_aufs(dentry->d_sb)) + return; + + dinfo = au_di(dentry); + if (!dinfo) + return; + dpri("d-1: bstart %d, bend %d, bwh %d, bdiropq %d, gen %d\n", + dinfo->di_bstart, dinfo->di_bend, + dinfo->di_bwh, dinfo->di_bdiropq, au_digen(dentry)); + if (dinfo->di_bstart < 0) + return; + for (bindex = dinfo->di_bstart; bindex <= dinfo->di_bend; bindex++) + do_pri_dentry(bindex, dinfo->di_hdentry[0 + bindex].hd_dentry, + au_dbg_h_intent(dinfo, bindex)); +} + +static int do_pri_file(aufs_bindex_t bindex, struct file *file) +{ + char a[32]; + + if (!file || IS_ERR(file)) { + dpri("f%d: err %ld\n", bindex, PTR_ERR(file)); + return -1; + } + a[0] = 0; + if (bindex < 0 + && file->f_dentry + && au_test_aufs(file->f_dentry->d_sb) + && au_fi(file)) + snprintf(a, sizeof(a), ", mmapped %d", au_test_mmapped(file)); + dpri("f%d: mode 0x%x, flags 0%o, cnt %ld, pos %llu%s\n", + bindex, file->f_mode, file->f_flags, (long)file_count(file), + file->f_pos, a); + if (file->f_dentry) + do_pri_dentry(bindex, file->f_dentry, NULL); + return 0; +} + +void au_dpri_file(struct file *file) +{ + struct au_finfo *finfo; + aufs_bindex_t bindex; + int err; + + err = do_pri_file(-1, file); + if (err || !file->f_dentry || !au_test_aufs(file->f_dentry->d_sb)) + return; + + finfo = au_fi(file); + if (!finfo) + return; + if (finfo->fi_bstart < 0) + return; + for (bindex = finfo->fi_bstart; bindex <= finfo->fi_bend; bindex++) { + struct au_hfile *hf; + hf = finfo->fi_hfile + bindex; + do_pri_file(bindex, hf ? hf->hf_file : NULL); + } +} + +static int do_pri_br(aufs_bindex_t bindex, struct au_branch *br) +{ + struct vfsmount *mnt; + struct super_block *sb; + + if (!br || IS_ERR(br) + || !(mnt = br->br_mnt) || IS_ERR(mnt) + || !(sb = mnt->mnt_sb) || IS_ERR(sb)) { + dpri("s%d: err %ld\n", bindex, PTR_ERR(br)); + return -1; + } + + dpri("s%d: {perm 0x%x, cnt %d, wbr %p}, " + "%s, dev 0x%02x%02x, flags 0x%lx, cnt(BIAS) %d, active %d, " + "xino %d\n", + bindex, br->br_perm, au_br_count(br), br->br_wbr, + au_sbtype(sb), MAJOR(sb->s_dev), MINOR(sb->s_dev), + sb->s_flags, sb->s_count - S_BIAS, + atomic_read(&sb->s_active), !!br->br_xino.xi_file); + return 0; +} + +void au_dpri_sb(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + aufs_bindex_t bindex; + int err; + /* to reuduce stack size */ + struct { + struct vfsmount mnt; + struct au_branch fake; + } *a; + + /* this function can be called from magic sysrq */ + a = kzalloc(sizeof(*a), GFP_ATOMIC); + if (unlikely(!a)) { + dpri("no memory\n"); + return; + } + + a->mnt.mnt_sb = sb; + a->fake.br_perm = 0; + a->fake.br_mnt = &a->mnt; + a->fake.br_xino.xi_file = NULL; + atomic_set(&a->fake.br_count, 0); + smp_mb(); /* atomic_set */ + err = do_pri_br(-1, &a->fake); + kfree(a); + dpri("dev 0x%x\n", sb->s_dev); + if (err || !au_test_aufs(sb)) + return; + + sbinfo = au_sbi(sb); + if (!sbinfo) + return; + dpri("nw %d, gen %u\n", + atomic_read(&sbinfo->si_nowait.nw_len), sbinfo->si_generation); + for (bindex = 0; bindex <= sbinfo->si_bend; bindex++) + do_pri_br(bindex, sbinfo->si_branch[0 + bindex]); +} + +/* ---------------------------------------------------------------------- */ + +void au_dbg_sleep(int sec) +{ + static DECLARE_WAIT_QUEUE_HEAD(wq); + wait_event_timeout(wq, 0, sec * HZ); +} + +void au_dbg_sleep_jiffy(int jiffy) +{ + static DECLARE_WAIT_QUEUE_HEAD(wq); + wait_event_timeout(wq, 0, jiffy); +} + +void au_dbg_iattr(struct iattr *ia) +{ +#define AuBit(name) if (ia->ia_valid & ATTR_ ## name) \ + dpri(#name "\n") + AuBit(MODE); + AuBit(UID); + AuBit(GID); + AuBit(SIZE); + AuBit(ATIME); + AuBit(MTIME); + AuBit(CTIME); + AuBit(ATIME_SET); + AuBit(MTIME_SET); + AuBit(FORCE); + AuBit(ATTR_FLAG); + AuBit(KILL_SUID); + AuBit(KILL_SGID); + AuBit(FILE); + AuBit(KILL_PRIV); + AuBit(OPEN); +#undef AuBit + dpri("ia_file %p\n", ia->ia_file); +} + +/* ---------------------------------------------------------------------- */ + +void au_debug_sbinfo_init(struct au_sbinfo *sbinfo) +{ +#ifdef ForceInotify + au_opt_set_udba(sbinfo->si_mntflags, UDBA_INOTIFY); +#endif +#ifdef ForceDlgt + au_opt_set(sbinfo->si_mntflags, DLGT); +#endif +#ifdef ForceNoPlink + au_opt_clr(sbinfo->si_mntflags, PLINK); +#endif +#ifdef ForceNoXino + au_opt_clr(sbinfo->si_mntflags, XINO); +#endif +#ifdef ForceNoRefrof + au_opt_clr(sbinfo->si_mntflags, REFROF); +#endif +#ifdef ForceShwh + au_opt_set(sbinfo->si_mntflags, SHWH); +#endif + +#ifdef CONFIG_AUFS_DEBUG_LOCK + { + int i; + for (i = 0; i < AuDbgLock_Last; i++) + au_spl_init(sbinfo->si_dbg_lock + i); + } +#endif +} + +int __init au_debug_init(void) +{ + aufs_bindex_t bindex; + struct au_vdir_destr destr; + + bindex = -1; + AuDebugOn(bindex >= 0); + + destr.len = -1; + AuDebugOn(destr.len < NAME_MAX); + +#ifdef CONFIG_4KSTACKS + AuWarn("CONFIG_4KSTACKS is defined.\n"); +#endif + +#ifdef ForceBrs + sysaufs_brs = 1; +#endif + +#if 0 /* verbose debug */ + { + union { + struct au_branch *br; + struct au_dinfo *di; + struct au_finfo *fi; + struct au_iinfo *ii; + struct au_hinode *hi; + struct au_sbinfo *si; + struct au_vdir_destr *destr; + struct au_vdir_de *de; + struct au_vdir_wh *wh; + struct au_vdir *vd; + } u; + + pr_info("br{" + "xino %d, " + "id %d, perm %d, mnt %d, count %d, " + "wbr %d, " + "xup %d, xrun %d, " + "gen %d, " + "sa %d} %d\n", + offsetof(typeof(*u.br), br_xino), + offsetof(typeof(*u.br), br_id), + offsetof(typeof(*u.br), br_perm), + offsetof(typeof(*u.br), br_mnt), + offsetof(typeof(*u.br), br_count), + offsetof(typeof(*u.br), wbr), + offsetof(typeof(*u.br), br_xino_upper), + offsetof(typeof(*u.br), br_xino_running), + offsetof(typeof(*u.br), br_generation), + offsetof(typeof(*u.br), br_sabr), + sizeof(*u.br)); + pr_info("di{gen %d, rwsem %d, bstart %d, bend %d, bwh %d, " + "bdiropq %d, hdentry %d} %d\n", + offsetof(typeof(*u.di), di_generation), + offsetof(typeof(*u.di), di_rwsem), + offsetof(typeof(*u.di), di_bstart), + offsetof(typeof(*u.di), di_bend), + offsetof(typeof(*u.di), di_bwh), + offsetof(typeof(*u.di), di_bdiropq), + offsetof(typeof(*u.di), di_hdentry), + sizeof(*u.di)); + pr_info("fi{gen %d, rwsem %d, hfile %d, bstart %d, bend %d, " + "h_vm_ops %d, vdir_cach %d} %d\n", + offsetof(typeof(*u.fi), fi_generation), + offsetof(typeof(*u.fi), fi_rwsem), + offsetof(typeof(*u.fi), fi_hfile), + offsetof(typeof(*u.fi), fi_bstart), + offsetof(typeof(*u.fi), fi_bend), + offsetof(typeof(*u.fi), fi_h_vm_ops), + offsetof(typeof(*u.fi), fi_vdir_cache), + sizeof(*u.fi)); + pr_info("ii{gen %d, hsb %d, " + "rwsem %d, bstart %d, bend %d, hinode %d, vdir %d} " + "%d\n", + offsetof(typeof(*u.ii), ii_generation), + offsetof(typeof(*u.ii), ii_hsb1), + offsetof(typeof(*u.ii), ii_rwsem), + offsetof(typeof(*u.ii), ii_bstart), + offsetof(typeof(*u.ii), ii_bend), + offsetof(typeof(*u.ii), ii_hinode), + offsetof(typeof(*u.ii), ii_vdir), + sizeof(*u.ii)); + pr_info("hi{inode %d, id %d, notify %d, wh %d} %d\n", + offsetof(typeof(*u.hi), hi_inode), + offsetof(typeof(*u.hi), hi_id), + offsetof(typeof(*u.hi), hi_notify), + offsetof(typeof(*u.hi), hi_whdentry), + sizeof(*u.hi)); + pr_info("si{nwt %d, rwsem %d, gen %d, stat %d, " + "bend %d, last id %d, br %d, " + "cpup %d, creat %d, ops %d, ops %d, " + "rr %d, mfs %d, " + "mntflags %d, " + "xread %d, xwrite %d, xib %d, xmtx %d, buf %d, " + "xlast %d, xnext %d, " + "rdcache %d, " + "dirwh %d, " + "pl %d, " + "mnt %d, " + "sys %d, " + /* "lvma_l %d, lvma %d" */ + "} %d\n", + offsetof(typeof(*u.si), si_nowait), + offsetof(typeof(*u.si), si_rwsem), + offsetof(typeof(*u.si), si_generation), + offsetof(typeof(*u.si), au_si_status), + offsetof(typeof(*u.si), si_bend), + offsetof(typeof(*u.si), si_last_br_id), + offsetof(typeof(*u.si), si_branch), + offsetof(typeof(*u.si), si_wbr_copyup), + offsetof(typeof(*u.si), si_wbr_create), + offsetof(typeof(*u.si), si_wbr_copyup_ops), + offsetof(typeof(*u.si), si_wbr_create_ops), + offsetof(typeof(*u.si), si_wbr_rr_next), + offsetof(typeof(*u.si), si_wbr_mfs), + offsetof(typeof(*u.si), si_mntflags), + offsetof(typeof(*u.si), si_xread), + offsetof(typeof(*u.si), si_xwrite), + offsetof(typeof(*u.si), si_xib), + offsetof(typeof(*u.si), si_xib_mtx), + offsetof(typeof(*u.si), si_xib_buf), + offsetof(typeof(*u.si), si_xib_last_pindex), + offsetof(typeof(*u.si), si_xib_next_bit), + offsetof(typeof(*u.si), si_rdcache), + offsetof(typeof(*u.si), si_dirwh), + offsetof(typeof(*u.si), si_plink), + offsetof(typeof(*u.si), si_mnt), + offsetof(typeof(*u.si), si_sa), + /*offsetof(typeof(*u.si), si_lvma_lock), + offsetof(typeof(*u.si), si_lvma),*/ + sizeof(*u.si)); + pr_info("destr{len %d, name %d} %d\n", + offsetof(typeof(*u.destr), len), + offsetof(typeof(*u.destr), name), + sizeof(*u.destr)); + pr_info("de{ino %d, type %d, str %d} %d\n", + offsetof(typeof(*u.de), de_ino), + offsetof(typeof(*u.de), de_type), + offsetof(typeof(*u.de), de_str), + sizeof(*u.de)); + pr_info("wh{hash %d, bindex %d, str %d} %d\n", + offsetof(typeof(*u.wh), wh_hash), + offsetof(typeof(*u.wh), wh_bindex), + offsetof(typeof(*u.wh), wh_str), + sizeof(*u.wh)); + pr_info("vd{deblk %d, nblk %d, last %d, ver %d, jiffy %d} %d\n", + offsetof(typeof(*u.vd), vd_deblk), + offsetof(typeof(*u.vd), vd_nblk), + offsetof(typeof(*u.vd), vd_last), + offsetof(typeof(*u.vd), vd_version), + offsetof(typeof(*u.vd), vd_jiffy), + sizeof(*u.vd)); + } +#endif + + return 0; +} diff --git a/fs/aufs/debug.h b/fs/aufs/debug.h new file mode 100644 index 0000000..a7bba3c --- /dev/null +++ b/fs/aufs/debug.h @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * debug print functions + * + * $Id: debug.h,v 1.54 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_DEBUG_H__ +#define __AUFS_DEBUG_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include + +/* to debug easier, do not make it an inlined function */ +#define MtxMustLock(mtx) AuDebugOn(!mutex_is_locked(mtx)) + +#ifdef CONFIG_AUFS_DEBUG +/* sparse warns about pointer */ +#define AuDebugOn(a) BUG_ON(!!(a)) +extern atomic_t au_cond; +#define au_debug_on() atomic_inc_return(&au_cond) +#define au_debug_off() atomic_dec_return(&au_cond) +static inline int au_debug_test(void) +{ + return atomic_read(&au_cond); +} +#else +#define AuDebugOn(a) do {} while (0) +#define au_debug_on() do {} while (0) +#define au_debug_off() do {} while (0) +static inline int au_debug_test(void) +{ + return 0; +} +#endif /* CONFIG_AUFS_DEBUG */ + +/* ---------------------------------------------------------------------- */ + +/* debug print */ +#if defined(CONFIG_LKTR) || defined(CONFIG_LKTR_MODULE) +#include +#ifdef CONFIG_AUFS_DEBUG +#undef LktrCond +#define LktrCond unlikely(au_debug_test() || (lktr_cond && lktr_cond())) +#endif +#else +#define LktrCond au_debug_test() +#define LKTRDumpVma(pre, vma, suf) do {} while (0) +#define LKTRDumpStack() do {} while (0) +#define LKTRTrace(fmt, args...) do { \ + if (LktrCond) \ + AuDbg(fmt, ##args); \ +} while (0) +#define LKTRLabel(label) LKTRTrace("%s\n", #label) +#endif /* CONFIG_LKTR */ + +#define AuTraceErr(e) do { \ + if (unlikely((e) < 0)) \ + LKTRTrace("err %d\n", (int)(e)); \ +} while (0) + +#define AuTraceErrPtr(p) do { \ + if (IS_ERR(p)) \ + LKTRTrace("err %ld\n", PTR_ERR(p)); \ +} while (0) + +#define AuTraceEnter() LKTRLabel(enter) + +/* dirty macros for debug print, use with "%.*s" and caution */ +#define AuLNPair(qstr) (qstr)->len, (qstr)->name +#define AuDLNPair(d) AuLNPair(&(d)->d_name) + +/* ---------------------------------------------------------------------- */ + +#define AuDpri(lvl, fmt, arg...) \ + printk(lvl AUFS_NAME " %s:%d:%s[%d]: " fmt, \ + __func__, __LINE__, current->comm, current->pid, ##arg) +#define AuDbg(fmt, arg...) AuDpri(KERN_DEBUG, fmt, ##arg) +#define AuInfo(fmt, arg...) AuDpri(KERN_INFO, fmt, ##arg) +#define AuWarn(fmt, arg...) AuDpri(KERN_WARNING, fmt, ##arg) +#define AuErr(fmt, arg...) AuDpri(KERN_ERR, fmt, ##arg) +#define AuIOErr(fmt, arg...) AuErr("I/O Error, " fmt, ##arg) +#define AuIOErrWhck(fmt, arg...) AuErr("I/O Error, try whck. " fmt, ##arg) +#define AuWarn1(fmt, arg...) do { \ + static unsigned char _c; \ + if (!_c++) \ + AuWarn(fmt, ##arg); \ +} while (0) + +#define AuErr1(fmt, arg...) do { \ + static unsigned char _c; \ + if (!_c++) \ + AuErr(fmt, ##arg); \ +} while (0) + +#define AuIOErr1(fmt, arg...) do { \ + static unsigned char _c; \ + if (!_c++) \ + AuIOErr(fmt, ##arg); \ +} while (0) + +#define AuUnsupportMsg "This operation is not supported." \ + " Please report this application to aufs-users ML." +#define AuUnsupport(fmt, args...) do { \ + AuErr(AuUnsupportMsg "\n" fmt, ##args); \ + dump_stack(); \ +} while (0) + +/* ---------------------------------------------------------------------- */ + +struct au_sbinfo; +#ifdef CONFIG_AUFS_DEBUG +extern char *au_plevel; +struct au_nhash; +void au_dpri_whlist(struct au_nhash *whlist); +struct au_vdir; +void au_dpri_vdir(struct au_vdir *vdir); +void au_dpri_inode(struct inode *inode); +void au_dpri_dentry(struct dentry *dentry); +void au_dpri_file(struct file *filp); +void au_dpri_sb(struct super_block *sb); +void au_dbg_sleep(int sec); +void au_dbg_sleep_jiffy(int jiffy); + +#ifndef ATTR_OPEN +#define ATTR_OPEN 0 +#endif +#ifndef ATTR_KILL_PRIV +#define ATTR_KILL_PRIV 0 +#endif +void au_dbg_iattr(struct iattr *ia); +int __init au_debug_init(void); +void au_debug_sbinfo_init(struct au_sbinfo *sbinfo); +#define AuDbgWhlist(w) do { \ + LKTRTrace(#w "\n"); \ + au_dpri_whlist(w); \ +} while (0) + +#define AuDbgVdir(v) do { \ + LKTRTrace(#v "\n"); \ + au_dpri_vdir(v); \ +} while (0) + +#define AuDbgInode(i) do { \ + LKTRTrace(#i "\n"); \ + au_dpri_inode(i); \ +} while (0) + +#define AuDbgDentry(d) do { \ + LKTRTrace(#d "\n"); \ + au_dpri_dentry(d); \ +} while (0) + +#define AuDbgFile(f) do { \ + LKTRTrace(#f "\n"); \ + au_dpri_file(f); \ +} while (0) + +#define AuDbgSb(sb) do { \ + LKTRTrace(#sb "\n"); \ + au_dpri_sb(sb); \ +} while (0) + +#define AuDbgSleep(sec) do { \ + AuDbg("sleep %d sec\n", sec); \ + au_dbg_sleep(sec); \ +} while (0) + +#define AuDbgSleepJiffy(jiffy) do { \ + AuDbg("sleep %d jiffies\n", jiffy); \ + au_dbg_sleep_jiffy(jiffy); \ +} while (0) + +#define AuDbgIAttr(ia) do { \ + AuDbg("ia_valid 0x%x\n", (ia)->ia_valid); \ + au_dbg_iattr(ia); \ +} while (0) +#else +static inline int au_debug_init(void) +{ + return 0; +} +static inline void au_debug_sbinfo_init(struct au_sbinfo *sbinfo) +{ + /* empty */ +} +#define AuDbgWhlist(w) do {} while (0) +#define AuDbgVdir(v) do {} while (0) +#define AuDbgInode(i) do {} while (0) +#define AuDbgDentry(d) do {} while (0) +#define AuDbgFile(f) do {} while (0) +#define AuDbgSb(sb) do {} while (0) +#define AuDbgSleep(sec) do {} while (0) +#define AuDbgSleepJiffy(jiffy) do {} while (0) +#define AuDbgIAttr(ia) do {} while (0) +#endif /* CONFIG_AUFS_DEBUG */ + +#ifdef DbgUdbaRace +#define AuDbgSleep_UdbaRace() AuDbgSleep(DbgUdbaRace) +#else +#define AuDbgSleep_UdbaRace() do {} while (0) +#endif + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_MAGIC_SYSRQ +int __init au_sysrq_init(void); +void au_sysrq_fin(void); + +#if defined(CONFIG_HW_CONSOLE) \ + && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#define au_dbg_blocked() do { \ + WARN_ON(1); \ + handle_sysrq('w', vc_cons[fg_console].d->vc_tty); \ +} while (0) +#else +#define au_dbg_blocked() do {} while (0) +#endif + +#else +static inline int au_sysrq_init(void) +{ + return 0; +} +#define au_sysrq_fin() do {} while (0) +#define au_dbg_blocked() do {} while (0) +#endif /* CONFIG_AUFS_MAGIC_SYSRQ */ + +enum { + AuDbgLock_SI_LOCKING, + AuDbgLock_SI_LOCKED, + AuDbgLock_DI_LOCKING, + AuDbgLock_DI_LOCKED, + AuDbgLock_II_LOCKING, + AuDbgLock_II_LOCKED, + AuDbgLock_Last +}; +#ifdef CONFIG_AUFS_DEBUG_LOCK +void au_dbg_locking_si_reg(struct super_block *sb, int flags); +void au_dbg_locking_si_unreg(struct super_block *sb, int flags); +void au_dbg_locked_si_reg(struct super_block *sb, int flags); +void au_dbg_locked_si_unreg(struct super_block *sb, int flags); +void au_dbg_locking_di_reg(struct dentry *d, int flags, unsigned int lsc); +void au_dbg_locking_di_unreg(struct dentry *d, int flags); +void au_dbg_locked_di_reg(struct dentry *d, int flags, unsigned int lsc); +void au_dbg_locked_di_unreg(struct dentry *d, int flags); +void au_dbg_locking_ii_reg(struct inode *i, int flags, unsigned int lsc); +void au_dbg_locking_ii_unreg(struct inode *i, int flags); +void au_dbg_locked_ii_reg(struct inode *i, int flags, unsigned int lsc); +void au_dbg_locked_ii_unreg(struct inode *i, int flags); +#else +static inline +void au_dbg_locking_si_reg(struct super_block *sb, int flags) +{ + /* empty */ +} +static inline +void au_dbg_locking_si_unreg(struct super_block *sb, int flags) +{ + /* empty */ +} +static inline +void au_dbg_locked_si_reg(struct super_block *sb, int flags) +{ + /* empty */ +} +static inline +void au_dbg_locked_si_unreg(struct super_block *sb, int flags) +{ + /* empty */ +} + +static inline +void au_dbg_locking_di_reg(struct dentry *d, int flags, unsigned int lsc) +{ + /* empty */ +} +static inline +void au_dbg_locking_di_unreg(struct dentry *d, int flags) +{ + /* empty */ +} +static inline +void au_dbg_locked_di_reg(struct dentry *d, int flags, unsigned int lsc) +{ + /* empty */ +} +static inline +void au_dbg_locked_di_unreg(struct dentry *d, int flags) +{ + /* empty */ +} +static inline +void au_dbg_locking_ii_reg(struct inode *i, int flags, unsigned int lsc) +{ + /* empty */ +} +static inline +void au_dbg_locking_ii_unreg(struct inode *i, int flags) +{ + /* empty */ +} +static inline +void au_dbg_locked_ii_reg(struct inode *i, int flags, unsigned int lsc) +{ + /* empty */ +} +static inline +void au_dbg_locked_ii_unreg(struct inode *i, int flags) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_DEBUG_LOCK */ + +#endif /* __KERNEL__ */ +#endif /* __AUFS_DEBUG_H__ */ diff --git a/fs/aufs/dentry.c b/fs/aufs/dentry.c new file mode 100644 index 0000000..e1d992e --- /dev/null +++ b/fs/aufs/dentry.c @@ -0,0 +1,960 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * lookup and dentry operations + * + * $Id: dentry.c,v 1.86 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +/* + * au_lkup_one() is a generic abstract entry function which calls + * lookup_one_len() or __lookup_hash() finally. it is some condisions that makes + * lookup complicated, which are nfs branch, open-intent and dlgt mode. + */ + +#if defined(CONFIG_AUFS_BR_NFS) || defined(CONFIG_AUFS_DLGT) +/* cf. lookup_one_len() in linux/fs/namei.c */ +struct dentry *au_lkup_one(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx) +{ + struct dentry *dentry; + + LKTRTrace("%.*s/%.*s, ndx{%d, 0x%x}\n", + AuDLNPair(parent), len, name, !!ndx->nfsmnt, ndx->flags); + + ndx->nd_file = NULL; + if (!ndx->nfsmnt) + dentry = au_lkup_one_dlgt(name, parent, len, ndx->flags); + else + dentry = au_lkup_hash(name, parent, len, ndx); + + AuTraceErrPtr(dentry); + return dentry; +} +#endif /* CONFIG_AUFS_BR_NFS || CONFIG_AUFS_DLGT */ + +struct au_lkup_one_args { + struct dentry **errp; + const char *name; + struct dentry *parent; + int len; + struct au_ndx *ndx; +}; + +static void au_call_lkup_one(void *args) +{ + struct au_lkup_one_args *a = args; + *a->errp = au_lkup_one(a->name, a->parent, a->len, a->ndx); +} + +#define AuLkup_ALLOW_NEG 1 +#define AuLkup_DLGT (1 << 1) +#define AuLkup_DIRPERM1 (1 << 2) +#define au_ftest_lkup(flags, name) ((flags) & AuLkup_##name) +#define au_fset_lkup(flags, name) { (flags) |= AuLkup_##name; } +#define au_fclr_lkup(flags, name) { (flags) &= ~AuLkup_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuLkup_DLGT +#define AuLkup_DLGT 0 +#undef AuLkup_DIRPERM1 +#define AuLkup_DIRPERM1 0 +#endif + +struct au_do_lookup_args { + unsigned int flags; + mode_t type; + struct nameidata *nd; +}; + +/* + * returns positive/negative dentry, NULL or an error. + * NULL means whiteout-ed or not-found. + */ +static /* noinline_for_stack */ +struct dentry *au_do_lookup(struct dentry *h_parent, struct dentry *dentry, + aufs_bindex_t bindex, struct qstr *wh_name, + struct au_do_lookup_args *args) +{ + struct dentry *h_dentry; + int err, wh_found, opq; + unsigned char wh_able; + struct inode *h_dir, *h_inode, *inode; + struct qstr *name; + struct super_block *sb; + unsigned int nd_flags; + struct au_ndx ndx = { + .flags = 0, + .nd = args->nd + }; + const int allow_neg = au_ftest_lkup(args->flags, ALLOW_NEG); + + LKTRTrace("%.*s/%.*s, b%d, {flags 0x%x, type 0%o, nd %d}\n", + AuDLNPair(h_parent), AuDLNPair(dentry), bindex, + args->flags, args->type, !!args->nd); + if (args->nd) + LKTRTrace("nd{0x%x}\n", args->nd->flags); + AuDebugOn(IS_ROOT(dentry)); + h_dir = h_parent->d_inode; + + nd_flags = 0; + wh_found = 0; + sb = dentry->d_sb; + ndx.nfsmnt = au_nfsmnt(sb, bindex); + if (au_ftest_lkup(args->flags, DLGT)) + au_fset_ndx(ndx.flags, DLGT); + if (au_ftest_lkup(args->flags, DIRPERM1)) + au_fset_ndx(ndx.flags, DIRPERM1); + LKTRTrace("nfsmnt %p\n", ndx.nfsmnt); + ndx.br = au_sbr(sb, bindex); + wh_able = !!au_br_whable(ndx.br->br_perm); + name = &dentry->d_name; + if (wh_able) + wh_found = au_test_robr_wh(name, h_parent, wh_name, + /*try_sio*/0, &ndx); + h_dentry = ERR_PTR(wh_found); + if (!wh_found) + goto real_lookup; + if (unlikely(wh_found < 0)) + goto out; + + /* We found a whiteout */ + /* au_set_dbend(dentry, bindex); */ + au_set_dbwh(dentry, bindex); + if (!allow_neg) + return NULL; /* success */ + if (ndx.nd + && au_test_nfs(h_parent->d_sb) + && (ndx.nd->flags & LOOKUP_CREATE)) { + nd_flags = ndx.nd->flags; + ndx.nd->flags &= ~(LOOKUP_OPEN | LOOKUP_CREATE); + } + + real_lookup: + /* do not superio. */ + h_dentry = au_lkup_one(name->name, h_parent, name->len, &ndx); + if (IS_ERR(h_dentry)) + goto out; + AuDebugOn(d_unhashed(h_dentry)); + h_inode = h_dentry->d_inode; + if (!h_inode) { + if (!allow_neg) + goto out_neg; + } else if (wh_found + || (args->type && args->type != (h_inode->i_mode & S_IFMT))) + goto out_neg; + + if (au_dbend(dentry) <= bindex) + au_set_dbend(dentry, bindex); + if (au_dbstart(dentry) < 0 || bindex < au_dbstart(dentry)) + au_set_dbstart(dentry, bindex); + au_set_h_dptr(dentry, bindex, h_dentry); + + err = au_br_nfs_h_intent(ndx.nd_file, dentry, bindex, args->nd); + if (unlikely(err)) { + h_dentry = ERR_PTR(err); + goto out; + } + + inode = dentry->d_inode; + if (!h_inode || !S_ISDIR(h_inode->i_mode) || !wh_able + || (inode && !S_ISDIR(inode->i_mode))) + goto out; /* success */ + + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + opq = au_diropq_test(h_dentry, &ndx); + vfsub_i_unlock(h_inode); + if (opq > 0) + au_set_dbdiropq(dentry, bindex); + else if (unlikely(opq < 0)) { + au_set_h_dptr(dentry, bindex, NULL); + h_dentry = ERR_PTR(opq); + } + goto out; + + out_neg: + dput(h_dentry); + h_dentry = NULL; + out: + if (nd_flags) + ndx.nd->flags |= (nd_flags & (LOOKUP_OPEN | LOOKUP_CREATE)); + AuTraceErrPtr(h_dentry); + return h_dentry; +} + +/* + * returns the number of hidden positive dentries, + * otherwise an error. + * can be called at unlinking with @type is zero. + */ +int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type, + struct nameidata *nd) +{ + int npositive, err; + unsigned int mnt_flags; + aufs_bindex_t bindex, btail, bdiropq; + unsigned char isdir; + struct qstr whname; + struct au_do_lookup_args args = { + .type = type, + .nd = nd + }; + const struct qstr *name = &dentry->d_name; + struct dentry *parent; + struct super_block *sb; + struct inode *inode; + + LKTRTrace("%.*s, b%d, type 0%o\n", AuLNPair(name), bstart, type); + AuDebugOn(bstart < 0 || IS_ROOT(dentry)); + + /* dir may not be locked */ + parent = dget_parent(dentry); + + err = au_test_robr_shwh(dentry->d_sb, name); + if (unlikely(err)) + goto out; + + err = au_wh_name_alloc(name->name, name->len, &whname); + if (unlikely(err)) + goto out; + + sb = dentry->d_sb; + mnt_flags = au_mntflags(sb); + inode = dentry->d_inode; + isdir = !!(inode && S_ISDIR(inode->i_mode)); + args.flags = 0; + if (au_test_dlgt(mnt_flags)) + au_fset_lkup(args.flags, DLGT); + if (au_test_dirperm1(mnt_flags)) + au_fset_lkup(args.flags, DIRPERM1); + if (!type) + au_fset_lkup(args.flags, ALLOW_NEG); + npositive = 0; + btail = au_dbtaildir(parent); + for (bindex = bstart; bindex <= btail; bindex++) { + struct dentry *h_parent, *h_dentry; + struct inode *h_inode, *h_dir; + + h_dentry = au_h_dptr(dentry, bindex); + if (h_dentry) { + if (h_dentry->d_inode) + npositive++; + if (type != S_IFDIR) + break; + continue; + } + h_parent = au_h_dptr(parent, bindex); + if (!h_parent) + continue; + h_dir = h_parent->d_inode; + if (!h_dir || !S_ISDIR(h_dir->i_mode)) + continue; + + vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT); + h_dentry = au_do_lookup(h_parent, dentry, bindex, &whname, + &args); + vfsub_i_unlock(h_dir); + err = PTR_ERR(h_dentry); + if (IS_ERR(h_dentry)) + goto out_wh; + au_fclr_lkup(args.flags, ALLOW_NEG); + + if (au_dbwh(dentry) >= 0) + break; + if (!h_dentry) + continue; + h_inode = h_dentry->d_inode; + if (!h_inode) + continue; + npositive++; + if (!args.type) + args.type = h_inode->i_mode & S_IFMT; + if (args.type != S_IFDIR) + break; + else if (isdir) { + /* the type of lowers may be different */ + bdiropq = au_dbdiropq(dentry); + if (bdiropq >= 0 && bdiropq <= bindex) + break; + } + } + + if (npositive) { + LKTRLabel(positive); + au_update_dbstart(dentry); + } + err = npositive; + if (!au_opt_test(mnt_flags, UDBA_NONE) && au_dbstart(dentry) < 0) + /* both of real entry and whiteout found */ + err = -EIO; + + out_wh: + au_wh_name_free(&whname); + out: + dput(parent); + AuTraceErr(err); + return err; +} + +struct dentry *au_sio_lkup_one(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx) +{ + struct dentry *dentry; + int wkq_err; + + LKTRTrace("%.*s/%.*s\n", AuDLNPair(parent), len, name); + + if (!au_test_h_perm_sio(parent->d_inode, MAY_EXEC, + au_ftest_ndx(ndx->flags, DLGT))) + dentry = au_lkup_one(name, parent, len, ndx); + else { + /* todo: ugly? */ + unsigned int flags = ndx->flags; + struct au_lkup_one_args args = { + .errp = &dentry, + .name = name, + .parent = parent, + .len = len, + .ndx = ndx + }; + + au_fclr_ndx(ndx->flags, DLGT); + au_fclr_ndx(ndx->flags, DIRPERM1); + wkq_err = au_wkq_wait(au_call_lkup_one, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + dentry = ERR_PTR(wkq_err); + ndx->flags = flags; + } + + AuTraceErrPtr(dentry); + return dentry; +} + +/* + * lookup @dentry on @bindex which should be negative. + */ +int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex) +{ + int err; + struct dentry *parent, *h_parent, *h_dentry; + struct inode *h_dir; + struct au_ndx ndx = { + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + struct super_block *sb; + unsigned int mnt_flags; + + LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bindex); + /* dir may not be locked */ + parent = dget_parent(dentry); + AuDebugOn(!parent || !parent->d_inode + || !S_ISDIR(parent->d_inode->i_mode)); + h_parent = au_h_dptr(parent, bindex); + AuDebugOn(!h_parent); + h_dir = h_parent->d_inode; + AuDebugOn(!h_dir || !S_ISDIR(h_dir->i_mode)); + + sb = dentry->d_sb; + mnt_flags = au_mntflags(sb); + ndx.nfsmnt = au_nfsmnt(sb, bindex); + if (au_test_dlgt(mnt_flags)) + au_fset_ndx(ndx.flags, DLGT); + if (au_test_dirperm1(mnt_flags)) + au_fset_ndx(ndx.flags, DIRPERM1); + h_dentry = au_sio_lkup_one(dentry->d_name.name, h_parent, + dentry->d_name.len, &ndx); + err = PTR_ERR(h_dentry); + if (IS_ERR(h_dentry)) + goto out; + if (unlikely(h_dentry->d_inode)) { + err = -EIO; + AuIOErr("b%d %.*s should be negative.\n", + bindex, AuDLNPair(h_dentry)); + dput(h_dentry); + goto out; + } + + if (bindex < au_dbstart(dentry)) + au_set_dbstart(dentry, bindex); + if (au_dbend(dentry) < bindex) + au_set_dbend(dentry, bindex); + au_set_h_dptr(dentry, bindex, h_dentry); + err = 0; + + out: + dput(parent); + AuTraceErr(err); + return err; +} + +/* + * returns the number of found hidden positive dentries, + * otherwise an error. + */ +int au_refresh_hdentry(struct dentry *dentry, mode_t type) +{ + int npositive, new_sz; + struct au_dinfo *dinfo; + struct super_block *sb; + struct dentry *parent; + aufs_bindex_t bindex, parent_bend, parent_bstart, bwh, bdiropq, bend; + struct au_hdentry *p; + au_gen_t sgen; + + LKTRTrace("%.*s, type 0%o\n", AuDLNPair(dentry), type); + DiMustWriteLock(dentry); + sb = dentry->d_sb; + AuDebugOn(IS_ROOT(dentry)); + sgen = au_sigen(sb); + parent = dget_parent(dentry); + AuDebugOn(au_digen(parent) != sgen + || au_iigen(parent->d_inode) != sgen); + + npositive = -ENOMEM; + new_sz = sizeof(*dinfo->di_hdentry) * (au_sbend(sb) + 1); + dinfo = au_di(dentry); + p = au_kzrealloc(dinfo->di_hdentry, sizeof(*p) * (dinfo->di_bend + 1), + new_sz, GFP_NOFS); + if (unlikely(!p)) + goto out; + dinfo->di_hdentry = p; + + bend = dinfo->di_bend; + bwh = dinfo->di_bwh; + bdiropq = dinfo->di_bdiropq; + p += dinfo->di_bstart; + for (bindex = dinfo->di_bstart; bindex <= bend; bindex++, p++) { + struct dentry *hd, *hdp; + struct au_hdentry tmp, *q; + aufs_bindex_t new_bindex; + + hd = p->hd_dentry; + if (!hd) + continue; + hdp = dget_parent(hd); + if (hdp == au_h_dptr(parent, bindex)) { + dput(hdp); + continue; + } + + new_bindex = au_find_dbindex(parent, hdp); + dput(hdp); + AuDebugOn(new_bindex == bindex); + if (dinfo->di_bwh == bindex) + bwh = new_bindex; + if (dinfo->di_bdiropq == bindex) + bdiropq = new_bindex; + /* todo: test more? */ + if (new_bindex < 0) { + au_hdput(p, /*do_free*/0); + p->hd_dentry = NULL; + continue; + } + /* swap two hidden dentries, and loop again */ + q = dinfo->di_hdentry + new_bindex; + tmp = *q; + *q = *p; + *p = tmp; + if (tmp.hd_dentry) { + bindex--; + p--; + } + } + + /* todo: test more? */ + dinfo->di_bwh = -1; + if (bwh >= 0 && bwh <= au_sbend(sb) && au_sbr_whable(sb, bwh)) + dinfo->di_bwh = bwh; + dinfo->di_bdiropq = -1; + if (bdiropq >= 0 + && bdiropq <= au_sbend(sb) + && au_sbr_whable(sb, bdiropq)) + dinfo->di_bdiropq = bdiropq; + parent_bend = au_dbend(parent); + p = dinfo->di_hdentry; + for (bindex = 0; bindex <= parent_bend; bindex++, p++) + if (p->hd_dentry) { + dinfo->di_bstart = bindex; + break; + } + p = dinfo->di_hdentry + parent_bend; + for (bindex = parent_bend; bindex >= 0; bindex--, p--) + if (p->hd_dentry) { + dinfo->di_bend = bindex; + break; + } + + npositive = 0; + parent_bstart = au_dbstart(parent); + if (type != S_IFDIR && dinfo->di_bstart == parent_bstart) + goto out_dgen; /* success */ + + npositive = au_lkup_dentry(dentry, parent_bstart, type, /*nd*/NULL); + if (npositive < 0) + goto out; + if (dinfo->di_bwh >= 0 && dinfo->di_bwh <= dinfo->di_bstart) + d_drop(dentry); + + out_dgen: + au_update_digen(dentry); + out: + dput(parent); + AuTraceErr(npositive); + return npositive; +} + +static int au_lock_nd(struct dentry *dentry, struct nameidata *nd) +{ + int locked = 0; + if (nd && dentry != nd->dentry) { + di_read_lock_parent(nd->dentry, 0); + locked = 1; + } + return locked; +} + +static void au_unlock_nd(int locked, struct nameidata *nd) +{ + if (locked) + di_read_unlock(nd->dentry, 0); +} + +/* #define TestingFuse */ +static noinline_for_stack +int au_do_h_d_reval(struct dentry *dentry, aufs_bindex_t bindex, + struct nameidata *nd, struct dentry *h_dentry) +{ + int err, valid, e; + int (*reval)(struct dentry *, struct nameidata *); + struct super_block *sb; + struct nameidata fake_nd, *p; + + LKTRTrace("%.*s, b%d, nd %d\n", AuDLNPair(dentry), bindex, !!nd); + + err = 0; + reval = NULL; + if (h_dentry->d_op) + reval = h_dentry->d_op->d_revalidate; + if (!reval) + goto out; + + sb = dentry->d_sb; + if (nd) { + memcpy(&fake_nd, nd, sizeof(*nd)); + err = au_fake_intent(&fake_nd, au_sbr(sb, bindex)); + if (unlikely(err)) { + err = -EINVAL; + goto out; + } + } + p = au_fake_dm(&fake_nd, nd, sb, bindex); + AuDebugOn(IS_ERR(p)); + AuDebugOn(nd && p != &fake_nd); + LKTRTrace("b%d\n", bindex); + + /* it may return tri-state */ + valid = reval(h_dentry, p); + if (unlikely(valid < 0)) + err = valid; + else if (!valid) + err = -EINVAL; + else + AuDebugOn(err); + + if (p) { + AuDebugOn(!nd); + e = au_hin_after_reval(p, dentry, bindex, nd->intent.open.file); +#ifndef TestingFuse + au_update_fuse_h_inode(p->mnt, h_dentry); /*ignore*/ +#endif + if (unlikely(e && !err)) + err = e; + } +#ifndef TestingFuse + else + au_update_fuse_h_inode(NULL, h_dentry); /*ignore*/ +#endif + au_fake_dm_release(p); + + out: + AuTraceErr(err); + return err; +} + +static noinline_for_stack +int h_d_revalidate(struct dentry *dentry, struct inode *inode, + struct nameidata *nd, int do_udba) +{ + int err; + aufs_bindex_t bindex, btail, bstart, ibs, ibe; + unsigned char plus, locked, unhashed, is_root, h_plus; + struct super_block *sb; + struct inode *first, *h_inode, *h_cached_inode; + umode_t mode, h_mode; + struct dentry *h_dentry; + struct qstr *name; + + LKTRTrace("%.*s, nd %d\n", AuDLNPair(dentry), !!nd); + AuDebugOn(inode && au_digen(dentry) != au_iigen(inode)); + + err = 0; + sb = dentry->d_sb; + plus = 0; + mode = 0; + first = NULL; + ibs = -1; + ibe = -1; + unhashed = !!d_unhashed(dentry); + is_root = !!IS_ROOT(dentry); + name = &dentry->d_name; + + /* + * Theoretically, REVAL test should be unnecessary in case of INOTIFY. + * But inotify doesn't fire some necessary events, + * IN_ATTRIB for atime/nlink/pageio + * IN_DELETE for NFS dentry + * Let's do REVAL test too. + */ + if (do_udba && inode) { + mode = (inode->i_mode & S_IFMT); + plus = (inode->i_nlink > 0); + first = au_h_iptr(inode, au_ibstart(inode)); + ibs = au_ibstart(inode); + ibe = au_ibend(inode); + } + + bstart = au_dbstart(dentry); + btail = bstart; + if (inode && S_ISDIR(inode->i_mode)) + btail = au_dbtaildir(dentry); + locked = !!au_lock_nd(dentry, nd); + for (bindex = bstart; bindex <= btail; bindex++) { + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + + LKTRTrace("b%d, %.*s\n", bindex, AuDLNPair(h_dentry)); +#ifdef TestingFuse + /* force re-lookup for fuse, in order to update attributes */ + if (au_test_fuse(h_dentry->d_sb)) + goto err; +#endif + + if (unlikely(do_udba + && !is_root + && (unhashed != !!d_unhashed(h_dentry) + || name->len != h_dentry->d_name.len + || memcmp(name->name, h_dentry->d_name.name, + name->len) + ))) { + LKTRTrace("unhash 0x%x 0x%x, %.*s %.*s\n", + unhashed, d_unhashed(h_dentry), + AuDLNPair(dentry), AuDLNPair(h_dentry)); + goto err; + } + + err = au_do_h_d_reval(dentry, bindex, nd, h_dentry); + if (unlikely(err)) + /* do not goto err, to keep the errno */ + break; + + /* todo: plink too? */ + if (!do_udba) + continue; + + /* UDBA tests */ + h_inode = h_dentry->d_inode; + if (unlikely(!!inode != !!h_inode)) + goto err; + + h_plus = plus; + h_mode = mode; + h_cached_inode = h_inode; + if (h_inode) { + h_mode = (h_inode->i_mode & S_IFMT); + h_plus = (h_inode->i_nlink > 0); + } + if (inode && ibs <= bindex && bindex <= ibe) + h_cached_inode = au_h_iptr(inode, bindex); + + LKTRTrace("{%d, 0%o, %p}, h{%d, 0%o, %p}\n", + plus, mode, h_cached_inode, + h_plus, h_mode, h_inode); + if (unlikely(plus != h_plus + || mode != h_mode + || h_cached_inode != h_inode)) + goto err; + continue; + + err: + err = -EINVAL; + break; + } + au_unlock_nd(locked, nd); + + /* + * judging by timestamps is meaningless since some filesystem uses + * CURRENT_TIME_SEC instead of CURRENT_TIME. + */ + /* + * NFS may stop IN_DELETE because of DCACHE_NFSFS_RENAMED. + */ + + AuTraceErr(err); + return err; +} + +static int simple_reval_dpath(struct dentry *dentry, au_gen_t sgen) +{ + int err; + mode_t type; + struct dentry *parent; + struct inode *inode; + + LKTRTrace("%.*s, sgen %d\n", AuDLNPair(dentry), sgen); + SiMustAnyLock(dentry->d_sb); + DiMustWriteLock(dentry); + inode = dentry->d_inode; + AuDebugOn(!inode); + + if (au_digen(dentry) == sgen && au_iigen(inode) == sgen) + return 0; + + parent = dget_parent(dentry); + di_read_lock_parent(parent, AuLock_IR); + AuDebugOn(au_digen(parent) != sgen + || au_iigen(parent->d_inode) != sgen); +#ifdef CONFIG_AUFS_DEBUG + { + int i, j; + struct au_dcsub_pages dpages; + struct au_dpage *dpage; + struct dentry **dentries; + + err = au_dpages_init(&dpages, GFP_NOFS); + AuDebugOn(err); + err = au_dcsub_pages_rev(&dpages, parent, /*do_include*/1, NULL, + NULL); + AuDebugOn(err); + for (i = dpages.ndpage - 1; !err && i >= 0; i--) { + dpage = dpages.dpages + i; + dentries = dpage->dentries; + for (j = dpage->ndentry - 1; !err && j >= 0; j--) + AuDebugOn(au_digen(dentries[j]) != sgen); + } + au_dpages_free(&dpages); + } +#endif + type = (inode->i_mode & S_IFMT); + /* returns a number of positive dentries */ + err = au_refresh_hdentry(dentry, type); + if (err >= 0) + err = au_refresh_hinode(inode, dentry); + di_read_unlock(parent, AuLock_IR); + dput(parent); + AuTraceErr(err); + return err; +} + +int au_reval_dpath(struct dentry *dentry, au_gen_t sgen) +{ + int err; + struct dentry *d, *parent; + struct inode *inode; + + LKTRTrace("%.*s, sgen %d\n", AuDLNPair(dentry), sgen); + AuDebugOn(!dentry->d_inode); + DiMustWriteLock(dentry); + + if (!au_ftest_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIRS)) + return simple_reval_dpath(dentry, sgen); + + /* slow loop, keep it simple and stupid */ + /* cf: au_cpup_dirs() */ + err = 0; + parent = NULL; + while (au_digen(dentry) != sgen || au_iigen(dentry->d_inode) != sgen) { + d = dentry; + while (1) { + dput(parent); + parent = dget_parent(d); + if (au_digen(parent) == sgen + && au_iigen(parent->d_inode) == sgen) + break; + d = parent; + } + + inode = d->d_inode; + if (d != dentry) + di_write_lock_child(d); + + /* someone might update our dentry while we were sleeping */ + if (au_digen(d) != sgen || au_iigen(d->d_inode) != sgen) { + di_read_lock_parent(parent, AuLock_IR); + /* returns a number of positive dentries */ + err = au_refresh_hdentry(d, inode->i_mode & S_IFMT); + if (err >= 0) + err = au_refresh_hinode(inode, d); + di_read_unlock(parent, AuLock_IR); + } + + if (d != dentry) + di_write_unlock(d); + dput(parent); + if (unlikely(err)) + break; + } + + AuTraceErr(err); + return err; +} + +/* + * THIS IS A BOOLEAN FUNCTION: returns 1 if valid, 0 otherwise. + * nfsd passes NULL as nameidata. + */ +static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd) +{ + int valid, err; + au_gen_t sgen; + unsigned char do_udba; + struct nameidata tmp_nd, *ndp; + struct super_block *sb; + struct inode *inode; + + LKTRTrace("dentry %.*s\n", AuDLNPair(dentry)); + if (nd && nd->dentry) + LKTRTrace("nd{%.*s, 0x%x}\n", + AuDLNPair(nd->dentry), nd->flags); + /* + * dir case: AuDebugOn(dentry->d_parent != nd->dentry); + * remove failure case:AuDebugOn(!IS_ROOT(dentry) + * && d_unhashed(dentry)); + */ + AuDebugOn(!dentry->d_fsdata); + + err = -EINVAL; + sb = dentry->d_sb; + aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW); + inode = dentry->d_inode; + sgen = au_sigen(sb); + if (au_digen(dentry) != sgen) { + AuDebugOn(IS_ROOT(dentry)); +#ifdef ForceInotify + AuDbg("UDBA or digen, %.*s\n", AuDLNPair(dentry)); +#endif + if (inode) + err = au_reval_dpath(dentry, sgen); + if (unlikely(err)) + goto out_dgrade; + AuDebugOn(au_digen(dentry) != sgen); + } + if (inode && au_iigen(inode) != sgen) { + AuDebugOn(IS_ROOT(dentry)); +#ifdef ForceInotify + AuDbg("UDBA or survived, %.*s\n", AuDLNPair(dentry)); +#endif + err = au_refresh_hinode(inode, dentry); + if (unlikely(err)) + goto out_dgrade; + AuDebugOn(au_iigen(inode) != sgen); + } + di_downgrade_lock(dentry, AuLock_IR); + +#if 0 /* todo: support it? */ + /* parent dir i_nlink is not updated in the case of setattr */ + if (S_ISDIR(inode->i_mode)) { + vfsub_i_lock(inode); + ii_write_lock(inode); + au_cpup_attr_nlink(inode); + ii_write_unlock(inode); + vfsub_i_unlock(inode); + } +#endif + + AuDebugOn(au_digen(dentry) != sgen); + AuDebugOn(inode && au_iigen(inode) != sgen); + err = -EINVAL; + do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE); + if (do_udba && inode) { + aufs_bindex_t bstart = au_ibstart(inode); + if (bstart >= 0 + && au_test_higen(inode, au_h_iptr(inode, bstart))) + goto out; + } + ndp = au_dup_nd(au_sbi(sb), &tmp_nd, nd); + err = h_d_revalidate(dentry, inode, ndp, do_udba); + if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) + /* both of real entry and whiteout found */ + err = -EIO; + goto out; + + out_dgrade: + di_downgrade_lock(dentry, AuLock_IR); + out: + au_store_fmode_exec(nd, inode); + aufs_read_unlock(dentry, AuLock_IR); + AuTraceErr(err); + valid = !err; + if (!valid) + LKTRTrace("%.*s invalid\n", AuDLNPair(dentry)); + return valid; +} + +static void aufs_d_release(struct dentry *dentry) +{ + struct au_dinfo *dinfo; + aufs_bindex_t bend, bindex; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + AuDebugOn(!d_unhashed(dentry)); + + dinfo = dentry->d_fsdata; + if (!dinfo) + return; + + /* dentry may not be revalidated */ + bindex = dinfo->di_bstart; + if (bindex >= 0) { + struct au_hdentry *p; + bend = dinfo->di_bend; + AuDebugOn(bend < bindex); + p = dinfo->di_hdentry + bindex; + while (bindex++ <= bend) { + if (p->hd_dentry) + au_hdput(p, /*do_free*/1); + p++; + } + } + kfree(dinfo->di_hdentry); + au_rwsem_destroy(&dinfo->di_rwsem); + au_cache_free_dinfo(dinfo); + au_hin_di_reinit(dentry); +} + +struct dentry_operations aufs_dop = { + .d_revalidate = aufs_d_revalidate, + .d_release = aufs_d_release, + /* never use d_delete, especially in case of nfs server */ +}; diff --git a/fs/aufs/dentry.h b/fs/aufs/dentry.h new file mode 100644 index 0000000..3a8092f --- /dev/null +++ b/fs/aufs/dentry.h @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * lookup and dentry operations + * + * $Id: dentry.h,v 1.49 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_DENTRY_H__ +#define __AUFS_DENTRY_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include +#include "misc.h" +#include "vfsub.h" + +/* nameidata open_intent */ +enum { + AuIntent_AUFS, + AuIntent_BRANCH, + AuIntent_Last +}; + +struct au_hdintent { + struct list_head hdi_list; + struct file *hdi_file[AuIntent_Last]; +}; + +struct au_hdentry { + struct dentry *hd_dentry; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) \ + && defined(CONFIG_AUFS_BR_NFS) + spinlock_t hd_lock; /* intest_list */ + struct list_head *hd_intent_list; +#endif +}; + +struct au_dinfo { + atomic_t di_generation; + + struct au_rwsem di_rwsem; + aufs_bindex_t di_bstart, di_bend, di_bwh, di_bdiropq; + struct au_hdentry *di_hdentry; +}; + +/* nameidata extension flags */ +#define AuNdx_DLGT 1 +#define AuNdx_DIRPERM1 (1 << 1) +#define au_ftest_ndx(flags, name) ((flags) & AuNdx_##name) +#define au_fset_ndx(flags, name) { (flags) |= AuNdx_##name; } +#define au_fclr_ndx(flags, name) { (flags) &= ~AuNdx_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuNdx_DLGT +#define AuNdx_DLGT 0 +#undef AuNdx_DIRPERM1 +#define AuNdx_DIRPERM1 0 +#endif + +struct au_ndx { + struct vfsmount *nfsmnt; + unsigned int flags; + struct nameidata *nd; + struct au_branch *br; + struct file *nd_file; +}; + +/* ---------------------------------------------------------------------- */ + +static inline void au_do_h_dentry_init(struct au_hdentry *hdentry) +{ + hdentry->hd_dentry = NULL; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) \ + && defined(CONFIG_AUFS_BR_NFS) +static inline void au_h_dentry_init(struct au_hdentry *hdentry) +{ + au_do_h_dentry_init(hdentry); + spin_lock_init(&hdentry->hd_lock); +} + +static inline void au_h_dentry_init_all(struct au_hdentry *hdentry, int n) +{ + while (n--) + spin_lock_init(&hdentry[n].hd_lock); +} + +/* br_nfs.c */ +struct file *au_h_intent(struct dentry *dentry, aufs_bindex_t bindex, + struct file *file); +int au_br_nfs_h_intent(struct file *nd_file, struct dentry *dentry, + aufs_bindex_t bindex, struct nameidata *nd); +void au_hintent_put(struct au_hdentry *hd, int do_free); +int au_fake_intent(struct nameidata *nd, struct au_branch *br); +int au_hin_after_reval(struct nameidata *nd, struct dentry *dentry, + aufs_bindex_t bindex, struct file *file); +struct dentry *au_lkup_hash(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx); +#else + +static inline void au_h_dentry_init(struct au_hdentry *hdentry) +{ + au_do_h_dentry_init(hdentry); +} + +static inline void au_h_dentry_init_all(struct au_hdentry *hdentry, int n) +{ + /* nothing */ +} + +static inline +struct file *au_h_intent(struct dentry *dentry, aufs_bindex_t bindex, + struct file *file) +{ + /* return ERR_PTR(-ENOSYS); */ + return NULL; +} + +static inline +int au_br_nfs_h_intent(struct file *nd_file, struct dentry *dentry, + aufs_bindex_t bindex, struct nameidata *nd) +{ + return 0; +} + +static inline void au_hintent_put(struct au_hdentry *hd, int do_free) +{ + /* empty */ +} + +static inline int au_fake_intent(struct nameidata *nd, struct au_branch *br) +{ + return 0; +} + +static inline +int au_hin_after_reval(struct nameidata *nd, struct dentry *dentry, + aufs_bindex_t bindex, struct file *file) +{ + return 0; +} + +#if defined(CONFIG_AUFS_BR_NFS) || defined(CONFIG_AUFS_DLGT) +static inline +struct dentry *au_lkup_hash(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx) +{ + /* return ERR_PTR(-ENOSYS); */ + return vfsub_lookup_one_len(name, parent, len); +} +#endif +#endif /* >= 2.6.19 && CONFIG_AUFS_BR_NFS */ + +#ifdef CONFIG_AUFS_DLGT +/* dlgt.c */ +struct dentry *au_lkup_one_dlgt(const char *name, struct dentry *parent, + int len, unsigned int flags); +#elif defined(CONFIG_AUFS_BR_NFS) +/* regardelss kernel version */ +static inline +struct dentry *au_lkup_one_dlgt(const char *name, struct dentry *parent, + int len, unsigned int flags) +{ + return vfsub_lookup_one_len(name, parent, len); +} +#endif + +/* dentry.c */ +extern struct dentry_operations aufs_dop; +#if defined(CONFIG_AUFS_BR_NFS) || defined(CONFIG_AUFS_DLGT) +struct dentry *au_lkup_one(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx); +#else +static inline +struct dentry *au_lkup_one(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx) +{ + /* todo? ndx->nd_file = NULL; */ + return vfsub_lookup_one_len(name, parent, len); +} +#endif +struct dentry *au_sio_lkup_one(const char *name, struct dentry *parent, int len, + struct au_ndx *ndx); +int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type, + struct nameidata *nd); +int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex); +int au_refresh_hdentry(struct dentry *dentry, mode_t type); +int au_reval_dpath(struct dentry *dentry, au_gen_t sgen); + +/* dinfo.c */ +int au_alloc_dinfo(struct dentry *dentry); +struct au_dinfo *au_di(struct dentry *dentry); + +void di_read_lock(struct dentry *d, int flags, unsigned int lsc); +void di_read_unlock(struct dentry *d, int flags); +void di_downgrade_lock(struct dentry *d, int flags); +void di_write_lock(struct dentry *d, unsigned int lsc); +void di_write_unlock(struct dentry *d); +void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir); +void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir); +void di_write_unlock2(struct dentry *d1, struct dentry *d2); + +struct dentry *au_h_dptr(struct dentry *dentry, aufs_bindex_t bindex); + +aufs_bindex_t au_dbtail(struct dentry *dentry); +aufs_bindex_t au_dbtaildir(struct dentry *dentry); +#if 0 /* reserved for future use */ +aufs_bindex_t au_dbtail_generic(struct dentry *dentry); +#endif + +void au_set_dbdiropq(struct dentry *dentry, aufs_bindex_t bindex); +void au_set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_dentry); + +void au_update_dbrange(struct dentry *dentry, int do_put_zero); +void au_update_dbstart(struct dentry *dentry); +void au_update_dbend(struct dentry *dentry); +int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry); + +/* ---------------------------------------------------------------------- */ + +/* todo: memory barrier? */ +static inline au_gen_t au_digen(struct dentry *d) +{ + return atomic_read(&au_di(d)->di_generation); +} + +#ifdef CONFIG_AUFS_HINOTIFY +static inline au_gen_t au_digen_dec(struct dentry *d) +{ + return atomic_dec_return(&au_di(d)->di_generation); +} + +static inline void au_hin_di_reinit(struct dentry *d) +{ + d->d_fsdata = NULL; +} +#else +static inline void au_hin_di_reinit(struct dentry *d) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_HINOTIFY */ + +/* ---------------------------------------------------------------------- */ + +/* lock subclass for dinfo */ +enum { + AuLsc_DI_CHILD, /* child first */ + AuLsc_DI_CHILD2, /* rename(2), link(2), and cpup at hinotify */ + AuLsc_DI_CHILD3, /* copyup dirs */ + AuLsc_DI_PARENT, + AuLsc_DI_PARENT2, + AuLsc_DI_PARENT3, + AuLsc_DI_PARENT4 +}; + +/* + * di_read_lock_child, di_write_lock_child, + * di_read_lock_child2, di_write_lock_child2, + * di_read_lock_child3, di_write_lock_child3, + * di_read_lock_parent, di_write_lock_parent, + * di_read_lock_parent2, di_write_lock_parent2, + * di_read_lock_parent3, di_write_lock_parent3, + * di_read_lock_parent4, di_write_lock_parent4, + */ +#define AuReadLockFunc(name, lsc) \ +static inline void di_read_lock_##name(struct dentry *d, int flags) \ +{ di_read_lock(d, flags, AuLsc_DI_##lsc); } + +#define AuWriteLockFunc(name, lsc) \ +static inline void di_write_lock_##name(struct dentry *d) \ +{ di_write_lock(d, AuLsc_DI_##lsc); } + +#define AuRWLockFuncs(name, lsc) \ + AuReadLockFunc(name, lsc) \ + AuWriteLockFunc(name, lsc) + +AuRWLockFuncs(child, CHILD); +AuRWLockFuncs(child2, CHILD2); +AuRWLockFuncs(child3, CHILD3); +AuRWLockFuncs(parent, PARENT); +AuRWLockFuncs(parent2, PARENT2); +AuRWLockFuncs(parent3, PARENT3); +AuRWLockFuncs(parent4, PARENT4); + +#undef AuReadLockFunc +#undef AuWriteLockFunc +#undef AuRWLockFuncs + +/* to debug easier, do not make them inlined functions */ +#define DiMustReadLock(d) do { \ + SiMustAnyLock((d)->d_sb); \ + AuRwMustReadLock(&au_di(d)->di_rwsem); \ +} while (0) + +#define DiMustWriteLock(d) do { \ + SiMustAnyLock((d)->d_sb); \ + AuRwMustWriteLock(&au_di(d)->di_rwsem); \ +} while (0) + +#define DiMustAnyLock(d) do { \ + SiMustAnyLock((d)->d_sb); \ + AuRwMustAnyLock(&au_di(d)->di_rwsem); \ +} while (0) + +#define DiMustNoWaiters(d) AuRwMustNoWaiters(&au_di(d)->di_rwsem) + +/* ---------------------------------------------------------------------- */ + +static inline aufs_bindex_t au_dbstart(struct dentry *dentry) +{ + DiMustAnyLock(dentry); + return au_di(dentry)->di_bstart; +} + +static inline aufs_bindex_t au_dbend(struct dentry *dentry) +{ + DiMustAnyLock(dentry); + return au_di(dentry)->di_bend; +} + +static inline aufs_bindex_t au_dbwh(struct dentry *dentry) +{ + DiMustAnyLock(dentry); + return au_di(dentry)->di_bwh; +} + +static inline aufs_bindex_t au_dbdiropq(struct dentry *dentry) +{ + DiMustAnyLock(dentry); + AuDebugOn(dentry->d_inode + && dentry->d_inode->i_mode + && !S_ISDIR(dentry->d_inode->i_mode)); + return au_di(dentry)->di_bdiropq; +} + +/* todo: hard/soft set? */ +static inline void au_set_dbstart(struct dentry *dentry, aufs_bindex_t bindex) +{ + DiMustWriteLock(dentry); + AuDebugOn(au_sbend(dentry->d_sb) < bindex); + /* */ + au_di(dentry)->di_bstart = bindex; +} + +static inline void au_set_dbend(struct dentry *dentry, aufs_bindex_t bindex) +{ + DiMustWriteLock(dentry); + AuDebugOn(au_sbend(dentry->d_sb) < bindex + || bindex < au_dbstart(dentry)); + au_di(dentry)->di_bend = bindex; +} + +static inline void au_set_dbwh(struct dentry *dentry, aufs_bindex_t bindex) +{ + DiMustWriteLock(dentry); + AuDebugOn(au_sbend(dentry->d_sb) < bindex); + /* dbwh can be outside of bstart - bend range */ + au_di(dentry)->di_bwh = bindex; +} + +static inline void au_hdput(struct au_hdentry *hd, int do_free) +{ + au_hintent_put(hd, do_free); + dput(hd->hd_dentry); +} + +static inline void au_update_digen(struct dentry *dentry) +{ + AuDebugOn(!dentry->d_sb); + atomic_set(&au_di(dentry)->di_generation, au_sigen(dentry->d_sb)); + /* smp_mb(); */ /* atomic_set */ +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_DENTRY_H__ */ diff --git a/fs/aufs/dinfo.c b/fs/aufs/dinfo.c new file mode 100644 index 0000000..909d1c5 --- /dev/null +++ b/fs/aufs/dinfo.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * dentry private data + * + * $Id: dinfo.c,v 1.47 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +int au_alloc_dinfo(struct dentry *dentry) +{ + struct au_dinfo *dinfo; + struct super_block *sb; + int nbr; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + AuDebugOn(dentry->d_fsdata); + + dinfo = au_cache_alloc_dinfo(); + if (dinfo) { + sb = dentry->d_sb; + nbr = au_sbend(sb) + 1; + if (nbr <= 0) + nbr = 1; + dinfo->di_hdentry = kcalloc(nbr, sizeof(*dinfo->di_hdentry), + GFP_NOFS); + if (dinfo->di_hdentry) { + au_h_dentry_init_all(dinfo->di_hdentry, nbr); + atomic_set(&dinfo->di_generation, au_sigen(sb)); + /* smp_mb(); */ /* atomic_set */ + au_rw_init_wlock_nested(&dinfo->di_rwsem, + AuLsc_DI_CHILD); + au_dbg_locked_di_reg(dentry, AuLock_DW, AuLsc_DI_CHILD); + dinfo->di_bstart = -1; + dinfo->di_bend = -1; + dinfo->di_bwh = -1; + dinfo->di_bdiropq = -1; + + dentry->d_fsdata = dinfo; + dentry->d_op = &aufs_dop; + return 0; /* success */ + } + au_cache_free_dinfo(dinfo); + } + AuTraceErr(-ENOMEM); + return -ENOMEM; +} + +struct au_dinfo *au_di(struct dentry *dentry) +{ + struct au_dinfo *dinfo = dentry->d_fsdata; + AuDebugOn(!dinfo + || !dinfo->di_hdentry + /* || au_sbi(dentry->d_sb)->si_bend < dinfo->di_bend */ + || dinfo->di_bend < dinfo->di_bstart + /* dbwh can be outside of this range */ + || (0 <= dinfo->di_bdiropq + && (dinfo->di_bdiropq < dinfo->di_bstart + /* || dinfo->di_bend < dinfo->di_bdiropq */)) + ); + return dinfo; +} + +/* ---------------------------------------------------------------------- */ + +static void do_ii_write_lock(struct inode *inode, unsigned int lsc) +{ + switch (lsc) { + case AuLsc_DI_CHILD: + ii_write_lock_child(inode); + break; + case AuLsc_DI_CHILD2: + ii_write_lock_child2(inode); + break; + case AuLsc_DI_CHILD3: + ii_write_lock_child3(inode); + break; + case AuLsc_DI_PARENT: + ii_write_lock_parent(inode); + break; + case AuLsc_DI_PARENT2: + ii_write_lock_parent2(inode); + break; + case AuLsc_DI_PARENT3: + ii_write_lock_parent3(inode); + break; + case AuLsc_DI_PARENT4: + ii_write_lock_parent4(inode); + break; + default: + BUG(); + } +} + +static void do_ii_read_lock(struct inode *inode, unsigned int lsc) +{ + switch (lsc) { + case AuLsc_DI_CHILD: + ii_read_lock_child(inode); + break; + case AuLsc_DI_CHILD2: + ii_read_lock_child2(inode); + break; + case AuLsc_DI_CHILD3: + ii_read_lock_child3(inode); + break; + case AuLsc_DI_PARENT: + ii_read_lock_parent(inode); + break; + case AuLsc_DI_PARENT2: + ii_read_lock_parent2(inode); + break; + case AuLsc_DI_PARENT3: + ii_read_lock_parent3(inode); + break; + case AuLsc_DI_PARENT4: + ii_read_lock_parent4(inode); + break; + default: + BUG(); + } +} + +void di_read_lock(struct dentry *d, int flags, unsigned int lsc) +{ + LKTRTrace("%.*s, %u\n", AuDLNPair(d), lsc); + SiMustAnyLock(d->d_sb); + + /* todo: always nested? */ + au_dbg_locking_di_reg(d, flags, lsc); + au_rw_read_lock_nested(&au_di(d)->di_rwsem, lsc); + au_dbg_locking_di_unreg(d, flags); + au_dbg_locked_di_reg(d, flags, lsc); + if (d->d_inode) { + if (au_ftest_lock(flags, IW)) + do_ii_write_lock(d->d_inode, lsc); + else if (au_ftest_lock(flags, IR)) + do_ii_read_lock(d->d_inode, lsc); + } +} + +void di_read_unlock(struct dentry *d, int flags) +{ + LKTRTrace("%.*s\n", AuDLNPair(d)); + SiMustAnyLock(d->d_sb); + + if (d->d_inode) { + if (au_ftest_lock(flags, IW)) + ii_write_unlock(d->d_inode); + else if (au_ftest_lock(flags, IR)) + ii_read_unlock(d->d_inode); + } + au_rw_read_unlock(&au_di(d)->di_rwsem); + au_dbg_locked_di_unreg(d, flags); +} + +void di_downgrade_lock(struct dentry *d, int flags) +{ + SiMustAnyLock(d->d_sb); + + au_rw_dgrade_lock(&au_di(d)->di_rwsem); + if (d->d_inode && au_ftest_lock(flags, IR)) + ii_downgrade_lock(d->d_inode); +} + +void di_write_lock(struct dentry *d, unsigned int lsc) +{ + LKTRTrace("%.*s, %u\n", AuDLNPair(d), lsc); + SiMustAnyLock(d->d_sb); + + /* todo: always nested? */ + au_dbg_locking_di_reg(d, AuLock_IW, lsc); + au_rw_write_lock_nested(&au_di(d)->di_rwsem, lsc); + au_dbg_locking_di_unreg(d, AuLock_IW); + au_dbg_locked_di_reg(d, AuLock_IW, lsc); + if (d->d_inode) + do_ii_write_lock(d->d_inode, lsc); +} + +void di_write_unlock(struct dentry *d) +{ + LKTRTrace("%.*s\n", AuDLNPair(d)); + SiMustAnyLock(d->d_sb); + + if (d->d_inode) + ii_write_unlock(d->d_inode); + au_rw_write_unlock(&au_di(d)->di_rwsem); + au_dbg_locked_di_unreg(d, AuLock_IW); +} + +void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir) +{ + AuTraceEnter(); + AuDebugOn(d1 == d2 + || d1->d_inode == d2->d_inode + || d1->d_sb != d2->d_sb); + + if (isdir && au_test_subdir(d1, d2)) { + di_write_lock_child(d1); + di_write_lock_child2(d2); + } else { + /* there should be no races */ + di_write_lock_child(d2); + di_write_lock_child2(d1); + } +} + +void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir) +{ + AuTraceEnter(); + AuDebugOn(d1 == d2 + || d1->d_inode == d2->d_inode + || d1->d_sb != d2->d_sb); + + if (isdir && au_test_subdir(d1, d2)) { + di_write_lock_parent(d1); + di_write_lock_parent2(d2); + } else { + /* there should be no races */ + di_write_lock_parent(d2); + di_write_lock_parent2(d1); + } +} + +void di_write_unlock2(struct dentry *d1, struct dentry *d2) +{ + di_write_unlock(d1); + if (d1->d_inode == d2->d_inode) { + au_rw_write_unlock(&au_di(d2)->di_rwsem); + au_dbg_locked_di_unreg(d2, AuLock_IW); + } else + di_write_unlock(d2); +} + +/* ---------------------------------------------------------------------- */ + +struct dentry *au_h_dptr(struct dentry *dentry, aufs_bindex_t bindex) +{ + struct dentry *d; + + DiMustAnyLock(dentry); + if (au_dbstart(dentry) < 0 || bindex < au_dbstart(dentry)) + return NULL; + AuDebugOn(bindex < 0 + /* || bindex > au_sbend(dentry->d_sb) */); + d = au_di(dentry)->di_hdentry[0 + bindex].hd_dentry; + AuDebugOn(d && (atomic_read(&d->d_count) <= 0)); + return d; +} + +aufs_bindex_t au_dbtail(struct dentry *dentry) +{ + aufs_bindex_t bend, bwh; + + bend = au_dbend(dentry); + if (0 <= bend) { + bwh = au_dbwh(dentry); + if (!bwh) + return bwh; + if (0 < bwh && bwh < bend) + return bwh - 1; + } + return bend; +} + +aufs_bindex_t au_dbtaildir(struct dentry *dentry) +{ + aufs_bindex_t bend, bopq; + + AuDebugOn(dentry->d_inode + && dentry->d_inode->i_mode + && !S_ISDIR(dentry->d_inode->i_mode)); + + bend = au_dbtail(dentry); + if (0 <= bend) { + bopq = au_dbdiropq(dentry); + AuDebugOn(bend < bopq); + if (0 <= bopq && bopq < bend) + bend = bopq; + } + return bend; +} + +#if 0 /* reserved for future use */ +aufs_bindex_t au_dbtail_generic(struct dentry *dentry) +{ + struct inode *inode; + + inode = dentry->d_inode; + if (inode && S_ISDIR(inode->i_mode)) + return au_dbtaildir(dentry); + else + return au_dbtail(dentry); +} +#endif + +/* ---------------------------------------------------------------------- */ + +void au_set_dbdiropq(struct dentry *dentry, aufs_bindex_t bindex) +{ + DiMustWriteLock(dentry); + AuDebugOn(au_sbend(dentry->d_sb) < bindex); + AuDebugOn((bindex >= 0 + && (bindex < au_dbstart(dentry) + || au_dbend(dentry) < bindex)) + || (dentry->d_inode + && dentry->d_inode->i_mode + && !S_ISDIR(dentry->d_inode->i_mode))); + au_di(dentry)->di_bdiropq = bindex; +} + +void au_set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_dentry) +{ + struct au_hdentry *hd = au_di(dentry)->di_hdentry + bindex; + DiMustWriteLock(dentry); + AuDebugOn(bindex < au_di(dentry)->di_bstart + || bindex > au_di(dentry)->di_bend + || (h_dentry && atomic_read(&h_dentry->d_count) <= 0) + || (h_dentry && hd->hd_dentry) + ); + if (hd->hd_dentry) + au_hdput(hd, /*do_free*/0); + hd->hd_dentry = h_dentry; +} + +/* ---------------------------------------------------------------------- */ + +void au_update_dbrange(struct dentry *dentry, int do_put_zero) +{ + struct au_dinfo *dinfo; + aufs_bindex_t bindex; + struct dentry *h_d; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), do_put_zero); + DiMustWriteLock(dentry); + + dinfo = au_di(dentry); + if (!dinfo || dinfo->di_bstart < 0) + return; + + if (do_put_zero) { + for (bindex = dinfo->di_bstart; bindex <= dinfo->di_bend; + bindex++) { + h_d = dinfo->di_hdentry[0 + bindex].hd_dentry; + if (h_d && !h_d->d_inode) + au_set_h_dptr(dentry, bindex, NULL); + } + } + + dinfo->di_bstart = -1; + while (++dinfo->di_bstart <= dinfo->di_bend) + if (dinfo->di_hdentry[0 + dinfo->di_bstart].hd_dentry) + break; + if (dinfo->di_bstart > dinfo->di_bend) { + dinfo->di_bstart = -1; + dinfo->di_bend = -1; + return; + } + + dinfo->di_bend++; + while (0 <= --dinfo->di_bend) + if (dinfo->di_hdentry[0 + dinfo->di_bend].hd_dentry) + break; + AuDebugOn(dinfo->di_bstart > dinfo->di_bend || dinfo->di_bend < 0); +} + +void au_update_dbstart(struct dentry *dentry) +{ + aufs_bindex_t bindex, + bstart = au_dbstart(dentry), + bend = au_dbend(dentry); + struct dentry *h_dentry; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + DiMustWriteLock(dentry); + + for (bindex = bstart; bindex <= bend; bindex++) { + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + if (h_dentry->d_inode) { + au_set_dbstart(dentry, bindex); + return; + } + au_set_h_dptr(dentry, bindex, NULL); + } +} + +void au_update_dbend(struct dentry *dentry) +{ + aufs_bindex_t bindex, + bstart = au_dbstart(dentry), + bend = au_dbend(dentry); + struct dentry *h_dentry; + + DiMustWriteLock(dentry); + for (bindex = bend; bindex <= bstart; bindex--) { + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + if (h_dentry->d_inode) { + au_set_dbend(dentry, bindex); + return; + } + au_set_h_dptr(dentry, bindex, NULL); + } +} + +int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry) +{ + aufs_bindex_t bindex, bend; + + bend = au_dbend(dentry); + for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) + if (au_h_dptr(dentry, bindex) == h_dentry) + return bindex; + return -1; +} diff --git a/fs/aufs/dir.c b/fs/aufs/dir.c new file mode 100644 index 0000000..6dbe3ff --- /dev/null +++ b/fs/aufs/dir.c @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * directory operations + * + * $Id: dir.c,v 1.69 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +static int reopen_dir(struct file *file) +{ + int err; + struct dentry *dentry, *h_dentry; + aufs_bindex_t bindex, btail, bstart; + struct file *h_file; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + AuDebugOn(!S_ISDIR(dentry->d_inode->i_mode)); + + /* open all hidden dirs */ + bstart = au_dbstart(dentry); +#if 1 /* todo: necessary? */ + for (bindex = au_fbstart(file); bindex < bstart; bindex++) + au_set_h_fptr(file, bindex, NULL); +#endif + au_set_fbstart(file, bstart); + btail = au_dbtaildir(dentry); +#if 1 /* todo: necessary? */ + for (bindex = au_fbend(file); btail < bindex; bindex--) + au_set_h_fptr(file, bindex, NULL); +#endif + au_set_fbend(file, btail); + for (bindex = bstart; bindex <= btail; bindex++) { + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + h_file = au_h_fptr(file, bindex); + if (h_file) { + AuDebugOn(h_file->f_dentry != h_dentry); + continue; + } + + h_file = au_h_open(dentry, bindex, file->f_flags, file); + err = PTR_ERR(h_file); + if (IS_ERR(h_file)) + goto out; /* close all? */ + /* cpup_file_flags(h_file, file); */ + au_set_h_fptr(file, bindex, h_file); + } + au_update_figen(file); + /* todo: necessary? */ + /* file->f_ra = h_file->f_ra; */ + err = 0; + + out: + AuTraceErr(err); + return err; +} + +static int do_open_dir(struct file *file, int flags) +{ + int err; + aufs_bindex_t bindex, btail; + struct dentry *dentry, *h_dentry; + struct file *h_file; + + dentry = file->f_dentry; + LKTRTrace("%.*s, 0x%x\n", AuDLNPair(dentry), flags); + AuDebugOn(!dentry->d_inode || !S_ISDIR(dentry->d_inode->i_mode)); + + err = 0; + au_set_fvdir_cache(file, NULL); + file->f_version = dentry->d_inode->i_version; + bindex = au_dbstart(dentry); + au_set_fbstart(file, bindex); + btail = au_dbtaildir(dentry); + au_set_fbend(file, btail); + for (; !err && bindex <= btail; bindex++) { + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + + h_file = au_h_open(dentry, bindex, flags, file); + if (IS_ERR(h_file)) { + err = PTR_ERR(h_file); + break; + } + au_set_h_fptr(file, bindex, h_file); + } + au_update_figen(file); + /* todo: necessary? */ + /* file->f_ra = h_file->f_ra; */ + if (!err) + return 0; /* success */ + + /* close all */ + for (bindex = au_fbstart(file); bindex <= btail; bindex++) + au_set_h_fptr(file, bindex, NULL); + au_set_fbstart(file, -1); + au_set_fbend(file, -1); + return err; +} + +static int aufs_open_dir(struct inode *inode, struct file *file) +{ + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(file->f_dentry)); + + return au_do_open(inode, file, do_open_dir); +} + +static int aufs_release_dir(struct inode *inode, struct file *file) +{ + struct au_vdir *vdir_cache; + struct super_block *sb; + + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(file->f_dentry)); + + sb = file->f_dentry->d_sb; + si_noflush_read_lock(sb); + fi_write_lock(file); + vdir_cache = au_fvdir_cache(file); + if (vdir_cache) + au_vdir_free(vdir_cache); + fi_write_unlock(file); + au_finfo_fin(file); + si_read_unlock(sb); + return 0; +} + +static int fsync_dir(struct dentry *dentry, int datasync) +{ + int err; + struct inode *inode; + struct super_block *sb; + aufs_bindex_t bend, bindex; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), datasync); + DiMustAnyLock(dentry); + sb = dentry->d_sb; + SiMustAnyLock(sb); + inode = dentry->d_inode; + IMustLock(inode); + IiMustAnyLock(inode); + + err = 0; + bend = au_dbend(dentry); + for (bindex = au_dbstart(dentry); !err && bindex <= bend; bindex++) { + struct dentry *h_dentry; + struct inode *h_inode; + struct file_operations *fop; + + if (au_test_ro(sb, bindex, inode)) + continue; + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + h_inode = h_dentry->d_inode; + if (!h_inode) + continue; + + /* no mnt_want_write() */ + /* cf. fs/nsfd/vfs.c and fs/nfsd/nfs4recover.c */ + vfsub_i_lock(h_inode); + /* todo: inotiry fired? */ + fop = (void *)h_inode->i_fop; + err = filemap_fdatawrite(h_inode->i_mapping); + if (!err && fop && fop->fsync) + err = fop->fsync(NULL, h_dentry, datasync); + if (!err) + err = filemap_fdatawrite(h_inode->i_mapping); + if (!err) + au_update_fuse_h_inode(NULL, h_dentry); /*ignore*/ + vfsub_i_unlock(h_inode); + } + + AuTraceErr(err); + return err; +} + +/* + * @file may be NULL + */ +static int aufs_fsync_dir(struct file *file, struct dentry *dentry, + int datasync) +{ + int err; + struct inode *inode; + struct file *h_file; + struct super_block *sb; + aufs_bindex_t bend, bindex; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), datasync); + inode = dentry->d_inode; + IMustLock(inode); + + err = 0; + sb = dentry->d_sb; + si_noflush_read_lock(sb); + if (file) { + err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, + /*locked*/1); + if (unlikely(err)) + goto out; + } else + di_write_lock_child(dentry); + + if (file) { + bend = au_fbend(file); + for (bindex = au_fbstart(file); !err && bindex <= bend; + bindex++) { + h_file = au_h_fptr(file, bindex); + if (!h_file || au_test_ro(sb, bindex, inode)) + continue; + + err = -EINVAL; + if (h_file->f_op && h_file->f_op->fsync) { + /* todo: try do_fsync() in fs/sync.c? */ + vfsub_i_lock(h_file->f_mapping->host); + err = h_file->f_op->fsync + (h_file, h_file->f_dentry, datasync); + if (!err) + au_update_fuse_h_inode + (h_file->f_vfsmnt, + h_file->f_dentry); + /*ignore*/ + vfsub_i_unlock(h_file->f_mapping->host); + } + } + } else + err = fsync_dir(dentry, datasync); + au_cpup_attr_timesizes(inode); + di_write_unlock(dentry); + if (file) + fi_write_unlock(file); + + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int aufs_readdir(struct file *file, void *dirent, filldir_t filldir) +{ + int err; + struct dentry *dentry; + struct inode *inode; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, pos %lld\n", AuDLNPair(dentry), file->f_pos); + inode = dentry->d_inode; + IMustLock(inode); + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, /*locked*/1); + if (unlikely(err)) + goto out; + err = au_vdir_init(file); + di_downgrade_lock(dentry, AuLock_IR); + if (unlikely(err)) + goto out_unlock; + + if (!au_test_nfsd(current)) { + err = au_vdir_fill_de(file, dirent, filldir); + } else { + /* + * nfsd filldir may call lookup_one_len(), vfs_getattr(), + * encode_fh() and others. + */ + struct inode *h_inode = au_h_iptr(inode, au_ibstart(inode)); + + di_read_unlock(dentry, AuLock_IR); + si_read_unlock(sb); + lockdep_off(); + err = au_vdir_fill_de(file, dirent, filldir); + lockdep_on(); + inode->i_atime = h_inode->i_atime; + fi_write_unlock(file); + + AuTraceErr(err); + return err; + } + + inode->i_atime = au_h_iptr(inode, au_ibstart(inode))->i_atime; + + out_unlock: + di_read_unlock(dentry, AuLock_IR); + fi_write_unlock(file); + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +#define AuTestEmpty_WHONLY 1 +#define AuTestEmpty_DLGT (1 << 1) +#define AuTestEmpty_DIRPERM1 (1 << 2) +#define AuTestEmpty_CALLED (1 << 3) +#define AuTestEmpty_SHWH (1 << 4) +#define au_ftest_testempty(flags, name) ((flags) & AuTestEmpty_##name) +#define au_fset_testempty(flags, name) { (flags) |= AuTestEmpty_##name; } +#define au_fclr_testempty(flags, name) { (flags) &= ~AuTestEmpty_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuTestEmpty_DLGT +#define AuTestEmpty_DLGT 0 +#undef AuTestEmpty_DIRPERM1 +#define AuTestEmpty_DIRPERM1 0 +#endif +#ifndef CONFIG_AUFS_SHWH +#undef AuTestEmpty_SHWH +#define AuTestEmpty_SHWH 0 +#endif + +struct test_empty_arg { + struct au_nhash *whlist; + unsigned int flags; + int err; + aufs_bindex_t bindex; +}; + +static int test_empty_cb(void *__arg, const char *__name, int namelen, + loff_t offset, au_filldir_ino_t ino, + unsigned int d_type) +{ + struct test_empty_arg *arg = __arg; + char *name = (void *)__name; + + LKTRTrace("%.*s\n", namelen, name); + + arg->err = 0; + au_fset_testempty(arg->flags, CALLED); + /* smp_mb(); */ + if (name[0] == '.' + && (namelen == 1 || (name[1] == '.' && namelen == 2))) + return 0; /* success */ + + if (namelen <= AUFS_WH_PFX_LEN + || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { + if (au_ftest_testempty(arg->flags, WHONLY) + && !au_nhash_test_known_wh(arg->whlist, name, namelen)) + arg->err = -ENOTEMPTY; + goto out; + } + + name += AUFS_WH_PFX_LEN; + namelen -= AUFS_WH_PFX_LEN; + if (!au_nhash_test_known_wh(arg->whlist, name, namelen)) + arg->err = au_nhash_append_wh + (arg->whlist, name, namelen, ino, d_type, arg->bindex, + au_ftest_testempty(arg->flags, SHWH)); + + out: + /* smp_mb(); */ + AuTraceErr(arg->err); + return arg->err; +} + +static int do_test_empty(struct dentry *dentry, struct test_empty_arg *arg) +{ + int err, dlgt; + struct file *h_file; + + LKTRTrace("%.*s, {%p, 0x%x, %d}\n", + AuDLNPair(dentry), arg->whlist, arg->flags, arg->bindex); + + h_file = au_h_open(dentry, arg->bindex, + O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_LARGEFILE, + /*file*/NULL); + err = PTR_ERR(h_file); + if (IS_ERR(h_file)) + goto out; + err = 0; + if (au_opt_test(au_mntflags(dentry->d_sb), UDBA_INOTIFY) + && !h_file->f_dentry->d_inode->i_nlink) + goto out_put; + + dlgt = au_ftest_testempty(arg->flags, DLGT); + do { + arg->err = 0; + au_fclr_testempty(arg->flags, CALLED); + /* smp_mb(); */ + err = vfsub_readdir(h_file, test_empty_cb, arg, dlgt); + if (err >= 0) + err = arg->err; + } while (!err && au_ftest_testempty(arg->flags, CALLED)); + + out_put: + fput(h_file); + au_sbr_put(dentry->d_sb, arg->bindex); + out: + AuTraceErr(err); + return err; +} + +struct do_test_empty_args { + int *errp; + struct dentry *dentry; + struct test_empty_arg *arg; +}; + +static void call_do_test_empty(void *args) +{ + struct do_test_empty_args *a = args; + *a->errp = do_test_empty(a->dentry, a->arg); +} + +static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg) +{ + int err, wkq_err; + struct dentry *h_dentry; + struct inode *h_inode; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + h_dentry = au_h_dptr(dentry, arg->bindex); + AuDebugOn(!h_dentry); + h_inode = h_dentry->d_inode; + AuDebugOn(!h_inode); + + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + err = au_test_h_perm_sio(h_inode, MAY_EXEC | MAY_READ, + au_test_dlgt(au_mntflags(dentry->d_sb))); + vfsub_i_unlock(h_inode); + if (!err) + err = do_test_empty(dentry, arg); + else { + struct do_test_empty_args args = { + .errp = &err, + .dentry = dentry, + .arg = arg + }; + unsigned int flags = arg->flags; + + au_fclr_testempty(arg->flags, DLGT); + au_fclr_testempty(arg->flags, DIRPERM1); + wkq_err = au_wkq_wait(call_do_test_empty, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + arg->flags = flags; + } + + AuTraceErr(err); + return err; +} + +int au_test_empty_lower(struct dentry *dentry) +{ + int err; + struct inode *inode; + struct test_empty_arg arg; + struct au_nhash *whlist; + aufs_bindex_t bindex, bstart, btail; + unsigned int mnt_flags; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + inode = dentry->d_inode; + AuDebugOn(!inode || !S_ISDIR(inode->i_mode)); + + whlist = au_nhash_new(GFP_NOFS); + err = PTR_ERR(whlist); + if (IS_ERR(whlist)) + goto out; + + bstart = au_dbstart(dentry); + mnt_flags = au_mntflags(dentry->d_sb); + arg.whlist = whlist; + arg.flags = 0; + if (au_test_dlgt(mnt_flags)) + au_fset_testempty(arg.flags, DLGT); + if (au_opt_test(mnt_flags, SHWH)) + au_fset_testempty(arg.flags, SHWH); + arg.bindex = bstart; + err = do_test_empty(dentry, &arg); + if (unlikely(err)) + goto out_whlist; + + au_fset_testempty(arg.flags, WHONLY); + if (au_test_dirperm1(mnt_flags)) + au_fset_testempty(arg.flags, DIRPERM1); + btail = au_dbtaildir(dentry); + for (bindex = bstart + 1; !err && bindex <= btail; bindex++) { + struct dentry *h_dentry; + h_dentry = au_h_dptr(dentry, bindex); + if (h_dentry && h_dentry->d_inode) { + arg.bindex = bindex; + err = do_test_empty(dentry, &arg); + } + } + + out_whlist: + au_nhash_del(whlist); + out: + AuTraceErr(err); + return err; +} + +int au_test_empty(struct dentry *dentry, struct au_nhash *whlist) +{ + int err; + struct inode *inode; + struct test_empty_arg arg; + aufs_bindex_t bindex, btail; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + inode = dentry->d_inode; + AuDebugOn(!inode || !S_ISDIR(inode->i_mode)); + + err = 0; + arg.whlist = whlist; + arg.flags = AuTestEmpty_WHONLY; + if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)) + au_fset_testempty(arg.flags, SHWH); + btail = au_dbtaildir(dentry); + for (bindex = au_dbstart(dentry); !err && bindex <= btail; bindex++) { + struct dentry *h_dentry; + h_dentry = au_h_dptr(dentry, bindex); + if (h_dentry && h_dentry->d_inode) { + arg.bindex = bindex; + err = sio_test_empty(dentry, &arg); + } + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct file_operations aufs_dir_fop = { + .read = generic_read_dir, + .readdir = aufs_readdir, + .open = aufs_open_dir, + .release = aufs_release_dir, + .flush = aufs_flush, + .fsync = aufs_fsync_dir, +}; diff --git a/fs/aufs/dir.h b/fs/aufs/dir.h new file mode 100644 index 0000000..1870601 --- /dev/null +++ b/fs/aufs/dir.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * directory operations + * + * $Id: dir.h,v 1.33 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_DIR_H__ +#define __AUFS_DIR_H__ + +#ifdef __KERNEL__ + +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +typedef u64 au_filldir_ino_t; +#else +typedef ino_t au_filldir_ino_t; +#endif + +/* ---------------------------------------------------------------------- */ + +/* need to be faster and smaller */ + +/* todo: changeable? */ +#define AuSize_DEBLK 512 +#define AuSize_NHASH 32 +#if AuSize_DEBLK < NAME_MAX || PAGE_SIZE < AuSize_DEBLK +#error invalid size AuSize_DEBLK +#endif + +typedef char au_vdir_deblk_t[AuSize_DEBLK]; + +struct au_nhash { + struct hlist_head heads[AuSize_NHASH]; +}; + +struct au_vdir_destr { + unsigned char len; + char name[0]; +} __packed; + +struct au_vdir_dehstr { + struct hlist_node hash; + struct au_vdir_destr *str; +}; + +struct au_vdir_de { + ino_t de_ino; + unsigned char de_type; + /* caution: packed */ + struct au_vdir_destr de_str; +} __packed; + +struct au_vdir_wh { + struct hlist_node wh_hash; + aufs_bindex_t wh_bindex; +#ifdef CONFIG_AUFS_SHWH + ino_t wh_ino; + unsigned char wh_type; + /* caution: packed */ +#endif + struct au_vdir_destr wh_str; +} __packed; + +union au_vdir_deblk_p { + unsigned char *p; + au_vdir_deblk_t *deblk; + struct au_vdir_de *de; +}; + +struct au_vdir { + au_vdir_deblk_t **vd_deblk; + int vd_nblk; + struct { + int i; + union au_vdir_deblk_p p; + } vd_last; + + unsigned long vd_version; + unsigned long vd_jiffy; +}; + +/* ---------------------------------------------------------------------- */ + +/* dir.c */ +extern struct file_operations aufs_dir_fop; +int au_test_empty_lower(struct dentry *dentry); +int au_test_empty(struct dentry *dentry, struct au_nhash *whlist); + +/* vdir.c */ +struct au_nhash *au_nhash_new(gfp_t gfp); +void au_nhash_del(struct au_nhash *nhash); +void au_nhash_init(struct au_nhash *nhash); +void au_nhash_move(struct au_nhash *dst, struct au_nhash *src); +void au_nhash_fin(struct au_nhash *nhash); +int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt, + int limit); +int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int namelen); +int au_nhash_append_wh(struct au_nhash *whlist, char *name, int namelen, + ino_t ino, unsigned int d_type, aufs_bindex_t bindex, + unsigned char shwh); +void au_vdir_free(struct au_vdir *vdir); +int au_vdir_init(struct file *file); +int au_vdir_fill_de(struct file *file, void *dirent, filldir_t filldir); + +/* ---------------------------------------------------------------------- */ + +static inline +void au_shwh_init_wh(struct au_vdir_wh *wh, ino_t ino, unsigned char d_type) +{ +#ifdef CONFIG_AUFS_SHWH + wh->wh_ino = ino; + wh->wh_type = d_type; +#endif +} + +static inline void au_add_nlink(struct inode *dir, struct inode *h_dir) +{ + AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode)); + dir->i_nlink += h_dir->i_nlink - 2; + if (h_dir->i_nlink < 2) + dir->i_nlink += 2; +} + +static inline void au_sub_nlink(struct inode *dir, struct inode *h_dir) +{ + AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode)); + dir->i_nlink -= h_dir->i_nlink - 2; + if (h_dir->i_nlink < 2) + dir->i_nlink -= 2; +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_DIR_H__ */ diff --git a/fs/aufs/dlgt.c b/fs/aufs/dlgt.c new file mode 100644 index 0000000..6eea1b5 --- /dev/null +++ b/fs/aufs/dlgt.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * lookup functions in 'delegate' mode + * + * $Id: dlgt.c,v 1.4 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +struct au_lookup_one_len_args { + struct dentry **errp; + const char *name; + struct dentry *parent; + int len; +}; + +static void au_call_lookup_one_len(void *args) +{ + struct au_lookup_one_len_args *a = args; + *a->errp = vfsub_lookup_one_len(a->name, a->parent, a->len); +} + +struct dentry *au_lkup_one_dlgt(const char *name, struct dentry *parent, + int len, unsigned int flags) +{ + struct dentry *dentry; + int dirperm1; + + LKTRTrace("%.*s/%.*s, 0x%x\n", AuDLNPair(parent), len, name, flags); + + dirperm1 = au_ftest_ndx(flags, DIRPERM1); + if (!dirperm1 && !au_ftest_ndx(flags, DLGT)) + dentry = vfsub_lookup_one_len(name, parent, len); + else { + int wkq_err; + struct au_lookup_one_len_args args = { + .errp = &dentry, + .name = name, + .parent = parent, + .len = len + }; + wkq_err = au_wkq_wait(au_call_lookup_one_len, &args, + /*dlgt*/!dirperm1); + if (unlikely(wkq_err)) + dentry = ERR_PTR(wkq_err); + } + + AuTraceErrPtr(dentry); + return dentry; +} + +/* ---------------------------------------------------------------------- */ + +struct security_inode_permission_args { + int *errp; + struct inode *h_inode; + int mask; + struct nameidata *fake_nd; +}; + +static void call_security_inode_permission(void *args) +{ + struct security_inode_permission_args *a = args; + LKTRTrace("fsuid %d\n", current->fsuid); + *a->errp = security_inode_permission(a->h_inode, a->mask, a->fake_nd); +} + +int au_security_inode_permission(struct inode *h_inode, int mask, + struct nameidata *fake_nd, int dlgt) +{ + int err; + + AuTraceEnter(); + + if (!dlgt) + err = security_inode_permission(h_inode, mask, fake_nd); + else { + int wkq_err; + struct security_inode_permission_args args = { + .errp = &err, + .h_inode = h_inode, + .mask = mask, + .fake_nd = fake_nd + }; + wkq_err = au_wkq_wait(call_security_inode_permission, &args, + /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/export.c b/fs/aufs/export.c new file mode 100644 index 0000000..d2d474c --- /dev/null +++ b/fs/aufs/export.c @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * export via nfs + * + * $Id: export.c,v 1.43 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +#include +#else +#include +#endif +#include +#include "aufs.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +static struct dentry * +au_call_decode_fh(struct vfsmount *h_mnt, __u32 *fh, int fh_len, int fh_type, + int (*acceptable)(void *, struct dentry *), void *context) +{ + /* in linux-2.6.24, it takes struct fid * as file handle */ + return exportfs_decode_fh(h_mnt, (void *)fh, fh_len, fh_type, + acceptable, context); +} + +static int +au_call_encode_fh(struct dentry *h_dentry, __u32 *fh, int *max_len, + int connectable) +{ + /* in linux-2.6.24, it takes struct fid * as file handle */ + return exportfs_encode_fh(h_dentry, (void *)fh, max_len, connectable); +} +#else +extern struct export_operations export_op_default; +#define CALL(ops, func) \ + (((ops)->func) ? ((ops)->func) : export_op_default.func) + +static struct dentry * +au_call_decode_fh(struct vfsmount *h_mnt, __u32 *fh, int fh_len, int fh_type, + int (*acceptable)(void *, struct dentry *), void *context) +{ + return CALL(h_mnt->mnt_sb->s_export_op, decode_fh) + (h_mnt->mnt_sb, fh, fh_len, fh_type, acceptable, context); +} + +static int +au_call_encode_fh(struct dentry *h_dentry, __u32 *fh, int *max_len, + int connectable) +{ + return CALL(h_dentry->d_sb->s_export_op, encode_fh) + (h_dentry, fh, max_len, connectable); +} +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) */ + +/* ---------------------------------------------------------------------- */ + +union conv { +#ifdef CONFIG_AUFS_INO_T_64 + __u32 a[2]; +#else + __u32 a[1]; +#endif + ino_t ino; +}; + +static ino_t decode_ino(__u32 *a) +{ + union conv u; + + BUILD_BUG_ON(sizeof(u.ino) != sizeof(u.a)); + u.a[0] = a[0]; +#ifdef CONFIG_AUFS_INO_T_64 + u.a[1] = a[1]; +#endif + return u.ino; +} + +static void encode_ino(__u32 *a, ino_t ino) +{ + union conv u; + + u.ino = ino; + a[0] = u.a[0]; +#ifdef CONFIG_AUFS_INO_T_64 + a[1] = u.a[1]; +#endif +} + +/* NFS file handle */ +enum { + Fh_br_id, + Fh_sigen, +#ifdef CONFIG_AUFS_INO_T_64 + /* support 64bit inode number */ + Fh_ino1, + Fh_ino2, + Fh_dir_ino1, + Fh_dir_ino2, +#else + Fh_ino1, + Fh_dir_ino1, +#endif + Fh_igen, + Fh_h_type, + Fh_tail, + + Fh_ino = Fh_ino1, + Fh_dir_ino = Fh_dir_ino1 +}; + +static int au_test_anon(struct dentry *dentry) +{ + return !!(dentry->d_flags & DCACHE_DISCONNECTED); +} + +/* ---------------------------------------------------------------------- */ +/* inode generation external table */ + +int au_xigen_inc(struct inode *inode) +{ + int err; + loff_t pos; + ssize_t sz; + __u32 igen; + struct super_block *sb; + struct au_sbinfo *sbinfo; + + LKTRTrace("i%lu\n", (unsigned long)inode->i_ino); + + err = 0; + sb = inode->i_sb; + if (unlikely(!au_opt_test_xino(au_mntflags(sb)))) + goto out; + + pos = inode->i_ino; + pos *= sizeof(igen); + igen = inode->i_generation + 1; + sbinfo = au_sbi(sb); + sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen, + sizeof(igen), &pos); + if (sz == sizeof(igen)) + goto out; /* success */ + + err = sz; + if (unlikely(sz >= 0)) { + err = -EIO; + AuIOErr("xigen error (%zd)\n", sz); + } + + out: + AuTraceErr(err); + return err; +} + +int au_xigen_new(struct inode *inode) +{ + int err; + loff_t pos; + ssize_t sz; + struct super_block *sb; + struct au_sbinfo *sbinfo; + struct file *file; + + LKTRTrace("i%lu\n", inode->i_ino); + + err = 0; + /* todo: dirty, at mount time */ + if (inode->i_ino == AUFS_ROOT_INO) + goto out; + sb = inode->i_sb; + if (unlikely(!au_opt_test_xino(au_mntflags(sb)))) + goto out; + + err = -EFBIG; + pos = inode->i_ino; + if (unlikely(Au_LOFF_MAX / sizeof(inode->i_generation) - 1 < pos)) { + AuIOErr1("too large i%lld\n", pos); + goto out; + } + pos *= sizeof(inode->i_generation); + + err = 0; + sbinfo = au_sbi(sb); + file = sbinfo->si_xigen; + BUG_ON(!file); + + if (i_size_read(file->f_dentry->d_inode) + < pos + sizeof(inode->i_generation)) { + inode->i_generation = atomic_inc_return(&sbinfo->si_xigen_next); + sz = xino_fwrite(sbinfo->si_xwrite, file, &inode->i_generation, + sizeof(inode->i_generation), &pos); + } else + sz = xino_fread(sbinfo->si_xread, file, &inode->i_generation, + sizeof(inode->i_generation), &pos); + if (sz == sizeof(inode->i_generation)) + goto out; /* success */ + + err = sz; + if (unlikely(sz >= 0)) { + err = -EIO; + AuIOErr("xigen error (%zd)\n", sz); + } + + out: + AuTraceErr(err); + return err; +} + +int au_xigen_set(struct super_block *sb, struct file *base) +{ + int err; + struct au_sbinfo *sbinfo; + struct file *file; + + LKTRTrace("%.*s\n", AuDLNPair(base->f_dentry)); + SiMustWriteLock(sb); + + sbinfo = au_sbi(sb); + file = au_xino_create2(sb, base, sbinfo->si_xigen); + err = PTR_ERR(file); + if (IS_ERR(file)) + goto out; + err = 0; + if (sbinfo->si_xigen) + fput(sbinfo->si_xigen); + sbinfo->si_xigen = file; + + out: + AuTraceErr(err); + return err; +} + +void au_xigen_clr(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + + sbinfo = au_sbi(sb); + if (sbinfo->si_xigen) { + fput(sbinfo->si_xigen); + sbinfo->si_xigen = NULL; + } +} + +/* ---------------------------------------------------------------------- */ + +static struct dentry *decode_by_ino(struct super_block *sb, ino_t ino, + ino_t dir_ino) +{ + struct dentry *dentry, *d; + struct inode *inode; + au_gen_t sigen; + + LKTRTrace("i%lu, diri%lu\n", + (unsigned long)ino, (unsigned long)dir_ino); + + dentry = NULL; + inode = ilookup(sb, ino); + if (!inode) + goto out; + + dentry = ERR_PTR(-ESTALE); + sigen = au_sigen(sb); + if (unlikely(is_bad_inode(inode) + || IS_DEADDIR(inode) + || sigen != au_iigen(inode))) + goto out_iput; + + dentry = NULL; + if (!dir_ino || S_ISDIR(inode->i_mode)) + dentry = d_find_alias(inode); + else { + spin_lock(&dcache_lock); + list_for_each_entry(d, &inode->i_dentry, d_alias) + if (!au_test_anon(d) + && d->d_parent->d_inode->i_ino == dir_ino) { + dentry = dget_locked(d); + break; + } + spin_unlock(&dcache_lock); + } + if (unlikely(dentry && sigen != au_digen(dentry))) { + dput(dentry); + dentry = ERR_PTR(-ESTALE); + } + + out_iput: + iput(inode); + out: + AuTraceErrPtr(dentry); + return dentry; +} + +/* ---------------------------------------------------------------------- */ + +struct au_nfsd_si_lock { + const au_gen_t sigen; + const aufs_bindex_t br_id; + unsigned char force_lock; +}; + +static aufs_bindex_t si_nfsd_read_lock(struct super_block *sb, + struct au_nfsd_si_lock *nsi_lock) +{ + aufs_bindex_t bindex; + + si_read_lock(sb, AuLock_FLUSH); + + /* branch id may be wrapped around */ + bindex = au_br_index(sb, nsi_lock->br_id); + LKTRTrace("b%d\n", bindex); + if (bindex >= 0 && nsi_lock->sigen + AUFS_BRANCH_MAX > au_sigen(sb)) + goto out; /* success */ + + if (!nsi_lock->force_lock) + si_read_unlock(sb); + bindex = -1; + + out: + return bindex; +} + +struct find_name_by_ino { + int called, found; + ino_t ino; + char *name; + int namelen; +}; + +static int +find_name_by_ino(void *arg, const char *name, int namelen, loff_t offset, + au_filldir_ino_t ino, unsigned int d_type) +{ + struct find_name_by_ino *a = arg; + + a->called++; + if (a->ino != ino) + return 0; + + memcpy(a->name, name, namelen); + a->namelen = namelen; + a->found = 1; + return 1; +} + +static struct dentry *au_lkup_by_ino(struct path *path, ino_t ino, + struct au_nfsd_si_lock *nsi_lock) +{ + struct dentry *dentry, *parent; + struct file *file; + struct inode *dir; + struct find_name_by_ino arg; + int err; + + parent = path->dentry; + LKTRTrace("%.*s, i%lu\n", AuDLNPair(parent), (unsigned long)ino); + + if (nsi_lock) + si_read_unlock(parent->d_sb); + path_get(path); + file = dentry_open(parent, path->mnt, au_dir_roflags); + dentry = (void *)file; + if (IS_ERR(file)) + goto out; + + dentry = ERR_PTR(-ENOMEM); + arg.name = __getname(); + if (unlikely(!arg.name)) + goto out_file; + arg.ino = ino; + arg.found = 0; + do { + arg.called = 0; + /* smp_mb(); */ + err = vfsub_readdir(file, find_name_by_ino, &arg, /*dlgt*/0); + } while (!err && !arg.found && arg.called); + dentry = ERR_PTR(err); + if (unlikely(err)) + goto out_name; + dentry = ERR_PTR(-ENOENT); + if (!arg.found) + goto out_name; + + /* do not call au_lkup_one(), nor dlgt */ + dir = parent->d_inode; + vfsub_i_lock(dir); + dentry = vfsub_lookup_one_len(arg.name, parent, arg.namelen); + vfsub_i_unlock(dir); + AuTraceErrPtr(dentry); + if (IS_ERR(dentry)) + goto out_name; + AuDebugOn(au_test_anon(dentry)); + if (unlikely(!dentry->d_inode)) { + dput(dentry); + dentry = ERR_PTR(-ENOENT); + } + + out_name: + __putname(arg.name); + out_file: + fput(file); + out: + if (unlikely(nsi_lock + && si_nfsd_read_lock(parent->d_sb, nsi_lock) < 0)) + if (!IS_ERR(dentry)) { + dput(dentry); + dentry = ERR_PTR(-ESTALE); + } + AuTraceErrPtr(dentry); + return dentry; +} + +static /* noinline_for_stack */ +struct dentry *decode_by_dir_ino(struct super_block *sb, ino_t ino, + ino_t dir_ino, struct au_nfsd_si_lock *nsi_lock) +{ + struct dentry *dentry, *parent; + struct path path; + + LKTRTrace("i%lu, diri%lu\n", + (unsigned long)ino, (unsigned long)dir_ino); + + parent = sb->s_root; + if (dir_ino != AUFS_ROOT_INO) { + parent = decode_by_ino(sb, dir_ino, 0); + dentry = parent; + if (!parent) + goto out; + if (IS_ERR(parent)) + goto out; + AuDebugOn(au_test_anon(parent)); + } else + dget(parent); + + path.dentry = parent; + path.mnt = au_sbi(sb)->si_mnt; + dentry = au_lkup_by_ino(&path, ino, nsi_lock); + dput(parent); + + out: + AuTraceErrPtr(dentry); + return dentry; +} + +/* ---------------------------------------------------------------------- */ + +static int h_acceptable(void *expv, struct dentry *dentry) +{ + return 1; +} + +static char *au_build_path(struct dentry *h_parent, struct path *h_rootpath, + char *buf, int len, struct super_block *sb) +{ + char *p; + int n; + + AuTraceEnter(); + + p = d_path(h_rootpath->dentry, h_rootpath->mnt, buf, len); + if (IS_ERR(p)) + goto out; + n = strlen(p); + + p = d_path(h_parent, h_rootpath->mnt, buf, len); + if (IS_ERR(p)) + goto out; + LKTRTrace("%s\n", p); + if (n != 1) + p += n; + LKTRTrace("%p, %s, %ld\n", + p, p, (long)(p - buf)); + + p = d_path(sb->s_root, au_sbi(sb)->si_mnt, buf, len - strlen(p)); + if (IS_ERR(p)) + goto out; + if (n != 1) + p[strlen(p)] = '/'; + LKTRTrace("%s\n", p); + + out: + AuTraceErrPtr(p); + return p; +} + +static noinline_for_stack +struct dentry *decode_by_path(struct super_block *sb, aufs_bindex_t bindex, + ino_t ino, __u32 *fh, int fh_len, + struct au_nfsd_si_lock *nsi_lock) +{ + struct dentry *dentry, *h_parent, *root; + struct super_block *h_sb; + char *pathname, *p; + struct vfsmount *h_mnt; + struct au_branch *br; + int err; + struct nameidata nd; + struct path path; + + LKTRTrace("b%d\n", bindex); + SiMustAnyLock(sb); + + br = au_sbr(sb, bindex); + /* au_br_get(br); */ + h_mnt = br->br_mnt; + h_sb = h_mnt->mnt_sb; + LKTRTrace("%s, h_decode_fh\n", au_sbtype(h_sb)); + h_parent = au_call_decode_fh(h_mnt, fh + Fh_tail, fh_len - Fh_tail, + fh[Fh_h_type], h_acceptable, + /*context*/NULL); + dentry = h_parent; + if (unlikely(!h_parent || IS_ERR(h_parent))) { + AuWarn1("%s decode_fh failed, %ld\n", + au_sbtype(h_sb), PTR_ERR(h_parent)); + goto out; + } + dentry = NULL; + if (unlikely(au_test_anon(h_parent))) { + AuWarn1("%s decode_fh returned a disconnected dentry\n", + au_sbtype(h_sb)); + goto out_h_parent; + } + + dentry = ERR_PTR(-ENOMEM); + pathname = (void *)__get_free_page(GFP_NOFS); + if (unlikely(!pathname)) + goto out_h_parent; + + root = sb->s_root; + path.mnt = h_mnt; + di_read_lock_parent(root, !AuLock_IR); + path.dentry = au_h_dptr(root, bindex); + di_read_unlock(root, !AuLock_IR); + p = au_build_path(h_parent, &path, pathname, PAGE_SIZE, sb); + dentry = (void *)p; + if (IS_ERR(p)) + goto out_pathname; + + LKTRTrace("%s\n", p); + si_read_unlock(sb); + err = vfsub_path_lookup(p, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &nd); + dentry = ERR_PTR(err); + if (unlikely(err)) + goto out_relock; + + dentry = ERR_PTR(-ENOENT); + AuDebugOn(au_test_anon(nd.dentry)); + if (unlikely(!nd.dentry->d_inode)) + goto out_nd; + + if (ino != nd.dentry->d_inode->i_ino) { + path.mnt = nd.mnt; + path.dentry = nd.dentry; + dentry = au_lkup_by_ino(&path, ino, /*nsi_lock*/NULL); + } else + dentry = dget(nd.dentry); + + out_nd: + path_release(&nd); + out_relock: + if (unlikely(si_nfsd_read_lock(sb, nsi_lock) < 0)) + if (!IS_ERR(dentry)) { + dput(dentry); + dentry = ERR_PTR(-ESTALE); + } + out_pathname: + free_page((unsigned long)pathname); + out_h_parent: + dput(h_parent); + out: + /* au_br_put(br); */ + AuTraceErrPtr(dentry); + return dentry; +} + +/* ---------------------------------------------------------------------- */ + +static struct dentry * +aufs_decode_fh(struct super_block *sb, __u32 *fh, int fh_len, int fh_type, + int (*acceptable)(void *context, struct dentry *de), + void *context) +{ + struct dentry *dentry; + ino_t ino, dir_ino; + aufs_bindex_t bindex; + struct au_nfsd_si_lock nsi_lock = { + .sigen = fh[Fh_sigen], + .br_id = fh[Fh_br_id], + .force_lock = 0 + }; + + LKTRTrace("%d, fh{br_id %u, sigen %u, i%u, diri%u, g%u}\n", + fh_type, fh[Fh_br_id], fh[Fh_sigen], fh[Fh_ino], + fh[Fh_dir_ino], fh[Fh_igen]); + AuDebugOn(fh_len < Fh_tail); + + dentry = ERR_PTR(-ESTALE); + /* branch id may be wrapped around */ + bindex = si_nfsd_read_lock(sb, &nsi_lock); + if (unlikely(bindex < 0)) + goto out; + nsi_lock.force_lock = 1; + + /* is this inode still cached? */ + ino = decode_ino(fh + Fh_ino); + AuDebugOn(ino == AUFS_ROOT_INO); + dir_ino = decode_ino(fh + Fh_dir_ino); + dentry = decode_by_ino(sb, ino, dir_ino); + if (IS_ERR(dentry)) + goto out_unlock; + if (dentry) + goto accept; + + /* is the parent dir cached? */ + dentry = decode_by_dir_ino(sb, ino, dir_ino, &nsi_lock); + if (IS_ERR(dentry)) + goto out_unlock; + if (dentry) + goto accept; + + /* lookup path */ + dentry = decode_by_path(sb, bindex, ino, fh, fh_len, &nsi_lock); + if (IS_ERR(dentry)) + goto out_unlock; + if (unlikely(!dentry)) + goto out_unlock; + + accept: + LKTRLabel(accept); + if (dentry->d_inode->i_generation == fh[Fh_igen] + && acceptable(context, dentry)) + goto out_unlock; /* success */ + + LKTRLabel(stale); + dput(dentry); + dentry = ERR_PTR(-ESTALE); + out_unlock: + LKTRLabel(out_unlock); + si_read_unlock(sb); + out: + LKTRLabel(out); + if (0 && IS_ERR(dentry)) + dentry = ERR_PTR(-ESTALE); + AuTraceErrPtr(dentry); + return dentry; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) +static struct dentry * +aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len, + int fh_type) +{ + return aufs_decode_fh(sb, fid->raw, fh_len, fh_type, h_acceptable, + /*context*/NULL); +} +#endif /* KERNEL_VERSION */ + +/* ---------------------------------------------------------------------- */ + +static int aufs_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len, + int connectable) +{ + int err; + aufs_bindex_t bindex, bend; + struct super_block *sb, *h_sb; + struct inode *inode; + struct dentry *parent, *h_parent; + struct au_branch *br; + + LKTRTrace("%.*s, max %d, conn %d\n", + AuDLNPair(dentry), *max_len, connectable); + AuDebugOn(au_test_anon(dentry)); + + parent = NULL; + err = -ENOSPC; + if (unlikely(*max_len <= Fh_tail)) { + AuWarn1("NFSv2 client (max_len %d)?\n", *max_len); + goto out; + } + + err = 0; //FILEID_ROOT; + if (IS_ROOT(dentry)) { + AuDebugOn(dentry->d_inode->i_ino != AUFS_ROOT_INO); + goto out; + } + + err = -EIO; + h_parent = NULL; + sb = dentry->d_sb; + aufs_read_lock(dentry, AuLock_FLUSH | AuLock_IR); + parent = dget_parent(dentry); + di_read_lock_parent(parent, !AuLock_IR); + inode = dentry->d_inode; + AuDebugOn(!inode); +#ifdef CONFIG_AUFS_DEBUG + { + unsigned int mnt_flags = au_mntflags(sb); + + if (unlikely(!au_opt_test_xino(mnt_flags))) + AuWarn1("NFS-exporting requires xino\n"); + if (unlikely(0 && !au_opt_test(mnt_flags, UDBA_INOTIFY))) + AuWarn1("udba=inotify is recommended " + "for NFS-exporting\n"); + } +#endif + + bend = au_dbtaildir(parent); + for (bindex = au_dbstart(parent); bindex <= bend; bindex++) { + h_parent = au_h_dptr(parent, bindex); + if (h_parent) { + dget(h_parent); + break; + } + } + if (unlikely(!h_parent)) + goto out_unlock; + LKTRTrace("b%d\n", bindex); + + err = -EPERM; + br = au_sbr(sb, bindex); + h_sb = br->br_mnt->mnt_sb; + if (unlikely(!h_sb->s_export_op)) { + AuErr1("%s branch is not exportable\n", au_sbtype(h_sb)); + goto out_dput; + } + + fh[Fh_br_id] = br->br_id; + fh[Fh_sigen] = au_sigen(sb); + encode_ino(fh + Fh_ino, inode->i_ino); + encode_ino(fh + Fh_dir_ino, parent->d_inode->i_ino); + fh[Fh_igen] = inode->i_generation; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) + /* it should be set at exporting time */ + if (unlikely(!h_sb->s_export_op->find_exported_dentry)) { + AuWarn("set default find_exported_dentry for %s\n", + au_sbtype(h_sb)); + h_sb->s_export_op->find_exported_dentry = find_exported_dentry; + } +#endif + + *max_len -= Fh_tail; + fh[Fh_h_type] = au_call_encode_fh(h_parent, fh + Fh_tail, max_len, + /*connectable or subtreecheck*/0); + err = fh[Fh_h_type]; + *max_len += Fh_tail; + /* todo: macros? */ + if (err != 255) + err = 99; + else + AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb)); + + out_dput: + dput(h_parent); + out_unlock: + di_read_unlock(parent, !AuLock_IR); + dput(parent); + aufs_read_unlock(dentry, AuLock_IR); + out: + AuTraceErr(err); + if (unlikely(err < 0)) + err = 255; + return err; +} + +/* ---------------------------------------------------------------------- */ + +static struct export_operations aufs_export_op = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + .fh_to_dentry = aufs_fh_to_dentry, +#else + .decode_fh = aufs_decode_fh, +#endif + .encode_fh = aufs_encode_fh +}; + +void au_export_init(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + __u32 u; + + AuTraceEnter(); + SiMustWriteLock(sb); + + sb->s_export_op = &aufs_export_op; + sbinfo = au_sbi(sb); + sbinfo->si_xigen = NULL; + get_random_bytes(&u, sizeof(u)); + BUILD_BUG_ON(sizeof(u) != sizeof(int)); + atomic_set(&sbinfo->si_xigen_next, u); + //memset(&sbinfo->si_xinodir, 0, sizeof(struct path)); +} diff --git a/fs/aufs/f_op.c b/fs/aufs/f_op.c new file mode 100644 index 0000000..df38c61 --- /dev/null +++ b/fs/aufs/f_op.c @@ -0,0 +1,813 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * file and vm operations + * + * $Id: f_op.c,v 1.60 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include +#include +#include +#include +#include "aufs.h" + +/* common function to regular file and dir */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +#define FlushArgs h_file, id +int aufs_flush(struct file *file, fl_owner_t id) +#else +#define FlushArgs h_file +int aufs_flush(struct file *file) +#endif +{ + int err; + struct dentry *dentry; + aufs_bindex_t bindex, bend; + struct file *h_file; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + si_noflush_read_lock(dentry->d_sb); + fi_read_lock(file); + di_read_lock_child(dentry, AuLock_IW); + + err = 0; + bend = au_fbend(file); + for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) { + h_file = au_h_fptr(file, bindex); + if (h_file && h_file->f_op && h_file->f_op->flush) { + err = h_file->f_op->flush(FlushArgs); + if (!err) + au_update_fuse_h_inode + (h_file->f_vfsmnt, h_file->f_dentry); + /*ignore*/ + } + } + au_cpup_attr_timesizes(dentry->d_inode); + + di_read_unlock(dentry, AuLock_IW); + fi_read_unlock(file); + si_read_unlock(dentry->d_sb); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int do_open_nondir(struct file *file, int flags) +{ + int err; + aufs_bindex_t bindex; + struct super_block *sb; + struct file *h_file; + struct dentry *dentry; + struct inode *inode; + struct au_finfo *finfo; + + dentry = file->f_dentry; + LKTRTrace("%.*s, flags 0%o\n", AuDLNPair(dentry), flags); + FiMustWriteLock(file); + inode = dentry->d_inode; + AuDebugOn(!inode || S_ISDIR(inode->i_mode)); + + err = 0; + finfo = au_fi(file); + finfo->fi_h_vm_ops = NULL; + sb = dentry->d_sb; + bindex = au_dbstart(dentry); + AuDebugOn(!au_h_dptr(dentry, bindex)->d_inode); + /* O_TRUNC is processed already */ + BUG_ON(au_test_ro(sb, bindex, inode) && (flags & O_TRUNC)); + + h_file = au_h_open(dentry, bindex, flags, file); + if (IS_ERR(h_file)) + err = PTR_ERR(h_file); + else { + au_set_fbstart(file, bindex); + au_set_fbend(file, bindex); + au_set_h_fptr(file, bindex, h_file); + au_update_figen(file); + /* todo: necessary? */ + /* file->f_ra = h_file->f_ra; */ + err = 0; + } + AuTraceErr(err); + return err; +} + +static int aufs_open_nondir(struct inode *inode, struct file *file) +{ + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(file->f_dentry)); + + return au_do_open(inode, file, do_open_nondir); +} + +static int aufs_release_nondir(struct inode *inode, struct file *file) +{ + struct super_block *sb = file->f_dentry->d_sb; + + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(file->f_dentry)); + + si_noflush_read_lock(sb); + au_finfo_fin(file); + si_read_unlock(sb); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static ssize_t aufs_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + ssize_t err; + struct dentry *dentry; + struct file *h_file; + struct super_block *sb; + struct inode *h_inode; + + dentry = file->f_dentry; + LKTRTrace("%.*s, cnt %zu, pos %lld\n", AuDLNPair(dentry), count, *ppos); + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0, + /*locked*/0); + if (unlikely(err)) + goto out; + + /* support LSM and notify */ + h_file = au_h_fptr(file, au_fbstart(file)); + h_inode = h_file->f_dentry->d_inode; + err = vfsub_read_u(h_file, buf, count, ppos, + au_test_dlgt(au_mntflags(sb))); + /* todo: necessary? */ + /* file->f_ra = h_file->f_ra; */ + dentry->d_inode->i_atime = h_file->f_dentry->d_inode->i_atime; + + di_read_unlock(dentry, AuLock_IR); + fi_read_unlock(file); + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +static ssize_t aufs_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + ssize_t err; + struct dentry *dentry; + struct inode *inode; + struct super_block *sb; + unsigned int mnt_flags; + struct file *h_file; + char __user *buf = (char __user *)ubuf; + struct au_hin_ignore ign; + struct vfsub_args vargs; + aufs_bindex_t bstart; + int hinotify; + struct au_pin pin; + + dentry = file->f_dentry; + LKTRTrace("%.*s, cnt %zu, pos %lld\n", AuDLNPair(dentry), count, *ppos); + + inode = dentry->d_inode; + vfsub_i_lock(inode); + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + mnt_flags = au_mntflags(sb); + hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY); + vfsub_args_init(&vargs, &ign, au_test_dlgt(mnt_flags), 0); + + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1, + /*locked*/1); + if (unlikely(err)) + goto out; + err = au_ready_to_write(file, -1, &pin); + di_downgrade_lock(dentry, AuLock_IR); + if (unlikely(err)) + goto out_unlock; + + bstart = au_fbstart(file); + h_file = au_h_fptr(file, bstart); + if (!hinotify) { + au_unpin(&pin); + err = vfsub_write_u(h_file, buf, count, ppos, &vargs); + } else { + vfsub_ign_hinode(&vargs, IN_MODIFY, au_pinned_hdir(&pin)); + err = vfsub_write_u(h_file, buf, count, ppos, &vargs); + au_unpin(&pin); + } + au_cpup_attr_timesizes(inode); + + out_unlock: + di_read_unlock(dentry, AuLock_IR); + fi_write_unlock(file); + out: + si_read_unlock(sb); + vfsub_i_unlock(inode); + AuTraceErr(err); + return err; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23) \ + || defined(CONFIG_AUFS_SPLICE_PATCH) +static int au_test_loopback(void) +{ + const char c = current->comm[4]; + /* true if a kernel thread named 'loop[0-9].*' accesses a file */ + const int loopback = (current->mm == NULL + && '0' <= c && c <= '9' + && strncmp(current->comm, "loop", 4) == 0); + return loopback; +} +#endif + +#ifdef CONFIG_AUFS_SPLICE_PATCH +static ssize_t aufs_splice_read(struct file *file, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags) +{ + ssize_t err; + struct file *h_file; + struct dentry *dentry; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, pos %lld, len %zu\n", AuDLNPair(dentry), *ppos, len); + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0, + /*locked*/0); + if (unlikely(err)) + goto out; + + err = -EINVAL; + /* support LSM and notify */ + h_file = au_h_fptr(file, au_fbstart(file)); + if (au_test_loopback()) { + file->f_mapping = h_file->f_mapping; + smp_mb(); /* unnecessary? */ + } + err = vfsub_splice_to(h_file, ppos, pipe, len, flags, + au_test_dlgt(au_mntflags(sb))); + /* todo: necessasry? */ + /* file->f_ra = h_file->f_ra; */ + dentry->d_inode->i_atime = h_file->f_dentry->d_inode->i_atime; + di_read_unlock(dentry, AuLock_IR); + fi_read_unlock(file); + + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +static ssize_t +aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos, + size_t len, unsigned int flags) +{ + ssize_t err; + struct dentry *dentry; + struct inode *inode, *h_inode; + struct super_block *sb; + struct file *h_file; + /* struct au_hin_ignore ign; */ + struct vfsub_args vargs; + unsigned int mnt_flags; + struct au_pin pin; + + dentry = file->f_dentry; + LKTRTrace("%.*s, len %zu, pos %lld\n", AuDLNPair(dentry), len, *ppos); + + inode = dentry->d_inode; + vfsub_i_lock(inode); + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + mnt_flags = au_mntflags(sb); + vfsub_args_init(&vargs, /*&ign*/NULL, au_test_dlgt(mnt_flags), 0); + + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1, + /*locked*/1); + if (unlikely(err)) + goto out; + err = au_ready_to_write(file, -1, &pin); + di_downgrade_lock(dentry, AuLock_IR); + if (unlikely(err)) + goto out_unlock; + + /* support LSM and notify */ + /* current vfs_splice_from() doesn't fire up the inotify event */ + h_file = au_h_fptr(file, au_fbstart(file)); + h_inode = h_file->f_dentry->d_inode; + if (1 || !au_opt_test(mnt_flags, UDBA_INOTIFY)) { + au_unpin(&pin); + err = vfsub_splice_from(pipe, h_file, ppos, len, flags, &vargs); + } +#if 0 /* reserved for future use */ + else { + struct dentry *parent = dget_parent(dentry); + vfsub_ign_hinode(&vargs, IN_MODIFY, au_pinned_hdir(&pin)); + err = vfsub_splice_from(pipe, h_file, ppos, len, flags, &vargs); + au_unpin(&pin); + } +#endif + au_cpup_attr_timesizes(inode); + + out_unlock: + di_read_unlock(dentry, AuLock_IR); + fi_write_unlock(file); + out: + si_read_unlock(sb); + vfsub_i_unlock(inode); + AuTraceErr(err); + return err; +} +#endif /* CONFIG_AUFS_SPLICE_PATCH */ + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +static int aufs_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + int err; + struct dentry *dentry; + struct file *file, *h_file; + struct inode *inode; + static DECLARE_WAIT_QUEUE_HEAD(wq); + struct au_finfo *finfo; + + AuTraceEnter(); + AuDebugOn(!vma || !vma->vm_file); + /* todo: non-robr mode, user vm_file as it is? */ + wait_event(wq, (file = au_robr_safe_file(vma))); + AuDebugOn(!au_test_aufs(file->f_dentry->d_sb)); + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + inode = dentry->d_inode; + AuDebugOn(!S_ISREG(inode->i_mode)); + + /* do not revalidate, no si lock */ + finfo = au_fi(file); + h_file = finfo->fi_hfile[0 + finfo->fi_bstart].hf_file; + AuDebugOn(!h_file || !au_test_mmapped(file)); + fi_write_lock(file); + vma->vm_file = h_file; + err = finfo->fi_h_vm_ops->fault(vma, vmf); + /* todo: necessary? */ + /* file->f_ra = h_file->f_ra; */ + au_robr_reset_file(vma, file); + fi_write_unlock(file); +#if 0 /* def CONFIG_SMP */ + /* wake_up_nr(&wq, online_cpu - 1); */ + wake_up_all(&wq); +#else + wake_up(&wq); +#endif + + if (!(err & VM_FAULT_ERROR)) { +#if 0 /* debug */ + struct page *page; + page = vmf->page; + AuDbg("%p, %d\n", page, page_mapcount(page)); + + page->mapping = file->f_mapping; + get_page(page); + file->f_mapping = h_file->f_mapping; + touch_atime(NULL, dentry); + inode->i_atime = h_file->f_dentry->d_inode->i_atime; +#endif + } + AuTraceErr(err); + return err; +} +#else +static struct page *aufs_nopage(struct vm_area_struct *vma, unsigned long addr, + int *type) +{ + struct page *page; + struct dentry *dentry; + struct file *file, *h_file; + struct inode *inode; + static DECLARE_WAIT_QUEUE_HEAD(wq); + struct au_finfo *finfo; + + AuTraceEnter(); + AuDebugOn(!vma || !vma->vm_file); + wait_event(wq, (file = au_robr_safe_file(vma))); + AuDebugOn(!au_test_aufs(file->f_dentry->d_sb)); + dentry = file->f_dentry; + LKTRTrace("%.*s, addr %lx\n", AuDLNPair(dentry), addr); + inode = dentry->d_inode; + AuDebugOn(!S_ISREG(inode->i_mode)); + + /* do not revalidate, no si lock */ + finfo = au_fi(file); + h_file = finfo->fi_hfile[0 + finfo->fi_bstart].hf_file; + AuDebugOn(!h_file || !au_test_mmapped(file)); + fi_write_lock(file); + vma->vm_file = h_file; + page = finfo->fi_h_vm_ops->nopage(vma, addr, type); + //file->f_ra = h_file->f_ra; //?? + au_robr_reset_file(vma, file); + fi_write_unlock(file); +#if 0 //def CONFIG_SMP + //wake_up_nr(&wq, online_cpu - 1); + wake_up_all(&wq); +#else + wake_up(&wq); +#endif + if (page && !IS_ERR(page)) { + //AuDbg("%p, %d\n", page, page_mapcount(page)); + //page->mapping = file->f_mapping; + //get_page(page); + //file->f_mapping = h_file->f_mapping; + //touch_atime(NULL, dentry); + //inode->i_atime = h_file->f_dentry->d_inode->i_atime; + } + AuTraceErrPtr(page); + return page; +} + +static int aufs_populate(struct vm_area_struct *vma, unsigned long addr, + unsigned long len, pgprot_t prot, unsigned long pgoff, + int nonblock) +{ + AuUnsupport(); + return au_fi(vma->vm_file)->fi_h_vm_ops->populate + (vma, addr, len, prot, pgoff, nonblock); +} +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) */ + +static struct vm_operations_struct aufs_vm_ops = { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) + .fault = aufs_fault, +#else + .nopage = aufs_nopage, + .populate = aufs_populate, +#endif +#if 0 // rfu + unsigned long (*nopfn)(struct vm_area_struct *area, + unsigned long address); + page_mkwrite(struct vm_area_struct *vma, struct page *page) +#endif +}; + +/* ---------------------------------------------------------------------- */ + +static struct vm_operations_struct *au_vm_ops(struct file *h_file, + struct vm_area_struct *vma) +{ + struct vm_operations_struct *vm_ops; + int err; + + AuTraceEnter(); + + if (!au_test_nfs(h_file->f_vfsmnt->mnt_sb)) + err = h_file->f_op->mmap(h_file, vma); + else { + lockdep_off(); + err = h_file->f_op->mmap(h_file, vma); + lockdep_on(); + } + vm_ops = ERR_PTR(err); + if (unlikely(err)) + goto out; + vm_ops = vma->vm_ops; + err = do_munmap(current->mm, vma->vm_start, + vma->vm_end - vma->vm_start); + if (unlikely(err)) { + AuIOErr("failed internal unmapping %.*s, %d\n", + AuDLNPair(h_file->f_dentry), err); + vm_ops = ERR_PTR(-EIO); + } + + out: + AuTraceErrPtr(vm_ops); + return vm_ops; +} + +static int aufs_mmap(struct file *file, struct vm_area_struct *vma) +{ + int err; + unsigned char wlock, mmapped; + struct dentry *dentry; + struct super_block *sb; + struct file *h_file; + struct vm_operations_struct *vm_ops; + + dentry = file->f_dentry; + LKTRTrace("%.*s, %lx, len %lu\n", + AuDLNPair(dentry), vma->vm_start, + vma->vm_end - vma->vm_start); + AuDebugOn(!S_ISREG(dentry->d_inode->i_mode)); + AuDebugOn(down_write_trylock(&vma->vm_mm->mmap_sem)); + + mmapped = au_test_mmapped(file); /* can be harmless race condition */ + wlock = !!(file->f_mode & FMODE_WRITE); + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, au_reopen_nondir, wlock | !mmapped, + /*locked*/0); + if (unlikely(err)) + goto out; + + if (wlock) { + struct au_pin pin; + + err = au_ready_to_write(file, -1, &pin); + di_downgrade_lock(dentry, AuLock_IR); + if (unlikely(err)) + goto out_unlock; + au_unpin(&pin); + } else if (!mmapped) + di_downgrade_lock(dentry, AuLock_IR); + + h_file = au_h_fptr(file, au_fbstart(file)); + if (au_test_fuse(h_file->f_dentry->d_sb)) { + /* + * by this assignment, f_mapping will differs from aufs inode + * i_mapping. + * if someone else mixes the use of f_dentry->d_inode and + * f_mapping->host, then a problem may arise. + */ + file->f_mapping = h_file->f_mapping; + } + + if (0 && h_file->f_op->mmap == generic_file_mmap) { + err = generic_file_mmap(file, vma); /* instead of h_file */ + if (unlikely(err)) + goto out_unlock; + au_fi(file)->fi_h_vm_ops = vma->vm_ops; + } else { + vm_ops = NULL; + if (!mmapped) { + vm_ops = au_vm_ops(h_file, vma); + err = PTR_ERR(vm_ops); + if (IS_ERR(vm_ops)) + goto out_unlock; + } + + err = generic_file_mmap(file, vma); + if (unlikely(err)) + goto out_unlock; + vma->vm_ops = &aufs_vm_ops; + /* test again */ + if (!au_test_mmapped(file)) { + FiMustWriteLock(file); + au_fi(file)->fi_h_vm_ops = vm_ops; + } + } + + file_accessed(h_file); + au_update_fuse_h_inode(h_file->f_vfsmnt, h_file->f_dentry); /*ignore*/ + dentry->d_inode->i_atime = h_file->f_dentry->d_inode->i_atime; + + out_unlock: + di_read_unlock(dentry, AuLock_IR); + if (!wlock && mmapped) + fi_read_unlock(file); + else + fi_write_unlock(file); + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 22) +// todo: try do_sendfile() in fs/read_write.c +static ssize_t aufs_sendfile(struct file *file, loff_t *ppos, + size_t count, read_actor_t actor, void *target) +{ + ssize_t err; + struct file *h_file; + struct dentry *dentry; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, pos %Ld, cnt %lu\n", + AuDLNPair(dentry), *ppos, (unsigned long)count); + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0, + /*locked*/0); + if (unlikely(err)) + goto out; + + err = -EINVAL; + h_file = au_h_fptr(file, au_fbstart(file)); + if (h_file->f_op && h_file->f_op->sendfile) { + if (/* unlikely */(au_test_loopback())) { + file->f_mapping = h_file->f_mapping; + smp_mb(); /* unnecessary? */ + } + if (!au_test_nfs(h_file->f_vfsmnt->mnt_sb)) + err = h_file->f_op->sendfile(h_file, ppos, count, actor, + target); + else { + lockdep_off(); + err = h_file->f_op->sendfile(h_file, ppos, count, actor, + target); + lockdep_on(); + } + if (!err) + au_update_fuse_h_inode(h_file->f_vfsmnt, + h_file->f_dentry); + /*ignore*/ + dentry->d_inode->i_atime = h_file->f_dentry->d_inode->i_atime; + } + di_read_unlock(dentry, AuLock_IR); + fi_read_unlock(file); + + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} +#endif /* LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 22) */ + +/* ---------------------------------------------------------------------- */ + +/* linux-2.6.22 and earlier */ +#ifndef DEFAULT_POLLMASK +/* copied from linux/fs/select.h, must match */ +#define DEFAULT_POLLMASK (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM) +#endif + +static unsigned int aufs_poll(struct file *file, poll_table *wait) +{ + unsigned int mask; + struct file *h_file; + int err; + struct dentry *dentry; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, wait %p\n", AuDLNPair(dentry), wait); + AuDebugOn(S_ISDIR(dentry->d_inode->i_mode)); + + /* We should pretend an error happened. */ + mask = POLLERR /* | POLLIN | POLLOUT */; + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0, + /*locked*/0); + if (unlikely(err)) + goto out; + + /* it is not an error of hidden_file has no operation */ + mask = DEFAULT_POLLMASK; + h_file = au_h_fptr(file, au_fbstart(file)); + if (h_file->f_op && h_file->f_op->poll) + mask = h_file->f_op->poll(h_file, wait); + di_read_unlock(dentry, AuLock_IR); + fi_read_unlock(file); + + out: + si_read_unlock(sb); + AuTraceErr((int)mask); + return mask; +} + +static int aufs_fsync_nondir(struct file *file, struct dentry *dentry, + int datasync) +{ + int err, my_lock; + struct inode *inode; + struct file *h_file; + struct super_block *sb; + struct au_pin pin; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), datasync); + inode = dentry->d_inode; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17) + IMustLock(file->f_mapping->host); + if (inode != file->f_mapping->host) { + vfsub_i_unlock(file->f_mapping->host); + vfsub_i_lock(inode); + } + IMustLock(inode); + my_lock = 0; +#else + /* before 2.6.17, + * msync(2) calls me without locking i_sem/i_mutex, but fsync(2). + */ + my_lock = vfsub_i_trylock(inode); +#endif + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = 0; /* -EBADF; */ /* posix? */ + if (unlikely(!(file->f_mode & FMODE_WRITE))) + goto out; + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1, + /*locked*/1); + if (unlikely(err)) + goto out; + err = au_ready_to_write(file, -1, &pin); + di_downgrade_lock(dentry, AuLock_IR); + if (unlikely(err)) + goto out_unlock; + au_unpin(&pin); + + err = -EINVAL; + h_file = au_h_fptr(file, au_fbstart(file)); + if (h_file->f_op && h_file->f_op->fsync) { + vfsub_i_lock_nested(h_file->f_dentry->d_inode, AuLsc_I_CHILD); + err = h_file->f_op->fsync(h_file, h_file->f_dentry, datasync); + if (!err) + au_update_fuse_h_inode(h_file->f_vfsmnt, + h_file->f_dentry); + au_cpup_attr_timesizes(inode); + vfsub_i_unlock(h_file->f_dentry->d_inode); + } + + out_unlock: + di_read_unlock(dentry, AuLock_IR); + fi_write_unlock(file); + out: + si_read_unlock(sb); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17) + if (inode != file->f_mapping->host) { + vfsub_i_unlock(inode); + vfsub_i_lock(file->f_mapping->host); + } +#else + if (my_lock) + vfsub_i_unlock(inode); +#endif + AuTraceErr(err); + return err; +} + +static int aufs_fasync(int fd, struct file *file, int flag) +{ + int err; + struct file *h_file; + struct dentry *dentry; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), flag); + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0, + /*locked*/0); + if (unlikely(err)) + goto out; + + h_file = au_h_fptr(file, au_fbstart(file)); + if (h_file->f_op && h_file->f_op->fasync) + err = h_file->f_op->fasync(fd, h_file, flag); + di_read_unlock(dentry, AuLock_IR); + fi_read_unlock(file); + + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct file_operations aufs_file_fop = { + .read = aufs_read, + .write = aufs_write, + .poll = aufs_poll, + .mmap = aufs_mmap, + .open = aufs_open_nondir, + .flush = aufs_flush, + .release = aufs_release_nondir, + .fsync = aufs_fsync_nondir, + .fasync = aufs_fasync, +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 22) + .sendfile = aufs_sendfile, +#endif +#ifdef CONFIG_AUFS_SPLICE_PATCH + .splice_write = aufs_splice_write, + .splice_read = aufs_splice_read, +#endif +}; diff --git a/fs/aufs/file.c b/fs/aufs/file.c new file mode 100644 index 0000000..1ad2fd8 --- /dev/null +++ b/fs/aufs/file.c @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * handling file/dir, and address_space operation + * + * $Id: file.c,v 1.83 2009/01/26 06:23:56 sfjro Exp $ + */ + +//#include +#include +//#include +//#include +#include "aufs.h" + +/* + * a dirty trick for handling FMODE_EXEC and deny_write_access(). + * because FMODE_EXEC flag is not passed to f_op->open(), + * set it to file->private_data temporary. + */ +#if !defined(CONFIG_AUFS_MODULE) || defined(CONFIG_AUFS_DENY_WRITE_ACCESS_PATCH) +int au_store_fmode_exec(struct nameidata *nd, struct inode *inode) +{ + int err; + union { + void *p; + unsigned long ul; + } u; + + err = 0; + if (nd + && (nd->flags & LOOKUP_OPEN) + && nd->intent.open.file + && (nd->intent.open.flags & FMODE_EXEC) + && inode + && S_ISREG(inode->i_mode)) { + u.ul = nd->intent.open.flags; + nd->intent.open.file->private_data = u.p; + /* smp_mb(); */ + err = 1; + } + + return err; +} +#endif + +/* drop flags for writing */ +unsigned int au_file_roflags(unsigned int flags) +{ + flags &= ~(O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC); + flags |= O_RDONLY | O_NOATIME; + return flags; +} + +/* common functions to regular file and dir */ +struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags, + struct file *file) +{ + struct file *h_file; + struct dentry *h_dentry; + struct inode *h_inode; + struct super_block *sb; + struct au_branch *br; + int err; + + LKTRTrace("%.*s, b%d, flags 0%o, f %d\n", + AuDLNPair(dentry), bindex, flags, !!file); + h_dentry = au_h_dptr(dentry, bindex); + AuDebugOn(!h_dentry); + h_inode = h_dentry->d_inode; + + /* a race condition can happen between open and unlink/rmdir */ + h_file = ERR_PTR(-ENOENT); + if (unlikely((!d_unhashed(dentry) && d_unhashed(h_dentry)) + || !h_inode)) + goto out; + + sb = dentry->d_sb; + br = au_sbr(sb, bindex); + au_br_get(br); + /* drop flags for writing */ + if (au_test_ro(sb, bindex, dentry->d_inode)) + flags = au_file_roflags(flags); + flags &= ~O_CREAT; + + h_file = NULL; + if (file && au_test_nfs(h_dentry->d_sb)) + h_file = au_h_intent(dentry, bindex, file); + if (!h_file) + h_file = dentry_open(dget(h_dentry), mntget(br->br_mnt), flags); + + /* + * a dirty trick for handling FMODE_EXEC and deny_write_access(). + */ + if (file && (file->f_mode & FMODE_EXEC)) { + h_file->f_mode |= FMODE_EXEC; + smp_mb(); /* flush f_mode */ + err = au_deny_write_access(h_file); + if (unlikely(err)) { + fput(h_file); + h_file = ERR_PTR(err); + } + } + if (IS_ERR(h_file)) + au_br_put(br); + +out: + AuTraceErrPtr(h_file); + return h_file; +} + +static int do_coo(struct dentry *dentry, aufs_bindex_t bstart) +{ + int err; + aufs_bindex_t bcpup; + unsigned char pin_flags; + struct au_pin pin; + struct dentry *parent; + struct inode *h_inode; + struct super_block *sb; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + AuDebugOn(IS_ROOT(dentry)); + DiMustWriteLock(dentry); + + parent = dget_parent(dentry); + di_write_lock_parent(parent); + sb = dentry->d_sb; + err = AuWbrCopyup(au_sbi(sb), dentry); + bcpup = err; + if (err < 0) { + err = 0; /* stop copyup, it is not an error */ + goto out_dgrade; + } + err = 0; + + if (!au_h_dptr(parent, bcpup)) { + err = au_cpup_dirs(dentry, bcpup); + if (unlikely(err)) + goto out_dgrade; + } + + di_downgrade_lock(parent, AuLock_IR); + pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) + au_fset_pin(pin_flags, DO_GPARENT); + err = au_pin(&pin, dentry, bcpup, pin_flags); + if (unlikely(err)) + goto out; + h_inode = au_h_dptr(dentry, bstart)->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + AuDebugOn(au_h_dptr(dentry, bcpup)); + err = au_sio_cpup_simple(dentry, bcpup, -1, AuCpup_DTIME); + AuTraceErr(err); + vfsub_i_unlock(h_inode); + au_unpin(&pin); + goto out; + + out_dgrade: + di_downgrade_lock(parent, AuLock_IR); + out: + di_read_unlock(parent, AuLock_IR); + dput(parent); + AuTraceErr(err); + return err; +} + +int au_do_open(struct inode *inode, struct file *file, + int (*open)(struct file *file, int flags)) +{ + int err; + struct dentry *dentry; + struct super_block *sb; + aufs_bindex_t bstart; + unsigned char coo; + + dentry = file->f_dentry; + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(dentry)); + + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + coo = 0; + if (!(sb->s_flags & MS_RDONLY)) + switch (au_mntflags(sb) & AuOptMask_COO) { + case AuOpt_COO_LEAF: + coo = !S_ISDIR(inode->i_mode); + break; + case AuOpt_COO_ALL: + coo = 1; + break; + } + err = au_finfo_init(file); + if (unlikely(err)) + goto out; + + if (!coo) + di_read_lock_child(dentry, AuLock_IR); + else { + di_write_lock_child(dentry); + bstart = au_dbstart(dentry); + if (au_test_ro(sb, bstart, dentry->d_inode)) { + err = do_coo(dentry, bstart); + if (err) { + di_write_unlock(dentry); + goto out_finfo; + } + } + di_downgrade_lock(dentry, AuLock_IR); + } + + err = open(file, file->f_flags); + di_read_unlock(dentry, AuLock_IR); + + out_finfo: + fi_write_unlock(file); + if (unlikely(err)) + au_finfo_fin(file); + out: + si_read_unlock(sb); + AuTraceErr(err); + return err; +} + +int au_reopen_nondir(struct file *file) +{ + int err; + aufs_bindex_t bstart, bindex, bend; + struct dentry *dentry; + struct file *h_file, *h_file_tmp; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + bstart = au_dbstart(dentry); + //bstart = au_ibstart(inode); + AuDebugOn(S_ISDIR(dentry->d_inode->i_mode) + || !au_h_dptr(dentry, bstart)->d_inode); + + h_file_tmp = NULL; + if (au_fbstart(file) == bstart) { + h_file = au_h_fptr(file, bstart); + if (file->f_mode == h_file->f_mode) + return 0; /* success */ + h_file_tmp = h_file; + get_file(h_file_tmp); + au_set_h_fptr(file, bstart, NULL); + } + AuDebugOn(au_fbstart(file) < bstart + || au_fi(file)->fi_hfile[0 + bstart].hf_file); + + h_file = au_h_open(dentry, bstart, file->f_flags & ~O_TRUNC, file); + err = PTR_ERR(h_file); + if (IS_ERR(h_file)) + goto out; /* todo: close all? */ + err = 0; + /* cpup_file_flags(h_file, file); */ + au_set_fbstart(file, bstart); + au_set_h_fptr(file, bstart, h_file); + au_update_figen(file); + /* todo: necessary? */ + /* file->f_ra = h_file->f_ra; */ + + /* close lower files */ + bend = au_fbend(file); + for (bindex = bstart + 1; bindex <= bend; bindex++) + au_set_h_fptr(file, bindex, NULL); + au_set_fbend(file, bstart); + + out: + if (h_file_tmp) + fput(h_file_tmp); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int au_ready_to_write_wh(struct file *file, loff_t len, + aufs_bindex_t bcpup) +{ + int err; + aufs_bindex_t old_bstart; + struct inode *inode; + struct dentry *dentry, *hi_wh, *old_h_dentry; + struct au_dinfo *dinfo; + struct super_block *sb; + + AuTraceEnter(); + + dentry = file->f_dentry; + inode = dentry->d_inode; + hi_wh = au_hi_wh(inode, bcpup); + if (!hi_wh) + err = au_sio_cpup_wh(dentry, bcpup, len, file); + else { + /* already copied-up after unlink */ + dinfo = au_di(dentry); + old_bstart = dinfo->di_bstart; + dinfo->di_bstart = bcpup; + old_h_dentry = dinfo->di_hdentry[0 + bcpup].hd_dentry; + dinfo->di_hdentry[0 + bcpup].hd_dentry = hi_wh; + err = au_reopen_nondir(file); + dinfo->di_hdentry[0 + bcpup].hd_dentry = old_h_dentry; + dinfo->di_bstart = old_bstart; + } + + sb = dentry->d_sb; + if (!err && inode->i_nlink > 1 && au_opt_test(au_mntflags(sb), PLINK)) + au_plink_append(sb, inode, au_h_dptr(dentry, bcpup), bcpup); + + AuTraceErr(err); + return err; +} + +/* + * prepare the @file for writing. + */ +int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin) +{ + int err; + unsigned char pin_flags; + aufs_bindex_t bstart, bcpup; + struct dentry *dentry, *parent, *h_dentry; + struct inode *h_inode, *inode; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, len %lld\n", AuDLNPair(dentry), len); + FiMustWriteLock(file); + + sb = dentry->d_sb; + bstart = au_fbstart(file); + AuDebugOn(au_fbr(file, bstart) != au_sbr(sb, bstart)); + + inode = dentry->d_inode; + AuDebugOn(S_ISDIR(inode->i_mode)); + LKTRTrace("rdonly %d, bstart %d\n", + au_test_ro(sb, bstart, inode), bstart); + + err = au_test_ro(sb, bstart, inode); + if (!err && (au_h_fptr(file, bstart)->f_mode & FMODE_WRITE)) { + err = au_pin(pin, dentry, bstart, /*flags*/0); + goto out; + } + + /* need to cpup */ + parent = dget_parent(dentry); + di_write_lock_parent(parent); + err = AuWbrCopyup(au_sbi(sb), dentry); + bcpup = err; + if (unlikely(err < 0)) + goto out_dgrade; + err = 0; + + if (!au_h_dptr(parent, bcpup)) { + err = au_cpup_dirs(dentry, bcpup); + if (unlikely(err)) + goto out_dgrade; + } + + pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) + au_fset_pin(pin_flags, DO_GPARENT); + err = au_pin(pin, dentry, bcpup, pin_flags); + if (unlikely(err)) + goto out_dgrade; + + AuDebugOn(au_fbstart(file) != bstart); + h_dentry = au_h_fptr(file, bstart)->f_dentry; + h_inode = h_dentry->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + if (d_unhashed(dentry) /* || d_unhashed(h_dentry) */ + /* || !h_inode->i_nlink */) { + err = au_ready_to_write_wh(file, len, bcpup); + di_downgrade_lock(parent, AuLock_IR); + } else { + di_downgrade_lock(parent, AuLock_IR); + if (!au_h_dptr(dentry, bcpup)) + err = au_sio_cpup_simple(dentry, bcpup, len, + AuCpup_DTIME); + AuTraceErr(err); + if (!err) + err = au_reopen_nondir(file); + AuTraceErr(err); + } + vfsub_i_unlock(h_inode); + + if (!err) { + au_unpin_gp(pin); + au_pin_set_parent_lflag(pin, /*lflag*/0); + goto out_dput; /* success */ + } + au_unpin(pin); + goto out_unlock; + + out_dgrade: + di_downgrade_lock(parent, AuLock_IR); + out_unlock: + di_read_unlock(parent, AuLock_IR); + out_dput: + dput(parent); + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int au_file_refresh_by_inode(struct file *file, int *need_reopen) +{ + int err; + unsigned int mnt_flags; + unsigned char pin_flags; + aufs_bindex_t bstart, new_bstart, old_bstart; + struct au_pin pin; + struct au_finfo *finfo; + struct dentry *dentry, *parent, *old_h_dentry, *hi_wh; + struct inode *inode, *dir; + struct super_block *sb; + struct au_dinfo *dinfo; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + FiMustWriteLock(file); + + err = 0; + finfo = au_fi(file); + inode = dentry->d_inode; + sb = dentry->d_sb; + mnt_flags = au_mntflags(sb); + pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (au_opt_test(mnt_flags, UDBA_INOTIFY)) + au_fset_pin(pin_flags, DO_GPARENT); + again: + bstart = au_ibstart(inode); + if (bstart == finfo->fi_bstart) + goto out; + + new_bstart = bstart; + parent = dget_parent(dentry); + dir = parent->d_inode; + if (au_test_ro(sb, bstart, inode)) { + di_read_lock_parent(parent, !AuLock_IR); + err = AuWbrCopyup(au_sbi(sb), dentry); + new_bstart = err; + di_read_unlock(parent, !AuLock_IR); + if (unlikely(err < 0)) + goto out_dput; + err = 0; + } + /* someone else might change our inode while we were sleeping */ + /* todo: test more? */ + if (bstart != au_ibstart(inode)) { + err = 0; + dput(parent); + goto again; + } + di_read_lock_parent(parent, AuLock_IR); + bstart = new_bstart; + + hi_wh = au_hi_wh(inode, bstart); + if (au_opt_test(mnt_flags, PLINK) + && au_plink_test(sb, inode) + && !d_unhashed(dentry)) { + err = au_test_and_cpup_dirs(dentry, bstart); + if (unlikely(err)) + goto out_unlock; + + /* always superio. */ +#if 1 + err = au_pin(&pin, dentry, bstart, pin_flags); + if (!err) + err = au_sio_cpup_simple(dentry, bstart, -1, + AuCpup_DTIME); + au_unpin(&pin); +#else /* reserved for future use */ + if (!au_test_wkq(current)) { + int wkq_err; + struct cpup_pseudo_link_args args = { + .errp = &err, + .dentry = dentry, + .bdst = bstart, + .do_lock = 1 + }; + wkq_err = au_wkq_wait(call_cpup_pseudo_link, &args); + if (unlikely(wkq_err)) + err = wkq_err; + } else + err = cpup_pseudo_link(dentry, bstart, /*do_lock*/1); +#endif + } else if (hi_wh) { + /* already copied-up after unlink */ + dinfo = au_di(dentry); + old_bstart = dinfo->di_bstart; + dinfo->di_bstart = bstart; + old_h_dentry = dinfo->di_hdentry[0 + bstart].hd_dentry; + dinfo->di_hdentry[0 + bstart].hd_dentry = hi_wh; + err = au_reopen_nondir(file); + dinfo->di_hdentry[0 + bstart].hd_dentry = old_h_dentry; + dinfo->di_bstart = old_bstart; + *need_reopen = 0; + } + + out_unlock: + di_read_unlock(parent, AuLock_IR); + out_dput: + dput(parent); + out: + AuTraceErr(err); + return err; +} + +/* + * after branch manipulating, refresh the file. + */ +static int refresh_file(struct file *file, int (*reopen)(struct file *file)) +{ + int err, new_sz, need_reopen; + struct dentry *dentry; + aufs_bindex_t bend, bindex, brid; + struct au_hfile *p; + struct au_finfo *finfo; + struct super_block *sb; + struct inode *inode; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + FiMustWriteLock(file); + DiMustAnyLock(dentry); + inode = dentry->d_inode; + IiMustAnyLock(inode); + + err = -ENOMEM; + sb = dentry->d_sb; + finfo = au_fi(file); + new_sz = sizeof(*finfo->fi_hfile) * (au_sbend(sb) + 1); + p = au_kzrealloc(finfo->fi_hfile, sizeof(*p) * (finfo->fi_bend + 1), + new_sz, GFP_NOFS); + if (unlikely(!p)) + goto out; + finfo->fi_hfile = p; + + p += finfo->fi_bstart; + brid = p->hf_br->br_id; + bend = finfo->fi_bend; + for (bindex = finfo->fi_bstart; bindex <= bend; bindex++, p++) { + struct au_hfile tmp, *q; + aufs_bindex_t new_bindex; + + if (!p->hf_file) + continue; + new_bindex = au_find_bindex(sb, p->hf_br); + if (new_bindex == bindex) + continue; + /* todo: test more? */ + if (new_bindex < 0) { + au_set_h_fptr(file, bindex, NULL); + continue; + } + + /* swap two hidden inode, and loop again */ + q = finfo->fi_hfile + new_bindex; + tmp = *q; + *q = *p; + *p = tmp; + if (tmp.hf_file) { + bindex--; + p--; + } + } + { + aufs_bindex_t s = finfo->fi_bstart, e = finfo->fi_bend; + finfo->fi_bstart = 0; + finfo->fi_bend = au_sbend(sb); + finfo->fi_bstart = s; + finfo->fi_bend = e; + } + + p = finfo->fi_hfile; + if (!au_test_mmapped(file) && !d_unhashed(dentry)) { + bend = au_sbend(sb); + for (finfo->fi_bstart = 0; finfo->fi_bstart <= bend; + finfo->fi_bstart++, p++) + if (p->hf_file) { + if (p->hf_file->f_dentry + && p->hf_file->f_dentry->d_inode) + break; + else + au_hfput(p); + } + } else { + bend = au_br_index(sb, brid); + for (finfo->fi_bstart = 0; finfo->fi_bstart < bend; + finfo->fi_bstart++, p++) + if (p->hf_file) + au_hfput(p); + bend = au_sbend(sb); + } + + p = finfo->fi_hfile + bend; + for (finfo->fi_bend = bend; finfo->fi_bend >= finfo->fi_bstart; + finfo->fi_bend--, p--) + if (p->hf_file) { + if (p->hf_file->f_dentry + && p->hf_file->f_dentry->d_inode) + break; + else + au_hfput(p); + } + AuDebugOn(finfo->fi_bend < finfo->fi_bstart); + + err = 0; + need_reopen = 1; + if (!au_test_mmapped(file)) + err = au_file_refresh_by_inode(file, &need_reopen); + if (!err && need_reopen && !d_unhashed(dentry)) + err = reopen(file); + if (!err) { + au_update_figen(file); + return 0; /* success */ + } + + /* error, close all hidden files */ + bend = au_fbend(file); + for (bindex = au_fbstart(file); bindex <= bend; bindex++) + au_set_h_fptr(file, bindex, NULL); + + out: + AuTraceErr(err); + return err; +} + +/* common function to regular file and dir */ +int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file), + int wlock, int locked) +{ + int err; + struct dentry *dentry; + struct super_block *sb; + aufs_bindex_t bstart; + unsigned char pseudo_link; + au_gen_t sgen, fgen; + + dentry = file->f_dentry; + LKTRTrace("%.*s, w %d, l %d\n", AuDLNPair(dentry), wlock, locked); + sb = dentry->d_sb; + SiMustAnyLock(sb); + + err = 0; + sgen = au_sigen(sb); + fi_write_lock(file); + fgen = au_figen(file); + di_write_lock_child(dentry); + bstart = au_dbstart(dentry); + pseudo_link = (bstart != au_ibstart(dentry->d_inode)); + if (sgen == fgen && !pseudo_link && au_fbstart(file) == bstart) { + if (!wlock) { + di_downgrade_lock(dentry, AuLock_IR); + fi_downgrade_lock(file); + } + goto out; /* success */ + } + + LKTRTrace("sgen %d, fgen %d\n", sgen, fgen); + if (sgen != au_digen(dentry) + || sgen != au_iigen(dentry->d_inode)) { + /* + * d_path() and path_lookup() is a simple and good approach + * to revalidate. but si_rwsem in DEBUG_RWSEM will cause a + * deadlock. removed the code. + */ + err = au_reval_dpath(dentry, sgen); + if (unlikely(err < 0)) + goto out; + AuDebugOn(au_digen(dentry) != sgen + || au_iigen(dentry->d_inode) != sgen); + } + + err = refresh_file(file, reopen + /* , au_opt_test(au_mnt_flags(sb), REFROF) */); + if (!err) { + if (!wlock) { + di_downgrade_lock(dentry, AuLock_IR); + fi_downgrade_lock(file); + } + } else { + di_write_unlock(dentry); + fi_write_unlock(file); + } + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* cf. aufs_nopage() */ +/* for madvise(2) */ +static int aufs_readpage(struct file *file, struct page *page) +{ + AuTraceEnter(); + unlock_page(page); + return 0; +} + +/* they will never be called. */ +#ifdef CONFIG_AUFS_DEBUG +static int aufs_prepare_write(struct file *file, struct page *page, + unsigned from, unsigned to) +{ AuUnsupport(); return 0; } +static int aufs_commit_write(struct file *file, struct page *page, + unsigned from, unsigned to) +{ AuUnsupport(); return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) +static int aufs_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned len, unsigned flags, + struct page **pagep, void **fsdata) +{ AuUnsupport(); return 0; } +static int aufs_write_end(struct file *file, struct address_space *mapping, + loff_t pos, unsigned len, unsigned copied, + struct page *page, void *fsdata) +{ AuUnsupport(); return 0; } +#endif +static int aufs_writepage(struct page *page, struct writeback_control *wbc) +{ AuUnsupport(); return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17) +static void aufs_sync_page(struct page *page) +{ AuUnsupport(); } +#else +static int aufs_sync_page(struct page *page) +{ AuUnsupport(); return 0; } +#endif + +#if 0 // comment +static int aufs_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ AuUnsupport(); return 0; } +static int aufs_readpages(struct file *filp, struct address_space *mapping, + struct list_head *pages, unsigned nr_pages) +{ AuUnsupport(); return 0; } +static sector_t aufs_bmap(struct address_space *mapping, sector_t block) +{ AuUnsupport(); return 0; } +#endif + +static int aufs_set_page_dirty(struct page *page) +{ AuUnsupport(); return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17) +static void aufs_invalidatepage(struct page *page, unsigned long offset) +{ AuUnsupport(); } +#else +static int aufs_invalidatepage(struct page *page, unsigned long offset) +{ AuUnsupport(); return 0; } +#endif +static int aufs_releasepage(struct page *page, gfp_t gfp) +{ AuUnsupport(); return 0; } +static ssize_t aufs_direct_IO(int rw, struct kiocb *iocb, + const struct iovec *iov, loff_t offset, + unsigned long nr_segs) +{ AuUnsupport(); return 0; } +static struct page *aufs_get_xip_page(struct address_space *mapping, + sector_t offset, int create) +{ AuUnsupport(); return NULL; } +//static int aufs_migratepage (struct page *newpage, struct page *page) +//{ AuUnsupport(); return 0; } +#if 0 //LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +int (*launder_page) (struct page *); +#endif +#endif /* CONFIG_AUFS_DEBUG */ + +struct address_space_operations aufs_aop = { + .readpage = aufs_readpage, +#ifdef CONFIG_AUFS_DEBUG + .writepage = aufs_writepage, + .sync_page = aufs_sync_page, + //.writepages = aufs_writepages, + .set_page_dirty = aufs_set_page_dirty, + //.readpages = aufs_readpages, + .prepare_write = aufs_prepare_write, + .commit_write = aufs_commit_write, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + .write_begin = aufs_write_begin, + .write_end = aufs_write_end, +#endif + //.bmap = aufs_bmap, + .invalidatepage = aufs_invalidatepage, + .releasepage = aufs_releasepage, + .direct_IO = aufs_direct_IO, + .get_xip_page = aufs_get_xip_page, + //.migratepage = aufs_migratepage +#endif /* CONFIG_AUFS_DEBUG */ +}; diff --git a/fs/aufs/file.h b/fs/aufs/file.h new file mode 100644 index 0000000..a974284 --- /dev/null +++ b/fs/aufs/file.h @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * file operations + * + * $Id: file.h,v 1.40 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_FILE_H__ +#define __AUFS_FILE_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include +#include +#include "misc.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +/* SEEK_xxx are defined in linux/fs.h */ +#else +enum { + SEEK_SET, SEEK_CUR, SEEK_END +}; +#endif + +/* ---------------------------------------------------------------------- */ + +struct au_branch; +struct au_hfile { + struct file *hf_file; + struct au_branch *hf_br; +}; + +struct au_vdir; +struct au_finfo { + atomic_t fi_generation; + + struct au_rwsem fi_rwsem; + struct au_hfile *fi_hfile; + aufs_bindex_t fi_bstart, fi_bend; + + union { + struct vm_operations_struct *fi_h_vm_ops; + struct au_vdir *fi_vdir_cache; + }; +}; + +/* ---------------------------------------------------------------------- */ + +/* file.c */ +extern struct address_space_operations aufs_aop; +unsigned int au_file_roflags(unsigned int flags); +struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags, + struct file *file); +int au_do_open(struct inode *inode, struct file *file, + int (*open)(struct file *file, int flags)); +int au_reopen_nondir(struct file *file); +struct au_pin; +int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin); +int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file), + int wlock, int locked); + +/* f_op.c */ +extern struct file_operations aufs_file_fop; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +int aufs_flush(struct file *file, fl_owner_t id); +#else +int aufs_flush(struct file *file); +#endif + +/* finfo.c */ +struct au_finfo *au_fi(struct file *file); +struct au_branch *au_fbr(struct file *file, aufs_bindex_t bindex); +struct file *au_h_fptr(struct file *file, aufs_bindex_t bindex); + +void au_hfput(struct au_hfile *hf); +void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, + struct file *h_file); + +void au_finfo_fin(struct file *file); +int au_finfo_init(struct file *file); + +#ifdef CONFIG_AUFS_ROBR +/* robr.c */ +struct file *au_robr_safe_file(struct vm_area_struct *vma); +void au_robr_reset_file(struct vm_area_struct *vma, struct file *file); +#else +static inline struct file *au_robr_safe_file(struct vm_area_struct *vma) +{ + struct file *file; + + file = vma->vm_file; + if (file->private_data && au_test_aufs(file->f_dentry->d_sb)) + return file; + return NULL; +} + +static inline +void au_robr_reset_file(struct vm_area_struct *vma, struct file *file) +{ + vma->vm_file = file; + /* smp_mb(); */ /* flush vm_file */ +} +#endif /* CONFIG_AUFS_ROBR */ + +/* ---------------------------------------------------------------------- */ + +/* todo: memory barrier? */ +static inline au_gen_t au_figen(struct file *f) +{ + return atomic_read(&au_fi(f)->fi_generation); +} + +static inline int au_test_mmapped(struct file *f) +{ + return !!(au_fi(f)->fi_h_vm_ops); +} + +static inline int au_test_aufs_file(struct file *f) +{ + return !(f->f_dentry->d_inode->i_mode + & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)); +} + +/* ---------------------------------------------------------------------- */ + +#if !defined(CONFIG_AUFS_MODULE) || defined(CONFIG_AUFS_DENY_WRITE_ACCESS_PATCH) +int au_store_fmode_exec(struct nameidata *nd, struct inode *inode); + +static inline int au_deny_write_access(struct file *h_file) +{ + LKTRTrace("%.*s\n", AuDLNPair(h_file->f_dentry)); + return deny_write_access(h_file); +} + +static inline void au_allow_write_access(struct file *h_file) +{ + allow_write_access(h_file); +} + +#else + +static inline int au_store_fmode_exec(struct nameidata *nd, struct inode *inode) +{ + /* nothing */ + return 0; +} + +static inline int au_deny_write_access(struct file *h_file) +{ + /* nothing */ + return 0; +} + +static inline void au_allow_write_access(struct file *h_file) +{ + /* nothing */ +} +#endif /* CONFIG_AUFS_DENY_WRITE_ACCESS_PATCH */ + +/* ---------------------------------------------------------------------- */ + +/* + * fi_read_lock, fi_write_lock, + * fi_read_unlock, fi_write_unlock, fi_downgrade_lock + */ +AuSimpleRwsemFuncs(fi, struct file *f, au_fi(f)->fi_rwsem); + +/* to debug easier, do not make them inlined functions */ +#define FiMustReadLock(f) do { \ + SiMustAnyLock((f)->f_dentry->d_sb); \ + AuRwMustReadLock(&au_fi(f)->fi_rwsem); \ +} while (0) + +#define FiMustWriteLock(f) do { \ + SiMustAnyLock((f)->f_dentry->d_sb); \ + AuRwMustWriteLock(&au_fi(f)->fi_rwsem); \ +} while (0) + +#define FiMustAnyLock(f) do { \ + SiMustAnyLock((f)->f_dentry->d_sb); \ + AuRwMustAnyLock(&au_fi(f)->fi_rwsem); \ +} while (0) + +#define FiMustNoWaiters(f) AuRwMustNoWaiters(&au_fi(f)->fi_rwsem) + +/* ---------------------------------------------------------------------- */ + +/* todo: hard/soft set? */ +static inline aufs_bindex_t au_fbstart(struct file *file) +{ + FiMustAnyLock(file); + return au_fi(file)->fi_bstart; +} + +static inline aufs_bindex_t au_fbend(struct file *file) +{ + FiMustAnyLock(file); + return au_fi(file)->fi_bend; +} + +static inline struct au_vdir *au_fvdir_cache(struct file *file) +{ +#ifdef CONFIG_AUFS_EXPORT + AuRwMustAnyLock(&au_fi(file)->fi_rwsem); +#else + FiMustAnyLock(file); +#endif + return au_fi(file)->fi_vdir_cache; +} + +static inline void au_set_fbstart(struct file *file, aufs_bindex_t bindex) +{ + FiMustWriteLock(file); + AuDebugOn(au_sbend(file->f_dentry->d_sb) < bindex); + au_fi(file)->fi_bstart = bindex; +} + +static inline void au_set_fbend(struct file *file, aufs_bindex_t bindex) +{ + FiMustWriteLock(file); + AuDebugOn(au_sbend(file->f_dentry->d_sb) < bindex + || bindex < au_fbstart(file)); + au_fi(file)->fi_bend = bindex; +} + +static inline void au_set_fvdir_cache(struct file *file, + struct au_vdir *vdir_cache) +{ + FiMustWriteLock(file); + AuDebugOn(!S_ISDIR(file->f_dentry->d_inode->i_mode) + || (au_fi(file)->fi_vdir_cache && vdir_cache)); + au_fi(file)->fi_vdir_cache = vdir_cache; +} + +static inline void au_update_figen(struct file *file) +{ + atomic_set(&au_fi(file)->fi_generation, au_digen(file->f_dentry)); + /* smp_mb(); */ /* atomic_set */ +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_FILE_H__ */ diff --git a/fs/aufs/finfo.c b/fs/aufs/finfo.c new file mode 100644 index 0000000..928c0f4 --- /dev/null +++ b/fs/aufs/finfo.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * file private data + * + * $Id: finfo.c,v 1.37 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +struct au_finfo *au_fi(struct file *file) +{ + struct au_finfo *finfo = file->private_data; + AuDebugOn(!finfo + || !finfo->fi_hfile + || (0 < finfo->fi_bend + && (/* au_sbi(file->f_dentry->d_sb)->si_bend + < finfo->fi_bend + || */ finfo->fi_bend < finfo->fi_bstart))); + return finfo; +} + +struct au_branch *au_fbr(struct file *file, aufs_bindex_t bindex) +{ + struct au_finfo *finfo = au_fi(file); + struct au_hfile *hf; + + FiMustAnyLock(file); + AuDebugOn(!finfo + || finfo->fi_bstart < 0 + || bindex < finfo->fi_bstart + || finfo->fi_bend < bindex); + hf = finfo->fi_hfile + bindex; + AuDebugOn(hf->hf_br && au_br_count(hf->hf_br) <= 0); + return hf->hf_br; +} + +struct file *au_h_fptr(struct file *file, aufs_bindex_t bindex) +{ + struct au_finfo *finfo = au_fi(file); + struct au_hfile *hf; + + FiMustAnyLock(file); + AuDebugOn(!finfo + || finfo->fi_bstart < 0 + || bindex < finfo->fi_bstart + || finfo->fi_bend < bindex); + hf = finfo->fi_hfile + bindex; + AuDebugOn(hf->hf_file + && file_count(hf->hf_file) <= 0 + && au_br_count(hf->hf_br) <= 0); + return hf->hf_file; +} + +void au_hfput(struct au_hfile *hf) +{ + if (hf->hf_file->f_mode & FMODE_EXEC) + au_allow_write_access(hf->hf_file); + fput(hf->hf_file); + hf->hf_file = NULL; + AuDebugOn(!hf->hf_br); + au_br_put(hf->hf_br); + hf->hf_br = NULL; +} + +void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *val) +{ + struct au_finfo *finfo = au_fi(file); + struct au_hfile *hf; + + FiMustWriteLock(file); + AuDebugOn(!finfo + || finfo->fi_bstart < 0 + || bindex < finfo->fi_bstart + || finfo->fi_bend < bindex); + AuDebugOn(val && file_count(val) <= 0); + hf = finfo->fi_hfile + bindex; + AuDebugOn(val && hf->hf_file); + if (hf->hf_file) + au_hfput(hf); + if (val) { + hf->hf_file = val; + hf->hf_br = au_sbr(file->f_dentry->d_sb, bindex); + } +} + +void au_finfo_fin(struct file *file) +{ + struct au_finfo *finfo; + struct dentry *dentry; + aufs_bindex_t bindex, bend; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + SiMustAnyLock(dentry->d_sb); + + fi_write_lock(file); + bend = au_fbend(file); + bindex = au_fbstart(file); + if (bindex >= 0) + for (; bindex <= bend; bindex++) + au_set_h_fptr(file, bindex, NULL); + + finfo = au_fi(file); +#ifdef CONFIG_AUFS_DEBUG + if (finfo->fi_bstart >= 0) { + bend = au_fbend(file); + for (bindex = finfo->fi_bstart; bindex <= bend; bindex++) { + struct au_hfile *hf; + hf = finfo->fi_hfile + bindex; + AuDebugOn(hf->hf_file || hf->hf_br); + } + } +#endif + + kfree(finfo->fi_hfile); + fi_write_unlock(file); + au_rwsem_destroy(&finfo->fi_rwsem); + au_cache_free_finfo(finfo); +} + +int au_finfo_init(struct file *file) +{ + struct au_finfo *finfo; + struct dentry *dentry; + union { + void *p; + unsigned long ul; + } u; + + dentry = file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + AuDebugOn(!dentry->d_inode); + + finfo = au_cache_alloc_finfo(); + if (finfo) { + finfo->fi_hfile = kcalloc(au_sbend(dentry->d_sb) + 1, + sizeof(*finfo->fi_hfile), GFP_NOFS); + if (finfo->fi_hfile) { + au_rw_init_wlock(&finfo->fi_rwsem); + //au_dbg_lock_fi_reg(file); + finfo->fi_bstart = -1; + finfo->fi_bend = -1; + atomic_set(&finfo->fi_generation, au_digen(dentry)); + /* smp_mb(); */ /* atomic_set */ + + /* + * a dirty trick for handling FMODE_EXEC and + * deny_write_access(). + * because FMODE_EXEC flag is not passed to + * f_op->open(), + * aufs set it to file->private_data temporary in lookup + * or dentry revalidation operations. + * restore the flag to f_mode here. + */ + u.p = file->private_data; + if (u.ul & FMODE_EXEC) { + file->f_mode |= FMODE_EXEC; + smp_mb(); /* flush f_mode */ + } + + file->private_data = finfo; + return 0; /* success */ + } + au_cache_free_finfo(finfo); + } + + AuTraceErr(-ENOMEM); + return -ENOMEM; +} diff --git a/fs/aufs/getattr.c b/fs/aufs/getattr.c new file mode 100644 index 0000000..d424a09 --- /dev/null +++ b/fs/aufs/getattr.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode attributes on NFS/FUSE branch or HINOTIFY + * + * $Id: getattr.c,v 1.5 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +static struct dentry * +au_h_dget_any(struct dentry *dentry, aufs_bindex_t *bindex) +{ + struct dentry *h_dentry; + struct inode *inode, *h_inode; + struct super_block *sb; + aufs_bindex_t ib, db; + + /* must be positive dentry */ + inode = dentry->d_inode; + LKTRTrace("%.*s, i%lu\n", AuDLNPair(dentry), inode->i_ino); + + sb = dentry->d_sb; + db = au_dbstart(dentry); + ib = au_ibstart(inode); + if (db == ib) { + *bindex = db; + h_dentry = dget(au_h_dptr(dentry, db)); + if (h_dentry) + goto out; /* success */ + } + + *bindex = ib; + h_inode = au_h_iptr(inode, ib); + h_dentry = d_find_alias(h_inode); + if (h_dentry) + goto out; /* success */ + +#if 0 + if (au_opt_test(au_mntflags(sb), PLINK) + && au_plink_test(sb, inode)) { + h_dentry = au_plink_lkup(sb, ib, inode); + if (IS_ERR(h_dentry)) + goto out; + AuDebugOn(!h_dentry->d_inode); + goto out; /* success */ + } +#endif + + h_dentry = dget(au_hi_wh(inode, ib)); + + out: + AuTraceErrPtr(h_dentry); + return h_dentry; +} + +int aufs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st) +{ + int err; + unsigned int mnt_flags; + aufs_bindex_t bindex; + unsigned char did_lock; + struct inode *inode; + struct dentry *h_dentry; + struct super_block *sb, *h_sb; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + err = 0; + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + mnt_flags = au_mntflags(sb); + if (dentry != sb->s_root) { + di_read_lock_parent(dentry, AuLock_IR); + inode = dentry->d_inode; + did_lock = 1; + + /* todo: test bit inotify option too? */ + bindex = au_ibstart(inode); + h_sb = au_sbr_sb(sb, bindex); + /* todo: fix this condition */ + if ((au_opt_test(mnt_flags, PLINK) && au_plink_test(sb, inode)) + /* au_iigen(inode) == au_sigen(sb) */ + || (!au_test_fuse(h_sb) && !au_test_nfs(h_sb))) + goto fill; + + h_dentry = au_h_dget_any(dentry, &bindex); + err = PTR_ERR(h_dentry); + if (IS_ERR(h_dentry)) + goto out; + } else { + /* lock free root dinfo */ + did_lock = 0; + bindex = 0; + inode = dentry->d_inode; + h_dentry = dget(au_di(dentry)->di_hdentry->hd_dentry); + } + + err = -EIO; + if (h_dentry && h_dentry->d_inode) + err = vfsub_getattr(au_sbr_mnt(sb, bindex), h_dentry, st, + au_test_dlgt(mnt_flags)); + dput(h_dentry); + if (!err) { + /* todo: I don't like this approach */ + au_cpup_attr_all(inode, /*force*/0); + fill: + generic_fillattr(inode, st); + } + + out: + if (did_lock) + di_read_unlock(dentry, AuLock_IR); + si_read_unlock(sb); + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/hin_or_dlgt.c b/fs/aufs/hin_or_dlgt.c new file mode 100644 index 0000000..172fc34 --- /dev/null +++ b/fs/aufs/hin_or_dlgt.c @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sub-routines for vfs in hinotify or dlgt mode + * + * $Id: hin_or_dlgt.c,v 1.7 2009/01/26 06:24:45 sfjro Exp $ + */ +// I'm going to slightly mad + +#include "aufs.h" + +#if !defined(CONFIG_AUFS_HINOTIFY) && !defined(CONFIG_AUFS_DLGT) +#error mis-configuraion or Makefile +#endif + +/* ---------------------------------------------------------------------- */ + +struct permission_args { + int *errp; + struct inode *inode; + int mask; + struct nameidata *nd; +}; + +static void call_permission(void *args) +{ + struct permission_args *a = args; + *a->errp = do_vfsub_permission(a->inode, a->mask, a->nd); +} + +int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd, + int dlgt) +{ + if (!dlgt) + return do_vfsub_permission(inode, mask, nd); + else { + int err, wkq_err; + struct permission_args args = { + .errp = &err, + .inode = inode, + .mask = mask, + .nd = nd + }; + wkq_err = au_wkq_wait(call_permission, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + return err; + } +} + +/* ---------------------------------------------------------------------- */ + +struct create_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + int mode; + struct nameidata *nd; + struct vfsub_args *vargs; +}; + +static void call_create(void *args) +{ + struct create_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_create(a->dir, a->dentry, a->mode, a->nd); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd, struct vfsub_args *vargs) +{ + int err; + struct create_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .mode = mode, + .nd = nd, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_create(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_create, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +struct symlink_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + const char *symname; + int mode; + struct vfsub_args *vargs; +}; + +static void call_symlink(void *args) +{ + struct symlink_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_symlink(a->dir, a->dentry, a->symname, a->mode); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname, + int mode, struct vfsub_args *vargs) +{ + int err; + struct symlink_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .symname = symname, + .mode = mode, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_symlink(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_symlink, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +struct mknod_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + int mode; + dev_t dev; + struct vfsub_args *vargs; +}; + +static void call_mknod(void *args) +{ + struct mknod_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_mknod(a->dir, a->dentry, a->mode, a->dev); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev, + struct vfsub_args *vargs) +{ + int err; + struct mknod_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .mode = mode, + .dev = dev, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_mknod(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_mknod, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +struct mkdir_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + int mode; + struct vfsub_args *vargs; +}; + +static void call_mkdir(void *args) +{ + struct mkdir_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_mkdir(a->dir, a->dentry, a->mode); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode, + struct vfsub_args *vargs) +{ + int err; + struct mkdir_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .mode = mode, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_mkdir(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_mkdir, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct link_args { + int *errp; + struct inode *dir; + struct dentry *src_dentry, *dentry; + struct vfsub_args *vargs; +}; + +static void call_link(void *args) +{ + struct link_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_link(a->src_dentry, a->dir, a->dentry); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry, struct vfsub_args *vargs) +{ + int err; + struct link_args args = { + .errp = &err, + .src_dentry = src_dentry, + .dir = dir, + .dentry = dentry, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_link(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_link, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +struct rename_args { + int *errp; + struct inode *src_dir, *dir; + struct dentry *src_dentry, *dentry; + struct vfsub_args *vargs; +}; + +static void call_rename(void *args) +{ + struct rename_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_rename(a->src_dir, a->src_dentry, a->dir, + a->dentry); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs) +{ + int err; + struct rename_args args = { + .errp = &err, + .src_dir = src_dir, + .src_dentry = src_dentry, + .dir = dir, + .dentry = dentry, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_rename(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_rename, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +struct rmdir_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + struct vfsub_args *vargs; +}; + +static void call_rmdir(void *args) +{ + struct rmdir_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_rmdir(a->dir, a->dentry); + if (unlikely(*a->errp)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +int vfsub_rmdir(struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs) +{ + int err; + struct rmdir_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_rmdir(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_rmdir, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct read_args { + ssize_t *errp; + struct file *file; + union { + void *kbuf; + char __user *ubuf; + }; + size_t count; + loff_t *ppos; +}; + +static void call_read_k(void *args) +{ + struct read_args *a = args; + LKTRTrace("%.*s, cnt %zu, pos %lld\n", + AuDLNPair(a->file->f_dentry), a->count, *a->ppos); + *a->errp = do_vfsub_read_k(a->file, a->kbuf, a->count, a->ppos); +} + +ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count, + loff_t *ppos, int dlgt) +{ + if (!dlgt) + return do_vfsub_read_u(file, ubuf, count, ppos); + else { + int wkq_err; + ssize_t err, read; + struct read_args args = { + .errp = &err, + .file = file, + .count = count, + .ppos = ppos + }; + + if (!count) + return 0; + + /* + * workaround an application bug. + * generally, read(2) or write(2) may return the value shorter + * than requested. But many applications don't support it, + * for example bash. + */ + err = -ENOMEM; + if (args.count > PAGE_SIZE) + args.count = PAGE_SIZE; + args.kbuf = kmalloc(args.count, GFP_NOFS); + if (unlikely(!args.kbuf)) + goto out; + + read = 0; + do { + wkq_err = au_wkq_wait(call_read_k, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + if (unlikely(err > 0 + && copy_to_user(ubuf, args.kbuf, err))) { + err = -EFAULT; + goto out_free; + } else if (!err) + break; + else if (unlikely(err < 0)) + goto out_free; + count -= err; + /* do not read too much because of file i/o pointer */ + if (count < args.count) + args.count = count; + ubuf += err; + read += err; + } while (count); + smp_mb(); /* flush ubuf */ + err = read; + + out_free: + kfree(args.kbuf); + out: + return err; + } +} + +ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, loff_t *ppos, + int dlgt) +{ + if (!dlgt) + return do_vfsub_read_k(file, kbuf, count, ppos); + else { + ssize_t err; + int wkq_err; + struct read_args args = { + .errp = &err, + .file = file, + .count = count, + .ppos = ppos + }; + args.kbuf = kbuf; + wkq_err = au_wkq_wait(call_read_k, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + return err; + } +} + +struct write_args { + ssize_t *errp; + struct file *file; + union { + void *kbuf; + const char __user *ubuf; + }; + size_t count; + loff_t *ppos; + struct vfsub_args *vargs; +}; + +static void call_write_k(void *args) +{ + struct write_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_write_k(a->file, a->kbuf, a->count, a->ppos); + if (unlikely(*a->errp <= 0)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count, + loff_t *ppos, struct vfsub_args *vargs) +{ + ssize_t err; + + if (!vfsub_ftest(vargs->flags, DLGT)) { + vfsub_ignore(vargs); + err = do_vfsub_write_u(file, ubuf, count, ppos); + if (unlikely(err <= 0)) + vfsub_unignore(vargs); + au_dbg_hin_list(vargs); + } else { + ssize_t written; + int wkq_err; + struct write_args args = { + .errp = &err, + .file = file, + .count = count, + .ppos = ppos, + .vargs = vargs + }; + + if (!count) + return 0; + + /* + * workaround an application bug. + * generally, read(2) or write(2) may return the value shorter + * than requested. But many applications don't support it, + * for example bash. + */ + err = -ENOMEM; + if (args.count > PAGE_SIZE) + args.count = PAGE_SIZE; + args.kbuf = kmalloc(args.count, GFP_NOFS); + if (unlikely(!args.kbuf)) + goto out; + + written = 0; + do { + if (unlikely(copy_from_user(args.kbuf, ubuf, + args.count))) { + err = -EFAULT; + goto out_free; + } + + wkq_err = au_wkq_wait(call_write_k, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + if (err > 0) { + count -= err; + if (count < args.count) + args.count = count; + ubuf += err; + written += err; + } else if (!err) + break; + else if (unlikely(err < 0)) + goto out_free; + } while (count); + err = written; + + out_free: + kfree(args.kbuf); + } + out: + return err; +} + +ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos, + struct vfsub_args *vargs) +{ + ssize_t err; + struct write_args args = { + .errp = &err, + .file = file, + .count = count, + .ppos = ppos, + .vargs = vargs + }; + + args.kbuf = kbuf; + if (!vfsub_ftest(vargs->flags, DLGT)) + call_write_k(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_write_k, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +struct readdir_args { + int *errp; + struct file *file; + filldir_t filldir; + void *arg; +}; + +static void call_readdir(void *args) +{ + struct readdir_args *a = args; + *a->errp = do_vfsub_readdir(a->file, a->filldir, a->arg); +} + +int vfsub_readdir(struct file *file, filldir_t filldir, void *arg, int dlgt) +{ + if (!dlgt) + return do_vfsub_readdir(file, filldir, arg); + else { + int err, wkq_err; + struct readdir_args args = { + .errp = &err, + .file = file, + .filldir = filldir, + .arg = arg + }; + wkq_err = au_wkq_wait(call_readdir, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + return err; + } +} + +/* ---------------------------------------------------------------------- */ + +struct splice_to_args { + long *errp; + struct file *in; + loff_t *ppos; + struct pipe_inode_info *pipe; + size_t len; + unsigned int flags; +}; + +static void call_splice_to(void *args) +{ + struct splice_to_args *a = args; + *a->errp = do_vfsub_splice_to(a->in, a->ppos, a->pipe, a->len, + a->flags); +} + +long vfsub_splice_to(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags, int dlgt) +{ + if (!dlgt) + return do_vfsub_splice_to(in, ppos, pipe, len, flags); + else { + long err; + int wkq_err; + struct splice_to_args args = { + .errp = &err, + .in = in, + .ppos = ppos, + .pipe = pipe, + .len = len, + .flags = flags + }; + wkq_err = au_wkq_wait(call_splice_to, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + return err; + } +} + +struct splice_from_args { + long *errp; + struct pipe_inode_info *pipe; + struct file *out; + loff_t *ppos; + size_t len; + unsigned int flags; + struct vfsub_args *vargs; +}; + +static void call_splice_from(void *args) +{ + struct splice_from_args *a = args; + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_splice_from(a->pipe, a->out, a->ppos, a->len, + a->flags); + if (unlikely(*a->errp < 0)) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); +} + +long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, + loff_t *ppos, size_t len, unsigned int flags, + struct vfsub_args *vargs) +{ + long err; + struct splice_from_args args = { + .errp = &err, + .pipe = pipe, + .out = out, + .ppos = ppos, + .len = len, + .flags = flags, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT)) + call_splice_from(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_splice_from, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + } + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct getattr_args { + int *errp; + struct vfsmount *mnt; + struct dentry *dentry; + struct kstat *st; +}; + +static void call_getattr(void *args) +{ + struct getattr_args *a = args; + *a->errp = do_vfsub_getattr(a->mnt, a->dentry, a->st); +} + +int vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st, + int dlgt) +{ + if (!dlgt) + return do_vfsub_getattr(mnt, dentry, st); + else { + int err, wkq_err; + struct getattr_args args = { + .errp = &err, + .mnt = mnt, + .dentry = dentry, + .st = st + }; + wkq_err = au_wkq_wait(call_getattr, &args, /*dlgt*/1); + if (unlikely(wkq_err)) + err = wkq_err; + return err; + } +} diff --git a/fs/aufs/hinode.h b/fs/aufs/hinode.h new file mode 100644 index 0000000..f14b664 --- /dev/null +++ b/fs/aufs/hinode.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * lower (branch filesystem) inode and setting inotify + * + * $Id: hinode.h,v 1.15 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_HINODE_H__ +#define __AUFS_HINODE_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include +//#include "branch.h" +//#include "inode.h" +//#include "vfsub.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +#else +struct inotify_watch { + /* empty */ +}; +#endif + +/* ---------------------------------------------------------------------- */ + +struct au_hinotify { +#ifdef CONFIG_AUFS_HINOTIFY + struct au_splhead hin_ignore; + struct inotify_watch hin_watch; + struct inode *hin_aufs_inode; /* no get/put */ +#endif +}; + +struct au_hinode { + struct inode *hi_inode; + aufs_bindex_t hi_id; +#ifdef CONFIG_AUFS_HINOTIFY + struct au_hinotify *hi_notify; +#endif + + /* reference to the copied-up whiteout with get/put */ + struct dentry *hi_whdentry; +}; + +struct au_hin_ignore { +#ifdef CONFIG_AUFS_HINOTIFY + struct list_head ign_list; + + pid_t ign_pid; + __u32 ign_events, ign_handled; + struct au_hinode *ign_hinode; +#endif +}; + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_HINOTIFY +/* inotify events */ +static const __u32 AuInMask = (IN_MOVE | IN_DELETE | IN_CREATE + /* | IN_ACCESS */ + | IN_MODIFY | IN_ATTRIB + /* | IN_DELETE_SELF | IN_MOVE_SELF */ + ); + +static inline +void au_hin_init(struct au_hinode *hinode, struct au_hinotify *val) +{ + hinode->hi_notify = val; +} + +/* hinotify.c */ +int au_hin_alloc(struct au_hinode *hinode, struct inode *inode, + struct inode *h_inode); +void au_hin_free(struct au_hinode *hinode); +void au_hin_ctl(struct au_hinode *hinode, const __u32 mask); +void au_reset_hinotify(struct inode *inode, unsigned int flags); + +int au_hin_verify_gen(struct dentry *dentry); + +int __init au_inotify_init(void); +void au_inotify_fin(void); + +static inline void au_hin_suspend(struct au_hinode *hinode) +{ + au_hin_ctl(hinode, 0); +} + +static inline void au_hin_resume(struct au_hinode *hinode) +{ + au_hin_ctl(hinode, AuInMask); +} + +#else + +static inline +void au_hin_init(struct au_hinode *hinode, struct au_hinotify *val) +{ + /* empty */ +} + +static inline +int au_hin_alloc(struct au_hinode *hinode, struct inode *inode, + struct inode *h_inode) +{ + return -EOPNOTSUPP; +} + +static inline void au_hin_free(struct au_hinode *hinode) +{ + /* nothing */ +} + +static inline void au_reset_hinotify(struct inode *inode, unsigned int flags) +{ + /* nothing */ +} + +static inline int au_hin_verify_gen(struct dentry *dentry) +{ + return 0; +} + +static inline int au_inotify_init(void) +{ + return 0; +} + +#define au_inotify_fin() do {} while (0) + +static inline void au_hin_suspend(struct au_hinode *hinode) +{ + /* empty */ +} + +static inline void au_hin_resume(struct au_hinode *hinode) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_HINOTIFY */ + +#if defined(CONFIG_AUFS_HINOTIFY) && defined(CONFIG_AUFS_DEBUG) +static inline void au_hin_list_del(struct list_head *e) +{ + list_del_init(e); +} + +void au_dbg_hin_list(struct vfsub_args *vargs); +#else +static inline void au_hin_list_del(struct list_head *e) +{ + list_del(e); +} + +static inline void au_dbg_hin_list(struct vfsub_args *vargs) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_DEBUG */ + +/* ---------------------------------------------------------------------- */ + +#endif /* __KERNEL__ */ +#endif /* __AUFS_HINODE_H__ */ diff --git a/fs/aufs/hinotify.c b/fs/aufs/hinotify.c new file mode 100644 index 0000000..036fd43 --- /dev/null +++ b/fs/aufs/hinotify.c @@ -0,0 +1,1146 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * internal/hidden inotify handler + * + * $Id: hinotify.c,v 1.62 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* +#ifdef DbgInotify +#define AuDbgHin(args...) AuDbg(##args) +#else +#define AuDbgHin(args...) do {} while () +#endif +*/ + +static struct inotify_handle *in_handle; + +AuCacheFuncs(hinotify, AuCache_HINOTIFY); + +int au_hin_alloc(struct au_hinode *hinode, struct inode *inode, + struct inode *h_inode) +{ + int err; + struct au_hinotify *hin; + s32 wd; + + LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino); + + err = -ENOMEM; + hin = au_cache_alloc_hinotify(); + if (hin) { + AuDebugOn(hinode->hi_notify); + hinode->hi_notify = hin; + au_spl_init(&hin->hin_ignore); + hin->hin_aufs_inode = inode; + + inotify_init_watch(&hin->hin_watch); + wd = inotify_add_watch(in_handle, &hin->hin_watch, h_inode, + AuInMask); + if (wd >= 0) + return 0; /* success */ + + err = wd; + put_inotify_watch(&hin->hin_watch); + au_cache_free_hinotify(hin); + hinode->hi_notify = NULL; + } + + AuTraceErr(err); + return err; +} + +void au_hin_free(struct au_hinode *hinode) +{ + int err; + struct au_hinotify *hin; + + AuTraceEnter(); + + hin = hinode->hi_notify; + if (hin) { + err = 0; + if (atomic_read(&hin->hin_watch.count)) + err = inotify_rm_watch(in_handle, &hin->hin_watch); + if (unlikely(err)) + /* it means the watch is already removed */ + LKTRTrace("failed inotify_rm_watch() %d\n", err); + au_cache_free_hinotify(hin); + hinode->hi_notify = NULL; + } +} + +/* ---------------------------------------------------------------------- */ + +void au_hin_ctl(struct au_hinode *hinode, const __u32 mask) +{ + struct inode *h_inode; + struct inotify_watch *watch; + + h_inode = hinode->hi_inode; + LKTRTrace("hi%lu, sb %p, 0x%x\n", h_inode->i_ino, h_inode->i_sb, mask); + IMustLock(h_inode); + if (!hinode->hi_notify) + return; + + watch = &hinode->hi_notify->hin_watch; +#if 0 /* reserved for future use */ + { + u32 wd; + wd = inotify_find_update_watch(in_handle, h_inode, mask); + AuTraceErr(wd); + /* ignore an err; */ + } +#else + /* struct inotify_handle is hidden */ + mutex_lock(&h_inode->inotify_mutex); + /* mutex_lock(&watch->ih->mutex); */ + watch->mask = mask; + /* mutex_unlock(&watch->ih->mutex); */ + mutex_unlock(&h_inode->inotify_mutex); +#endif + LKTRTrace("watch %p, mask %u\n", watch, watch->mask); +} + +void au_reset_hinotify(struct inode *inode, unsigned int flags) +{ + aufs_bindex_t bindex, bend; + struct inode *hi; + struct dentry *iwhdentry; + + LKTRTrace("i%lu, 0x%x\n", inode->i_ino, flags); + + bend = au_ibend(inode); + for (bindex = au_ibstart(inode); bindex <= bend; bindex++) { + hi = au_h_iptr(inode, bindex); + if (hi) { + //vfsub_i_lock_nested(hi, AuLsc_I_CHILD); + iwhdentry = au_hi_wh(inode, bindex); + if (iwhdentry) + dget(iwhdentry); + au_igrab(hi); + au_set_h_iptr(inode, bindex, NULL, 0); + au_set_h_iptr(inode, bindex, au_igrab(hi), + flags & ~AuHi_XINO); + iput(hi); + dput(iwhdentry); + //vfsub_i_unlock(hi); + } + } +} + +/* ---------------------------------------------------------------------- */ + +void au_unpin_gp(struct au_pin *args) +{ + struct au_pin1 *gp; + + gp = au_pin_gp(args); + AuDebugOn(!gp); + if (gp->dentry) + LKTRTrace("%.*s\n", AuDLNPair(gp->dentry)); + else + AuTraceEnter(); + + au_do_unpin(gp, NULL); +} + +int au_hin_verify_gen(struct dentry *dentry) +{ + struct super_block *sb = dentry->d_sb; + au_gen_t sigen; + struct inode *inode; + + if (!au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) + return 0; + + sigen = au_sigen(dentry->d_sb); + inode = dentry->d_inode; + return au_digen(dentry) != sigen + || (inode && au_iigen(inode) != sigen); +} + +/* ---------------------------------------------------------------------- */ + +/* cf. fsnotify_change() */ +__u32 vfsub_events_notify_change(struct iattr *ia) +{ + __u32 events; + const unsigned int amtime = (ATTR_ATIME | ATTR_MTIME); + + events = 0; + if ((ia->ia_valid & (ATTR_UID | ATTR_GID | ATTR_MODE)) + || (ia->ia_valid & amtime) == amtime) + events |= IN_ATTRIB; + if ((ia->ia_valid & ATTR_SIZE) + || (ia->ia_valid & amtime) == ATTR_MTIME) + events |= IN_MODIFY; + return events; +} + +void vfsub_ign_hinode(struct vfsub_args *vargs, __u32 events, + struct au_hinode *hinode) +{ + struct au_hinotify *hin; + struct super_block *sb; + struct au_hin_ignore *ign; + + if (!hinode) + return; + + hin = hinode->hi_notify; + if (!hin || !hin->hin_watch.mask) + return; + + sb = hin->hin_aufs_inode->i_sb; + AuDebugOn(!au_opt_test(au_mntflags(sb), UDBA_INOTIFY)); + + ign = vargs->ignore + vargs->nignore++; + ign->ign_events = events; + ign->ign_handled = 0; + ign->ign_hinode = hinode; + + { + struct inode *h_inode; + h_inode = hinode->hi_inode; + if (vfsub_i_trylock(h_inode)) + au_dbg_blocked(); + IMustLock(h_inode); + } +} + +static void au_hin_ignore(struct au_hin_ignore *ign) +{ + struct au_hinode *hinode; + __u32 events; + struct au_hinotify *hin; + struct inode *h_inode; + + hinode = ign->ign_hinode; + events = ign->ign_events; + LKTRTrace("0x%x\n", events); + AuDebugOn(!hinode || !events); + + hin = hinode->hi_notify; + h_inode = hinode->hi_inode; + if (h_inode && hin) { + LKTRTrace("hi%lu\n", h_inode->i_ino); +#ifdef DbgInotify + AuDbg("hi%lu, 0x%x\n", h_inode->i_ino, events); +#endif + + au_spl_add(&ign->ign_list, &hin->hin_ignore); + /* AuDbg("list_add %p, 0x%x\n", ign, events); */ + } +#if 1 /* todo: test dlgt */ + else + /* + * it may happen by this scenario. + * - a file and its parent dir exist on two branches + * - a file on the upper branch is opened + * - the parent dir and the file are removed by udba + * - the parent is re-accessed, and new dentry/inode in + * aufs is generated for it, based upon the one on the lower + * branch + * - the opened file is re-accessed, re-validated, and it may be + * re-connected to the new parent dentry + * it means the file in aufs cannot get the actual removed + * parent dir on the branch. + */ + INIT_LIST_HEAD(&ign->ign_list); +#endif +} + +static void au_hin_unignore(struct au_hin_ignore *ign) +{ + struct au_hinode *hinode; + __u32 events; + struct au_hinotify *hin; + struct inode *h_inode; + + hinode = ign->ign_hinode; + events = ign->ign_events; + LKTRTrace("0x%x\n", events); + /* AuDbg("0x%x\n", events); */ + AuDebugOn(!hinode || !events); + + hin = hinode->hi_notify; + h_inode = hinode->hi_inode; + if (!h_inode || !hin) + return; + LKTRTrace("hi%lu\n", h_inode->i_ino); +#ifdef DbgInotify + AuDbg("hi%lu, 0x%x\n", h_inode->i_ino, events); +#endif + + spin_lock(&hin->hin_ignore.spin); + au_hin_list_del(&ign->ign_list); + spin_unlock(&hin->hin_ignore.spin); + /* AuDbg("list_del %p, 0x%x\n", ign, events); */ +} + +static int au_hin_test_ignore(u32 mask, struct au_hinotify *hin) +{ + int do_ignore; + u32 events; + struct au_hin_ignore *ign, *tmp; + struct list_head *head; + + do_ignore = 0; + head = &hin->hin_ignore.head; + spin_lock(&hin->hin_ignore.spin); + list_for_each_entry_safe(ign, tmp, head, ign_list) { + /* AuDbg("ign %p\n", ign); */ + if (ign->ign_pid == current->pid) { + events = (mask & ign->ign_events); + if (events) { + do_ignore = 1; + ign->ign_handled |= events; + if (ign->ign_events == ign->ign_handled) { + list_del_init(&ign->ign_list); + /* + AuDbg("list_del %p, 0x%x\n", + ign, events); + */ + } + break; + } + } + } + spin_unlock(&hin->hin_ignore.spin); + + return do_ignore; +} + +void vfsub_ignore(struct vfsub_args *vargs) +{ + int n; + struct au_hin_ignore *ign; + struct super_block *sb; + struct au_hinode *hinode; + struct inode *h_inode; + + n = vargs->nignore; + if (!n) + return; + + ign = vargs->ignore; + hinode = ign->ign_hinode; + sb = hinode->hi_notify->hin_aufs_inode->i_sb; + h_inode = hinode->hi_inode; + if (au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) { + if (vfsub_i_trylock(h_inode)) + au_dbg_blocked(); + IMustLock(h_inode); + } + while (n-- > 0) { + ign->ign_pid = current->pid; + au_hin_ignore(ign++); + } +} + +void vfsub_unignore(struct vfsub_args *vargs) +{ + int n; + struct au_hin_ignore *ign; + + n = vargs->nignore; + if (!n) + return; + + ign = vargs->ignore; + while (n-- > 0) + au_hin_unignore(ign++); +} + +#ifdef CONFIG_AUFS_DEBUG +void au_dbg_hin_list(struct vfsub_args *vargs) +{ + int n; + struct au_hin_ignore *ign; + + n = vargs->nignore; + if (!n) + return; + + ign = vargs->ignore; + while (n-- > 0) { + /* AuDebugOn(!list_empty(&ign++->ign_list)); */ + if (list_empty(&ign++->ign_list)) + continue; + ign--; + AuDbg("%d: pid %d, 0x%x\n", + n + 1, ign->ign_pid, ign->ign_events); + au_hin_unignore(ign); + ign++; + au_dbg_blocked(); + } +} +#endif + +/* ---------------------------------------------------------------------- */ + +static char *in_name(u32 mask) +{ +#ifdef CONFIG_AUFS_DEBUG +#define test_ret(flag) if (mask & flag) \ + return #flag; + test_ret(IN_ACCESS); + test_ret(IN_MODIFY); + test_ret(IN_ATTRIB); + test_ret(IN_CLOSE_WRITE); + test_ret(IN_CLOSE_NOWRITE); + test_ret(IN_OPEN); + test_ret(IN_MOVED_FROM); + test_ret(IN_MOVED_TO); + test_ret(IN_CREATE); + test_ret(IN_DELETE); + test_ret(IN_DELETE_SELF); + test_ret(IN_MOVE_SELF); + test_ret(IN_UNMOUNT); + test_ret(IN_Q_OVERFLOW); + test_ret(IN_IGNORED); + return ""; +#undef test_ret +#else + return "??"; +#endif +} + +/* ---------------------------------------------------------------------- */ + +static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen, + struct inode *dir) +{ + struct dentry *dentry, *d, *parent; + struct qstr *dname; + + LKTRTrace("%.*s, dir%lu\n", nlen, name, dir->i_ino); + + parent = d_find_alias(dir); + if (!parent) + return NULL; + + dentry = NULL; + spin_lock(&dcache_lock); + list_for_each_entry(d, &parent->d_subdirs, d_u.d_child) { + LKTRTrace("%.*s\n", AuDLNPair(d)); + dname = &d->d_name; + if (dname->len != nlen || memcmp(dname->name, name, nlen)) + continue; + if (!atomic_read(&d->d_count) || !d->d_fsdata) { + spin_lock(&d->d_lock); + __d_drop(d); + spin_unlock(&d->d_lock); + continue; + } + + dentry = dget(d); + break; + } + spin_unlock(&dcache_lock); + dput(parent); + + if (dentry) { +#if 0 + lktr_set_pid(current->pid, LktrArrayPid); + AuDbgDentry(dentry); + lktr_clear_pid(current->pid, LktrArrayPid); +#endif + di_write_lock_child(dentry); + } + return dentry; +} + +static struct inode *lookup_wlock_by_ino(struct super_block *sb, + aufs_bindex_t bindex, ino_t h_ino) +{ + struct inode *inode; + struct au_xino_entry xinoe; + int err; + + LKTRTrace("b%d, hi%lu\n", bindex, (unsigned long)h_ino); + AuDebugOn(!au_opt_test_xino(au_mntflags(sb))); + + inode = NULL; + err = au_xino_read(sb, bindex, h_ino, &xinoe); + if (!err && xinoe.ino) + inode = ilookup(sb, xinoe.ino); + if (!inode) + goto out; + if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { + AuWarn("wrong root branch\n"); + iput(inode); + inode = NULL; + goto out; + } + + ii_write_lock_child(inode); + + out: + return inode; +} + +static int hin_xino(struct inode *inode, struct inode *h_inode) +{ + int err; + aufs_bindex_t bindex, bend, bfound, bstart; + struct inode *h_i; + + LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino); + + err = 0; + if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { + AuWarn("branch root dir was changed\n"); + goto out; + } + + bfound = -1; + bend = au_ibend(inode); + bstart = au_ibstart(inode); +#if 0 /* reserved for future use */ + if (bindex == bend) { + /* keep this ino in rename case */ + goto out; + } +#endif + for (bindex = bstart; bindex <= bend; bindex++) { + if (au_h_iptr(inode, bindex) == h_inode) { + bfound = bindex; + break; + } + } + if (bfound < 0) + goto out; + + for (bindex = bstart; bindex <= bend; bindex++) { + h_i = au_h_iptr(inode, bindex); + if (h_i) + err = au_xino_write0(inode->i_sb, bindex, h_i->i_ino, + 0); + /* ignore this error */ + /* bad action? */ + } + + /* children inode number will be broken */ + + out: + AuTraceErr(err); + return err; +} + +static int hin_gen_tree(struct dentry *dentry) +{ + int err, i, j, ndentry; + struct au_dcsub_pages dpages; + struct au_dpage *dpage; + struct dentry **dentries; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + err = au_dpages_init(&dpages, GFP_NOFS); + if (unlikely(err)) + goto out; + err = au_dcsub_pages(&dpages, dentry, NULL, NULL); + if (unlikely(err)) + goto out_dpages; + + for (i = 0; i < dpages.ndpage; i++) { + dpage = dpages.dpages + i; + dentries = dpage->dentries; + ndentry = dpage->ndentry; + for (j = 0; j < ndentry; j++) { + struct dentry *d; + d = dentries[j]; + LKTRTrace("%.*s\n", AuDLNPair(d)); + if (IS_ROOT(d)) + continue; + + d_drop(d); + au_digen_dec(d); + if (d->d_inode) + /* todo: reset children xino? + cached children only? */ + au_iigen_dec(d->d_inode); + } + } + + out_dpages: + au_dpages_free(&dpages); + + /* discard children */ + dentry_unhash(dentry); + dput(dentry); + out: + AuTraceErr(err); + return err; +} + +/* + * return 0 if processed. + */ +static int hin_gen_by_inode(char *name, unsigned int nlen, struct inode *inode, + const unsigned int isdir) +{ + int err; + struct dentry *d; + struct qstr *dname; + + LKTRTrace("%.*s, i%lu\n", nlen, name, inode->i_ino); + + err = 1; + if (unlikely(inode->i_ino == AUFS_ROOT_INO)) { + AuWarn("branch root dir was changed\n"); + err = 0; + goto out; + } + + if (!isdir) { + AuDebugOn(!name); + au_iigen_dec(inode); + spin_lock(&dcache_lock); + list_for_each_entry(d, &inode->i_dentry, d_alias) { + dname = &d->d_name; + if (dname->len != nlen + && memcmp(dname->name, name, nlen)) + continue; + err = 0; + spin_lock(&d->d_lock); + __d_drop(d); + au_digen_dec(d); + spin_unlock(&d->d_lock); + break; + } + spin_unlock(&dcache_lock); + } else { + au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIRS); + d = d_find_alias(inode); + if (!d) { + au_iigen_dec(inode); + goto out; + } + + dname = &d->d_name; + if (dname->len == nlen && !memcmp(dname->name, name, nlen)) + err = hin_gen_tree(d); + dput(d); + } + + out: + AuTraceErr(err); + return err; +} + +static int hin_gen_by_name(struct dentry *dentry, const unsigned int isdir) +{ + int err; + struct inode *inode; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + inode = dentry->d_inode; + if (IS_ROOT(dentry) + /* || (inode && inode->i_ino == AUFS_ROOT_INO) */ + ) { + AuWarn("branch root dir was changed\n"); + return 0; + } + + err = 0; + if (!isdir) { + d_drop(dentry); + au_digen_dec(dentry); + if (inode) + au_iigen_dec(inode); + } else { + au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIRS); + if (inode) + err = hin_gen_tree(dentry); + } + + AuTraceErr(err); + return err; +} + +static void hin_attr(struct inode *inode, struct inode *h_inode) +{ + struct dentry *h_dentry; + + LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino); + + if (au_h_iptr(inode, au_ibstart(inode)) != h_inode) + return; + + h_dentry = d_find_alias(h_inode); + if (h_dentry) { + au_update_fuse_h_inode(NULL, h_dentry); + /* ignore an error*/ + dput(h_dentry); + } + + au_cpup_attr_all(inode, /*force*/1); +} + +/* ---------------------------------------------------------------------- */ + +/* hinotify job flags */ +#define AuHinJob_XINO0 1 +#define AuHinJob_GEN (1 << 1) +#define AuHinJob_DIRENT (1 << 2) +#define AuHinJob_ATTR (1 << 3) +#define AuHinJob_ISDIR (1 << 4) +#define AuHinJob_TRYXINO0 (1 << 5) +#define AuHinJob_MNTPNT (1 << 6) +#define au_ftest_hinjob(flags, name) ((flags) & AuHinJob_##name) +#define au_fset_hinjob(flags, name) { (flags) |= AuHinJob_##name; } +#define au_fclr_hinjob(flags, name) { (flags) &= ~AuHinJob_##name; } + +struct hin_job_args { + unsigned int flags; + struct inode *inode, *h_inode, *dir, *h_dir; + struct dentry *dentry; + char *h_name; + int h_nlen; +}; + +static int hin_job(struct hin_job_args *a) +{ + const unsigned int isdir = au_ftest_hinjob(a->flags, ISDIR); + + /* reset xino */ + if (au_ftest_hinjob(a->flags, XINO0) && a->inode) + hin_xino(a->inode, a->h_inode); + /* ignore this error */ + + if (au_ftest_hinjob(a->flags, TRYXINO0) + && a->inode + && a->h_inode) { + vfsub_i_lock_nested(a->h_inode, AuLsc_I_CHILD); + if (!a->h_inode->i_nlink) + hin_xino(a->inode, a->h_inode); + /* ignore this error */ + vfsub_i_unlock(a->h_inode); + } + + /* make the generation obsolete */ + if (au_ftest_hinjob(a->flags, GEN)) { + int err = -1; + if (a->inode) + err = hin_gen_by_inode(a->h_name, a->h_nlen, a->inode, + isdir); + if (err && a->dentry) + hin_gen_by_name(a->dentry, isdir); + /* ignore this error */ + } + + /* make dir entries obsolete */ + if (au_ftest_hinjob(a->flags, DIRENT) && a->inode) { + struct au_vdir *vdir; + IiMustWriteLock(a->inode); + vdir = au_ivdir(a->inode); + if (vdir) + vdir->vd_jiffy = 0; + /* IMustLock(a->inode); */ + /* a->inode->i_version++; */ + } + + /* update the attr */ + if (au_ftest_hinjob(a->flags, ATTR) && a->inode && a->h_inode) { + mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD); + hin_attr(a->inode, a->h_inode); + mutex_unlock(&a->h_inode->i_mutex); + } + + /* can do nothing but warn */ + if (au_ftest_hinjob(a->flags, MNTPNT) + && a->dentry + && d_mountpoint(a->dentry)) + AuWarn("mount-point %.*s is removed or renamed\n", + AuDLNPair(a->dentry)); + + return 0; +} + +/* ---------------------------------------------------------------------- */ + +enum { CHILD, PARENT }; +struct postproc_args { + struct inode *h_dir, *dir, *h_child_inode; + u32 mask; + unsigned int flags[2]; + unsigned int h_child_nlen; + char h_child_name[]; +}; + +static void postproc(void *_args) +{ + struct postproc_args *a = _args; + struct super_block *sb; + aufs_bindex_t bindex, bend, bfound; + unsigned char xino, try_iput; + int err; + struct inode *inode; + ino_t h_ino; + struct hin_job_args args; + struct dentry *dentry; + struct au_sbinfo *sbinfo; + + AuDebugOn(!_args); + AuDebugOn(!a->h_dir); + AuDebugOn(!a->dir); + AuDebugOn(!a->mask); + LKTRTrace("mask 0x%x %s, i%lu, hi%lu, hci%lu\n", + a->mask, in_name(a->mask), a->dir->i_ino, a->h_dir->i_ino, + a->h_child_inode ? a->h_child_inode->i_ino : 0); + + inode = NULL; + dentry = NULL; + /* + * do not lock a->dir->i_mutex here + * because of d_revalidate() may cause a deadlock. + */ +#if 0 + /* + * just wait for the dir becoming non-busy. + * for instance, prevent NFSD lookup from "nlink == 0" message. + * but it is not guranteed. + */ + mutex_lock(&a->dir->i_mutex); + mutex_unlock(&a->dir->i_mutex); +#endif + + sb = a->dir->i_sb; + AuDebugOn(!sb); + sbinfo = au_sbi(sb); + AuDebugOn(!sbinfo); + /* big aufs lock */ + si_noflush_write_lock(sb); + + ii_read_lock_parent(a->dir); + bfound = -1; + bend = au_ibend(a->dir); + for (bindex = au_ibstart(a->dir); bindex <= bend; bindex++) + if (au_h_iptr(a->dir, bindex) == a->h_dir) { + bfound = bindex; + break; + } + ii_read_unlock(a->dir); + if (unlikely(bfound < 0)) + goto out; + + xino = !!au_opt_test_xino(au_mntflags(sb)); + h_ino = 0; + if (a->h_child_inode) + h_ino = a->h_child_inode->i_ino; + + if (a->h_child_nlen + && (au_ftest_hinjob(a->flags[CHILD], GEN) + || au_ftest_hinjob(a->flags[CHILD], MNTPNT))) + dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen, + a->dir); + try_iput = 0; + if (dentry) + inode = dentry->d_inode; + if (xino && !inode && h_ino + && (au_ftest_hinjob(a->flags[CHILD], XINO0) + || au_ftest_hinjob(a->flags[CHILD], TRYXINO0) + || au_ftest_hinjob(a->flags[CHILD], GEN) + || au_ftest_hinjob(a->flags[CHILD], ATTR))) { + inode = lookup_wlock_by_ino(sb, bfound, h_ino); + try_iput = 1; + } + + args.flags = a->flags[CHILD]; + args.dentry = dentry; + args.inode = inode; + args.h_inode = a->h_child_inode; + args.dir = a->dir; + args.h_dir = a->h_dir; + args.h_name = a->h_child_name; + args.h_nlen = a->h_child_nlen; + err = hin_job(&args); + if (dentry) { + if (dentry->d_fsdata) + di_write_unlock(dentry); + dput(dentry); + } + if (inode && try_iput) { + ii_write_unlock(inode); + iput(inode); + } + + ii_write_lock_parent(a->dir); + args.flags = a->flags[PARENT]; + args.dentry = NULL; + args.inode = a->dir; + args.h_inode = a->h_dir; + args.dir = NULL; + args.h_dir = NULL; + args.h_name = NULL; + args.h_nlen = 0; + err = hin_job(&args); + ii_write_unlock(a->dir); + + out: + au_nwt_done(&sbinfo->si_nowait); + si_write_unlock(sb); + + iput(a->h_child_inode); + iput(a->h_dir); + iput(a->dir); + kfree(a); +} + +static void aufs_inotify(struct inotify_watch *watch, u32 wd, u32 mask, + u32 cookie, const char *h_child_name, + struct inode *h_child_inode) +{ + struct au_hinotify *hinotify; + struct postproc_args *args; + int len, wkq_err; + unsigned char isdir, isroot, wh; + char *p; + struct inode *dir; + unsigned int flags[2]; + + LKTRTrace("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s, hi%lu\n", + watch->inode->i_ino, wd, mask, in_name(mask), cookie, + h_child_name ? h_child_name : "", + h_child_inode ? h_child_inode->i_ino : 0); + + /* if IN_UNMOUNT happens, there must be another bug */ + if (mask & (IN_IGNORED | IN_UNMOUNT)) { + put_inotify_watch(watch); + return; + } + +#ifdef DbgInotify + if (!h_child_name || strcmp(h_child_name, AUFS_XINO_FNAME)) + AuDbg("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s," + " hi%lu\n", + watch->inode->i_ino, wd, mask, in_name(mask), cookie, + h_child_name ? h_child_name : "", + h_child_inode ? h_child_inode->i_ino : 0); +#endif + + hinotify = container_of(watch, struct au_hinotify, hin_watch); + AuDebugOn(!hinotify || !hinotify->hin_aufs_inode); + if (au_hin_test_ignore(mask, hinotify)) { +#ifdef DbgInotify + AuDbg("ignored\n"); +#endif + return; + } +#if 0 /* tmp debug */ + if (h_child_name && !strcmp(h_child_name, AUFS_XINO_FNAME)) { + AuDbg("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s, hi%lu\n", + watch->inode->i_ino, wd, mask, in_name(mask), cookie, + h_child_name ? h_child_name : "", + h_child_inode ? h_child_inode->i_ino : 0); + //au_dbg_blocked(); + } +#endif + + dir = igrab(hinotify->hin_aufs_inode); + if (!dir) + return; + isroot = (dir->i_ino == AUFS_ROOT_INO); + len = 0; + wh = 0; + if (h_child_name) { + len = strlen(h_child_name); + if (!memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { + h_child_name += AUFS_WH_PFX_LEN; + len -= AUFS_WH_PFX_LEN; + wh = 1; + } + } + + isdir = 0; + if (h_child_inode) + isdir = !!S_ISDIR(h_child_inode->i_mode); + flags[PARENT] = AuHinJob_ISDIR; + flags[CHILD] = 0; + if (isdir) + flags[CHILD] = AuHinJob_ISDIR; + switch (mask & IN_ALL_EVENTS) { + case IN_MODIFY: + /*FALLTHROUGH*/ + case IN_ATTRIB: + if (h_child_inode) { + if (!wh) + au_fset_hinjob(flags[CHILD], ATTR); + } else + au_fset_hinjob(flags[PARENT], ATTR); + break; + + /* IN_MOVED_FROM is the first event in rename(2) */ + case IN_MOVED_FROM: + case IN_MOVED_TO: + AuDebugOn(!h_child_name || !h_child_inode); + au_fset_hinjob(flags[CHILD], GEN); + au_fset_hinjob(flags[CHILD], ATTR); + if (1 || isdir) + au_fset_hinjob(flags[CHILD], XINO0); + au_fset_hinjob(flags[CHILD], MNTPNT); + + au_fset_hinjob(flags[PARENT], ATTR); + au_fset_hinjob(flags[PARENT], DIRENT); + break; + + case IN_CREATE: + AuDebugOn(!h_child_name || !h_child_inode); + au_fset_hinjob(flags[PARENT], ATTR); + au_fset_hinjob(flags[PARENT], DIRENT); + au_fset_hinjob(flags[CHILD], GEN); + /* hard link */ + if (!isdir && h_child_inode->i_nlink > 1) + au_fset_hinjob(flags[CHILD], ATTR); + break; + + case IN_DELETE: + /* + * aufs never be able to get this child inode. + * revalidation should be in d_revalidate() + * by checking i_nlink, i_generation or d_unhashed(). + */ + AuDebugOn(!h_child_name); + au_fset_hinjob(flags[PARENT], ATTR); + au_fset_hinjob(flags[PARENT], DIRENT); + au_fset_hinjob(flags[CHILD], GEN); + au_fset_hinjob(flags[CHILD], TRYXINO0); + au_fset_hinjob(flags[CHILD], MNTPNT); + break; + + case IN_DELETE_SELF: +#if 0 + if (!isroot) + au_fset_hinjob(flags[PARENT], GEN); + /*FALLTHROUGH*/ +#endif + + case IN_MOVE_SELF: +#if 0 + /* + * when an inotify is set to an aufs inode, + * such inode can be isolated and this event can be fired + * solely. + */ + AuDebugOn(h_child_name || h_child_inode); + if (unlikely(isroot)) { + AuWarn("root branch was moved\n"); + iput(dir); + return; + } + au_fset_hinjob(flags[PARENT], XINO0); + au_fset_hinjob(flags[PARENT], GEN); + au_fset_hinjob(flags[PARENT], ATTR); + au_fset_hinjob(flags[PARENT], DIRENT); + /* au_fset_hinjob(flags[PARENT], MNTPNT); */ + break; +#endif + + case IN_ACCESS: + default: + AuDebugOn(1); + } + + if (wh) + h_child_inode = NULL; + + /* iput() and kfree() will be called in postproc() */ + /* + * inotify_mutex is already acquired and kmalloc/prune_icache may lock + * iprune_mutex. strange. + */ + lockdep_off(); + args = kmalloc(sizeof(*args) + len + 1, GFP_NOFS); + lockdep_on(); + if (unlikely(!args)) { + AuErr1("no memory\n"); + iput(dir); + return; + } + args->flags[PARENT] = flags[PARENT]; + args->flags[CHILD] = flags[CHILD]; + args->mask = mask; + args->dir = dir; + args->h_dir = igrab(watch->inode); + if (h_child_inode) + h_child_inode = igrab(h_child_inode); /* can be NULL */ + args->h_child_inode = h_child_inode; + args->h_child_nlen = len; + if (len) { + p = (void *)args; + p += sizeof(*args); + memcpy(p, h_child_name, len + 1); + } + + lockdep_off(); + wkq_err = au_wkq_nowait(postproc, args, dir->i_sb, /*dlgt*/0); + lockdep_on(); + if (unlikely(wkq_err)) + AuErr("wkq %d\n", wkq_err); +} + +static void aufs_inotify_destroy(struct inotify_watch *watch) +{ + return; +} + +static struct inotify_operations aufs_inotify_ops = { + .handle_event = aufs_inotify, + .destroy_watch = aufs_inotify_destroy +}; + +/* ---------------------------------------------------------------------- */ + +static void au_hin_destroy_cache(void) +{ + kmem_cache_destroy(au_cachep[AuCache_HINOTIFY]); + au_cachep[AuCache_HINOTIFY] = NULL; +} + +int __init au_inotify_init(void) +{ + int err; + + err = -ENOMEM; + au_cachep[AuCache_HINOTIFY] = AuCache(au_hinotify); + if (au_cachep[AuCache_HINOTIFY]) { + err = 0; + in_handle = inotify_init(&aufs_inotify_ops); + if (IS_ERR(in_handle)) { + err = PTR_ERR(in_handle); + au_hin_destroy_cache(); + } + } + AuTraceErr(err); + return err; +} + +void au_inotify_fin(void) +{ + inotify_destroy(in_handle); + if (au_cachep[AuCache_HINOTIFY]) + au_hin_destroy_cache(); +} diff --git a/fs/aufs/i_op.c b/fs/aufs/i_op.c new file mode 100644 index 0000000..3153263 --- /dev/null +++ b/fs/aufs/i_op.c @@ -0,0 +1,954 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode operations (except add/del/rename) + * + * $Id: i_op.c,v 1.79 2009/01/26 06:24:45 sfjro Exp $ + */ + +//#include +//#include +#include +#include "aufs.h" + +static int silly_lock(struct inode *inode, struct nameidata *nd) +{ + int locked = 0; + struct super_block *sb = inode->i_sb; + + LKTRTrace("i%lu, nd %p\n", inode->i_ino, nd); + + if (!nd || !nd->dentry) { + si_read_lock(sb, AuLock_FLUSH); + ii_read_lock_child(inode); + } else if (nd->dentry->d_inode != inode) { + locked = 1; + /* lock child first, then parent */ + si_read_lock(sb, AuLock_FLUSH); + ii_read_lock_child(inode); + di_read_lock_parent(nd->dentry, 0); + } else { + locked = 2; + aufs_read_lock(nd->dentry, AuLock_FLUSH | AuLock_IR); + } + return locked; +} + +static void silly_unlock(int locked, struct inode *inode, struct nameidata *nd) +{ + struct super_block *sb = inode->i_sb; + + LKTRTrace("locked %d, i%lu, nd %p\n", locked, inode->i_ino, nd); + + switch (locked) { + case 0: + ii_read_unlock(inode); + si_read_unlock(sb); + break; + case 1: + di_read_unlock(nd->dentry, 0); + ii_read_unlock(inode); + si_read_unlock(sb); + break; + case 2: + aufs_read_unlock(nd->dentry, AuLock_FLUSH | AuLock_IR); + break; + default: + BUG(); + } +} + +static int h_permission(struct inode *h_inode, int mask, + struct nameidata *fake_nd, int brperm, int dlgt) +{ + int err, submask; + const int write_mask = (mask & (MAY_WRITE | MAY_APPEND)); + + LKTRTrace("ino %lu, mask 0x%x, brperm 0x%x\n", + h_inode->i_ino, mask, brperm); + + err = -EACCES; + if ((write_mask && IS_IMMUTABLE(h_inode)) + || ((mask & MAY_EXEC) && S_ISREG(h_inode->i_mode) + && fake_nd && fake_nd->mnt + && (fake_nd->mnt->mnt_flags & MNT_NOEXEC))) + goto out; + + /* + * - skip hidden fs test in the case of write to ro branch. + * - nfs dir permission write check is optimized, but a policy for + * link/rename requires a real check. + */ + submask = mask & ~MAY_APPEND; + if ((write_mask && !au_br_writable(brperm)) + || (au_test_nfs(h_inode->i_sb) && S_ISDIR(h_inode->i_mode) + && write_mask && !(mask & MAY_READ)) + || !h_inode->i_op + || !h_inode->i_op->permission) { + /* LKTRLabel(generic_permission); */ + err = generic_permission(h_inode, submask, NULL); + } else { + /* LKTRLabel(h_inode->permission); */ + err = h_inode->i_op->permission(h_inode, submask, fake_nd); + AuTraceErr(err); + } + +#if 1 /* todo: export? */ + if (!err) + err = au_security_inode_permission(h_inode, mask, fake_nd, + dlgt); +#endif + + out: + AuTraceErr(err); + return err; +} + +static int aufs_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + int err; + aufs_bindex_t bindex, bend; + unsigned char locked, dlgt, do_nd; + const unsigned char isdir = S_ISDIR(inode->i_mode); + struct inode *h_inode; + struct super_block *sb; + unsigned int mnt_flags; + struct path path; + const int write_mask = (mask & (MAY_WRITE | MAY_APPEND)); + + LKTRTrace("ino %lu, mask 0x%x, isdir %d, write_mask %d, " + "nd %d{%d, %d}\n", + inode->i_ino, mask, isdir, write_mask, + !!nd, nd ? !!nd->dentry : 0, nd ? !!nd->mnt : 0); + + sb = inode->i_sb; + locked = silly_lock(inode, nd); + do_nd = (nd && locked >= 1); + mnt_flags = au_mntflags(sb); + dlgt = !!au_test_dlgt(mnt_flags); + + if (!isdir || write_mask || au_test_dirperm1(mnt_flags)) { + h_inode = au_h_iptr(inode, au_ibstart(inode)); + AuDebugOn(!h_inode + || ((h_inode->i_mode & S_IFMT) + != (inode->i_mode & S_IFMT))); + err = 0; + bindex = au_ibstart(inode); + LKTRTrace("b%d\n", bindex); + if (do_nd) { + path.mnt = nd->mnt; + path.dentry = nd->dentry; + nd->mnt = mntget(au_sbr_mnt(sb, bindex)); + nd->dentry = dget(au_h_dptr(nd->dentry, bindex)); + err = h_permission(h_inode, mask, nd, + au_sbr_perm(sb, bindex), dlgt); + path_release(nd); + nd->mnt = path.mnt; + nd->dentry = path.dentry; + } else { + AuDebugOn(nd && nd->mnt); + err = h_permission(h_inode, mask, nd, + au_sbr_perm(sb, bindex), dlgt); + } + + if (write_mask && !err) { + /* test whether the upper writable branch exists */ + err = -EROFS; + for (; bindex >= 0; bindex--) + if (!au_br_rdonly(au_sbr(sb, bindex))) { + err = 0; + break; + } + } + goto out; + } + + /* non-write to dir */ + if (do_nd) { + path.mnt = nd->mnt; + path.dentry = nd->dentry; + } else { + path.mnt = NULL; + path.dentry = NULL; + } + err = 0; + bend = au_ibend(inode); + for (bindex = au_ibstart(inode); !err && bindex <= bend; bindex++) { + h_inode = au_h_iptr(inode, bindex); + if (!h_inode) + continue; + AuDebugOn(!S_ISDIR(h_inode->i_mode)); + + LKTRTrace("b%d\n", bindex); + if (do_nd) { + nd->mnt = mntget(au_sbr_mnt(sb, bindex)); + nd->dentry = dget(au_h_dptr(path.dentry, bindex)); + err = h_permission(h_inode, mask, nd, + au_sbr_perm(sb, bindex), dlgt); + path_release(nd); + } else { + AuDebugOn(nd && nd->mnt); + err = h_permission(h_inode, mask, nd, + au_sbr_perm(sb, bindex), dlgt); + } + } + if (do_nd) { + nd->mnt = path.mnt; + nd->dentry = path.dentry; + } + + out: + silly_unlock(locked, inode, nd); + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} + +/* ---------------------------------------------------------------------- */ + +static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + struct dentry *ret, *parent; + int err, npositive; + struct inode *inode, *h_inode; + struct nameidata tmp_nd, *ndp; + aufs_bindex_t bstart; + struct mutex *mtx; + struct super_block *sb; + + LKTRTrace("dir %lu, %.*s, nd{0x%x}\n", + dir->i_ino, AuDLNPair(dentry), nd ? nd->flags : 0); + AuDebugOn(IS_ROOT(dentry)); + IMustLock(dir); + + sb = dir->i_sb; + si_read_lock(sb, AuLock_FLUSH); + err = au_alloc_dinfo(dentry); + ret = ERR_PTR(err); + if (unlikely(err)) + goto out; + + /* nd can be NULL */ + ndp = au_dup_nd(au_sbi(sb), &tmp_nd, nd); + parent = dentry->d_parent; /* dir inode is locked */ + di_read_lock_parent(parent, AuLock_IR); + npositive = au_lkup_dentry(dentry, au_dbstart(parent), /*type*/0, ndp); + di_read_unlock(parent, AuLock_IR); + err = npositive; + ret = ERR_PTR(err); + if (unlikely(err < 0)) + goto out_unlock; + + inode = NULL; + if (npositive) { + bstart = au_dbstart(dentry); + h_inode = au_h_dptr(dentry, bstart)->d_inode; + AuDebugOn(!h_inode); + if (!S_ISDIR(h_inode->i_mode)) { + /* + * stop 'race'-ing between hardlinks under different + * parents. + */ + mtx = &au_sbr(sb, bstart)->br_xino.xi_nondir_mtx; + mutex_lock(mtx); + inode = au_new_inode(dentry, /*must_new*/0); + mutex_unlock(mtx); + } else + inode = au_new_inode(dentry, /*must_new*/0); + ret = (void *)inode; + } + if (!IS_ERR(inode)) { + ret = d_splice_alias(inode, dentry); + if (unlikely(IS_ERR(ret) && inode)) + ii_write_unlock(inode); + AuDebugOn(nd + && (nd->flags & LOOKUP_OPEN) + && nd->intent.open.file + && nd->intent.open.file->f_dentry); + au_store_fmode_exec(nd, inode); + } + + out_unlock: + di_write_unlock(dentry); + out: + si_read_unlock(sb); + AuTraceErrPtr(ret); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return ret; +} + +/* ---------------------------------------------------------------------- */ + +/* + * decide the branch and the parent dir where we will create a new entry. + * returns new bindex or an error. + * copyup the parent dir if needed. + */ +int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry, + struct au_wr_dir_args *args) +{ + int err; + aufs_bindex_t bcpup, bstart, src_bstart; + struct super_block *sb; + struct dentry *parent; + struct au_sbinfo *sbinfo; + const int add_entry = au_ftest_wrdir(args->flags, ADD_ENTRY); + + LKTRTrace("%.*s, src %p, {%d, 0x%x}\n", + AuDLNPair(dentry), src_dentry, args->force_btgt, args->flags); + + sb = dentry->d_sb; + sbinfo = au_sbi(sb); + parent = dget_parent(dentry); + bstart = au_dbstart(dentry); + bcpup = bstart; + if (args->force_btgt < 0) { + if (src_dentry) { + src_bstart = au_dbstart(src_dentry); + if (src_bstart < bstart) + bcpup = src_bstart; + } else if (add_entry) { + err = AuWbrCreate(sbinfo, dentry, + au_ftest_wrdir(args->flags, ISDIR)); + bcpup = err; + } + + if (bcpup < 0 || au_test_ro(sb, bcpup, dentry->d_inode)) { + if (add_entry) + err = AuWbrCopyup(sbinfo, dentry); + else { + if (!IS_ROOT(dentry)) { + di_read_lock_parent(parent, !AuLock_IR); + err = AuWbrCopyup(sbinfo, dentry); + di_read_unlock(parent, !AuLock_IR); + } else + err = AuWbrCopyup(sbinfo, dentry); + } + bcpup = err; + if (unlikely(err < 0)) + goto out; + } + } else { + bcpup = args->force_btgt; + AuDebugOn(au_test_ro(sb, bcpup, dentry->d_inode)); + } + LKTRTrace("bstart %d, bcpup %d\n", bstart, bcpup); + if (bstart < bcpup) + au_update_dbrange(dentry, /*do_put_zero*/1); + + err = bcpup; + if (bcpup == bstart) + goto out; /* success */ + + /* copyup the new parent into the branch we process */ + if (add_entry) { + au_update_dbstart(dentry); + IMustLock(parent->d_inode); + DiMustWriteLock(parent); + IiMustWriteLock(parent->d_inode); + } else + di_write_lock_parent(parent); + + err = 0; + if (!au_h_dptr(parent, bcpup)) { + if (bstart < bcpup) + err = au_cpdown_dirs(dentry, bcpup); + else + err = au_cpup_dirs(dentry, bcpup); + } + if (!err && add_entry) { + struct dentry *h_parent; + struct inode *h_dir; + + h_parent = au_h_dptr(parent, bcpup); + AuDebugOn(!h_parent); + h_dir = h_parent->d_inode; + AuDebugOn(!h_dir); + vfsub_i_lock_nested(h_parent->d_inode, AuLsc_I_PARENT); + err = au_lkup_neg(dentry, bcpup); + vfsub_i_unlock(h_parent->d_inode); + if (bstart < bcpup && au_dbstart(dentry) < 0) { + au_set_dbstart(dentry, 0); + au_update_dbrange(dentry, /*do_put_zero*/0); + } + } + + if (!add_entry) + di_write_unlock(parent); + if (!err) + err = bcpup; /* success */ + out: + dput(parent); + LKTRTrace("err %d\n", err); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct dentry *au_do_pinned_h_parent(struct au_pin1 *pin) +{ + if (pin && pin->parent) + return au_h_dptr(pin->parent, pin->bindex); + return NULL; +} + +void au_do_unpin(struct au_pin1 *p, struct au_pin1 *gp) +{ + if (p->dentry) + LKTRTrace("%.*s\n", AuDLNPair(p->dentry)); + LKTRTrace("p{%d, %d, %d, %d, 0x%x}, gp %p\n", + p->lsc_di, p->lsc_hi, !!p->parent, !!p->h_dir, p->flags, gp); + + if (au_ftest_pin(p->flags, MNT_WRITE)) + au_br_drop_write(au_sbr(p->dentry->d_sb, p->bindex)); + if (au_ftest_pin(p->flags, VFS_RENAME)) + vfsub_unlock_rename_mutex(au_sbr_sb(p->dentry->d_sb, + p->bindex)); + if (!p->h_dir) + return; + + vfsub_i_unlock(p->h_dir); + if (gp) + au_do_unpin(gp, NULL); + if (!au_ftest_pin(p->flags, DI_LOCKED)) + di_read_unlock(p->parent, AuLock_IR); + iput(p->h_dir); + dput(p->parent); + p->parent = NULL; + p->h_dir = NULL; +} + +int au_do_pin(struct au_pin1 *p, struct au_pin1 *gp) +{ + int err; + struct dentry *h_dentry; + + LKTRTrace("%.*s, %d, b%d, 0x%x\n", + AuDLNPair(p->dentry), !!gp, p->bindex, p->flags); + AuDebugOn(au_ftest_pin(p->flags, DO_GPARENT) && !gp); + /* AuDebugOn(!do_gp && gp); */ + + err = 0; + if (IS_ROOT(p->dentry)) { + if (au_ftest_pin(p->flags, VFS_RENAME)) + vfsub_lock_rename_mutex(au_sbr_sb(p->dentry->d_sb, + p->bindex)); + if (au_ftest_pin(p->flags, MNT_WRITE)) { + err = au_br_want_write(au_sbr(p->dentry->d_sb, + p->bindex)); + if (unlikely(err)) + au_fclr_pin(p->flags, MNT_WRITE); + } + goto out; + } + + h_dentry = NULL; + if (p->bindex <= au_dbend(p->dentry)) + h_dentry = au_h_dptr(p->dentry, p->bindex); + + p->parent = dget_parent(p->dentry); + if (!au_ftest_pin(p->flags, DI_LOCKED)) + di_read_lock(p->parent, AuLock_IR, p->lsc_di); + else + DiMustAnyLock(p->parent); + AuDebugOn(!p->parent->d_inode); + p->h_dir = au_igrab(au_h_iptr(p->parent->d_inode, p->bindex)); + /* udba case */ + if (unlikely(au_ftest_pin(p->flags, VERIFY) && !p->h_dir)) { + err = -EIO; + if (!au_ftest_pin(p->flags, DI_LOCKED)) + di_read_unlock(p->parent, AuLock_IR); + dput(p->parent); + p->parent = NULL; + goto out; + } + + if (au_ftest_pin(p->flags, DO_GPARENT)) { + gp->dentry = p->parent; + err = au_do_pin(gp, NULL); + if (unlikely(err)) + gp->dentry = NULL; + } + if (au_ftest_pin(p->flags, VFS_RENAME)) + vfsub_lock_rename_mutex(p->h_dir->i_sb); + vfsub_i_lock_nested(p->h_dir, p->lsc_hi); + if (!err) { + /* todo: call d_revalidate() here? */ +#if 0 + if (h_dentry && h_dentry->d_op && h_dentry->d_op->d_revalidate + && !h_dentry->d_op->d_revalidate(h_dentry, NULL)) + err = -EIO; +#endif + if (!h_dentry + || !au_ftest_pin(p->flags, VERIFY) + || !au_verify_parent(h_dentry, p->h_dir)) { + if (au_ftest_pin(p->flags, MNT_WRITE)) { + err = au_br_want_write(au_sbr(p->dentry->d_sb, + p->bindex)); + if (unlikely(err)) + au_fclr_pin(p->flags, MNT_WRITE); + } + if (!err) + goto out; /* success */ + } else + err = -EIO; + } + + AuDbgDentry(p->dentry); + AuDbgDentry(h_dentry); + AuDbgDentry(p->parent); + AuDbgInode(p->h_dir); + if (h_dentry) + AuDbgDentry(h_dentry->d_parent); + + au_do_unpin(p, gp); + if (au_ftest_pin(p->flags, DO_GPARENT)) + gp->dentry = NULL; + + out: + AuTraceErr(err); + return err; +} + +void au_pin_init(struct au_pin *args, struct dentry *dentry, + aufs_bindex_t bindex, int lsc_di, int lsc_hi, + unsigned char flags) +{ + struct au_pin1 *p; + unsigned char f; + + AuTraceEnter(); + + memset(args, 0, sizeof(*args)); + p = args->pin + AuPin_PARENT; + p->dentry = dentry; + p->lsc_di = lsc_di; + p->lsc_hi = lsc_hi; + p->flags = flags; + p->bindex = bindex; + if (!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE)) + au_fset_pin(p->flags, VERIFY); + if (!au_ftest_pin(flags, DO_GPARENT)) + return; + + f = p->flags; + p = au_pin_gp(args); + if (p) { + p->lsc_di = lsc_di + 1; /* child first */ + p->lsc_hi = lsc_hi - 1; /* parent first */ + p->bindex = bindex; + p->flags = f & ~(AuPin_MNT_WRITE + | AuPin_DO_GPARENT + | AuPin_DI_LOCKED); + } +} + +int au_pin(struct au_pin *args, struct dentry *dentry, aufs_bindex_t bindex, + unsigned char flags) +{ + LKTRTrace("%.*s, b%d, 0x%x\n", + AuDLNPair(dentry), bindex, flags); + + au_pin_init(args, dentry, bindex, AuLsc_DI_PARENT, AuLsc_I_PARENT2, + flags); + return au_do_pin(args->pin + AuPin_PARENT, au_pin_gp(args)); +} + +/* ---------------------------------------------------------------------- */ + +struct au_icpup_args { + unsigned char isdir, hinotify, did_cpup; /* flags */ + unsigned char pin_flags; + aufs_bindex_t btgt; + struct au_pin pin; + struct au_hin_ignore ign[2]; + struct vfsub_args vargs; + struct dentry *h_dentry; + struct inode *h_inode; +}; + +/* todo: refine it */ +static int au_lock_and_icpup(struct dentry *dentry, loff_t sz, + struct au_icpup_args *a) +{ + int err; + aufs_bindex_t bstart; + struct super_block *sb; + struct dentry *hi_wh, *parent; + struct inode *inode; + struct au_wr_dir_args wr_dir_args = { + .force_btgt = -1, + .flags = 0 + }; + + LKTRTrace("%.*s, %lld\n", AuDLNPair(dentry), sz); + + di_write_lock_child(dentry); + bstart = au_dbstart(dentry); + sb = dentry->d_sb; + inode = dentry->d_inode; + a->isdir = !!S_ISDIR(inode->i_mode); + if (a->isdir) + au_fset_wrdir(wr_dir_args.flags, ISDIR); + /* plink or hi_wh() */ + if (bstart != au_ibstart(inode)) + wr_dir_args.force_btgt = au_ibstart(inode); + err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args); + if (unlikely(err < 0)) + goto out_dentry; + a->btgt = err; + a->did_cpup = (err != bstart); + err = 0; + + /* crazy udba locks */ + a->pin_flags = AuPin_MNT_WRITE; + a->hinotify = !!au_opt_test(au_mntflags(sb), UDBA_INOTIFY); + if (a->hinotify) + au_fset_pin(a->pin_flags, DO_GPARENT); + parent = NULL; + if (!IS_ROOT(dentry)) { + au_fset_pin(a->pin_flags, DI_LOCKED); + parent = dget_parent(dentry); + di_write_lock_parent(parent); + } + err = au_pin(&a->pin, dentry, a->btgt, a->pin_flags); + if (unlikely(err)) { + if (parent) { + di_write_unlock(parent); + dput(parent); + } + goto out_dentry; + } + a->h_dentry = au_h_dptr(dentry, bstart); + a->h_inode = a->h_dentry->d_inode; + AuDebugOn(!a->h_inode); + vfsub_i_lock_nested(a->h_inode, AuLsc_I_CHILD); + if (!a->did_cpup) { + au_unpin_gp(&a->pin); + if (parent) { + au_pin_set_parent_lflag(&a->pin, /*lflag*/0); + di_downgrade_lock(parent, AuLock_IR); + dput(parent); + } + goto out; /* success */ + } + + hi_wh = NULL; + if (!d_unhashed(dentry)) { + if (parent) { + au_pin_set_parent_lflag(&a->pin, /*lflag*/0); + di_downgrade_lock(parent, AuLock_IR); + dput(parent); + } + err = au_sio_cpup_simple(dentry, a->btgt, sz, AuCpup_DTIME); + if (!err) + a->h_dentry = au_h_dptr(dentry, a->btgt); + } else { + hi_wh = au_hi_wh(inode, a->btgt); + if (!hi_wh) { + err = au_sio_cpup_wh(dentry, a->btgt, sz, + /*file*/NULL); + if (!err) + hi_wh = au_hi_wh(inode, a->btgt); + /* todo: revalidate hi_wh? */ + } + if (parent) { + au_pin_set_parent_lflag(&a->pin, /*lflag*/0); + di_downgrade_lock(parent, AuLock_IR); + dput(parent); + } + if (!hi_wh) + a->h_dentry = au_h_dptr(dentry, a->btgt); + else + a->h_dentry = hi_wh; /* do not dget here */ + } + + vfsub_i_unlock(a->h_inode); + a->h_inode = a->h_dentry->d_inode; + AuDebugOn(!a->h_inode); + if (!err) { + vfsub_i_lock_nested(a->h_inode, AuLsc_I_CHILD); + au_unpin_gp(&a->pin); + goto out; /* success */ + } + + au_unpin(&a->pin); + + out_dentry: + di_write_unlock(dentry); + out: + AuTraceErr(err); + return err; +} + +static int aufs_setattr(struct dentry *dentry, struct iattr *ia) +{ + int err; + struct inode *inode; + struct super_block *sb; + __u32 events; + struct file *file; + loff_t sz; + struct au_icpup_args *a; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + inode = dentry->d_inode; + IMustLock(inode); + + err = -ENOMEM; + a = kzalloc(sizeof(*a), GFP_NOFS); + if (unlikely(!a)) + goto out; + + file = NULL; + sb = dentry->d_sb; + si_read_lock(sb, AuLock_FLUSH); + vfsub_args_init(&a->vargs, a->ign, au_test_dlgt(au_mntflags(sb)), 0); + + if (ia->ia_valid & ATTR_FILE) { + /* currently ftruncate(2) only */ + file = ia->ia_file; + fi_write_lock(file); + ia->ia_file = au_h_fptr(file, au_fbstart(file)); + } + + sz = -1; + if ((ia->ia_valid & ATTR_SIZE) + && ia->ia_size < i_size_read(inode)) + sz = ia->ia_size; + err = au_lock_and_icpup(dentry, sz, a); + if (unlikely(err < 0)) + goto out_si; + if (a->did_cpup) { + ia->ia_file = NULL; + ia->ia_valid &= ~ATTR_FILE; + } + + if ((ia->ia_valid & ATTR_SIZE) + && ia->ia_size < i_size_read(inode)) { + err = vmtruncate(inode, ia->ia_size); + if (unlikely(err)) + goto out_unlock; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) + ia->ia_valid &= ~ATTR_MODE; +#endif + + events = 0; + if (a->hinotify) { + events = vfsub_events_notify_change(ia); + if (events) { + if (a->isdir) + vfsub_ign_hinode(&a->vargs, events, + au_hi(inode, a->btgt)); + vfsub_ign_hinode(&a->vargs, events, + au_pinned_hdir(&a->pin)); + } + } + err = vfsub_notify_change(a->h_dentry, ia, &a->vargs); + if (!err) + au_cpup_attr_changeable(inode); + + out_unlock: + vfsub_i_unlock(a->h_inode); + au_unpin(&a->pin); + di_write_unlock(dentry); + out_si: + if (file) { + fi_write_unlock(file); + ia->ia_file = file; + ia->ia_valid |= ATTR_FILE; + } + si_read_unlock(sb); + kfree(a); + out: + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int h_readlink(struct dentry *dentry, int bindex, char __user *buf, + int bufsiz) +{ + struct super_block *sb; + struct dentry *h_dentry; + + LKTRTrace("%.*s, b%d, %d\n", AuDLNPair(dentry), bindex, bufsiz); + + h_dentry = au_h_dptr(dentry, bindex); + if (unlikely(/* !h_dentry + || !h_dentry->d_inode + || */ + !h_dentry->d_inode->i_op + || !h_dentry->d_inode->i_op->readlink)) + return -EINVAL; + + sb = dentry->d_sb; + if (!au_test_ro(sb, bindex, dentry->d_inode)) { + touch_atime(au_sbr_mnt(sb, bindex), h_dentry); + au_update_fuse_h_inode(NULL, h_dentry); /*ignore*/ + dentry->d_inode->i_atime = h_dentry->d_inode->i_atime; + } + return h_dentry->d_inode->i_op->readlink(h_dentry, buf, bufsiz); +} + +static int aufs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) +{ + int err; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), bufsiz); + + aufs_read_lock(dentry, AuLock_IR); + err = h_readlink(dentry, au_dbstart(dentry), buf, bufsiz); + aufs_read_unlock(dentry, AuLock_IR); + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} + +static void *aufs_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + int err; + char *buf; + mm_segment_t old_fs; + + LKTRTrace("%.*s, nd %.*s\n", + AuDLNPair(dentry), AuDLNPair(nd->dentry)); + + err = -ENOMEM; + buf = __getname(); + if (unlikely(!buf)) + goto out; + + aufs_read_lock(dentry, AuLock_IR); + old_fs = get_fs(); + set_fs(KERNEL_DS); + err = h_readlink(dentry, au_dbstart(dentry), (char __user *)buf, + PATH_MAX); + set_fs(old_fs); + aufs_read_unlock(dentry, AuLock_IR); + + if (err >= 0) { + buf[err] = 0; + /* will be freed by put_link */ + nd_set_link(nd, buf); + return NULL; /* success */ + } + __putname(buf); + + out: + path_release(nd); + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return ERR_PTR(err); +} + +static void aufs_put_link(struct dentry *dentry, struct nameidata *nd, + void *cookie) +{ + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + __putname(nd_get_link(nd)); +} + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) +static void aufs_truncate_range(struct inode *inode, loff_t start, loff_t end) +{ + AuUnsupport(); +} +#endif + +/* ---------------------------------------------------------------------- */ + +struct inode_operations aufs_symlink_iop = { + .permission = aufs_permission, + .setattr = aufs_setattr, +#ifdef CONFIG_AUFS_GETATTR + .getattr = aufs_getattr, +#endif + + .readlink = aufs_readlink, + .follow_link = aufs_follow_link, + .put_link = aufs_put_link +}; + +struct inode_operations aufs_dir_iop = { + .create = aufs_create, + .lookup = aufs_lookup, + .link = aufs_link, + .unlink = aufs_unlink, + .symlink = aufs_symlink, + .mkdir = aufs_mkdir, + .rmdir = aufs_rmdir, + .mknod = aufs_mknod, + .rename = aufs_rename, + + .permission = aufs_permission, + .setattr = aufs_setattr, +#ifdef CONFIG_AUFS_GETATTR + .getattr = aufs_getattr, +#endif + +#if 0 /* reserved for future use */ + .setxattr = aufs_setxattr, + .getxattr = aufs_getxattr, + .listxattr = aufs_listxattr, + .removexattr = aufs_removexattr, +#endif + +#if 0 //LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) + .fallocate = aufs_fallocate +#endif +}; + +struct inode_operations aufs_iop = { + .permission = aufs_permission, + .setattr = aufs_setattr, +#ifdef CONFIG_AUFS_GETATTR + .getattr = aufs_getattr, +#endif + +#if 0 /* reserved for future use */ + .setxattr = aufs_setxattr, + .getxattr = aufs_getxattr, + .listxattr = aufs_listxattr, + .removexattr = aufs_removexattr, +#endif + + //void (*truncate) (struct inode *); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) + .truncate_range = aufs_truncate_range, +#endif + +#if 0 //LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) + .fallocate = aufs_fallocate +#endif +}; diff --git a/fs/aufs/i_op_add.c b/fs/aufs/i_op_add.c new file mode 100644 index 0000000..cebc07f --- /dev/null +++ b/fs/aufs/i_op_add.c @@ -0,0 +1,757 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode operations (add entry) + * + * $Id: i_op_add.c,v 1.74 2009/01/26 06:24:01 sfjro Exp $ + */ + +#include "aufs.h" + +/* + * final procedure of adding a new entry, except link(2). + * remove whiteout, instantiate, copyup the parent dir's times and size + * and update version. + * if it failed, re-create the removed whiteout. + */ +static int epilog(struct inode *dir, aufs_bindex_t bindex, + struct dentry *wh_dentry, struct dentry *dentry) +{ + int err, rerr; + aufs_bindex_t bwh; + struct inode *inode, *h_dir; + struct dentry *wh; + struct au_ndx ndx; + struct super_block *sb; + + LKTRTrace("wh %p, %.*s\n", wh_dentry, AuDLNPair(dentry)); + + sb = dentry->d_sb; + bwh = -1; + if (wh_dentry) { + h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ + IMustLock(h_dir); + AuDebugOn(au_h_iptr(dir, bindex) != h_dir); + bwh = au_dbwh(dentry); + err = au_wh_unlink_dentry(au_hi(dir, bindex), wh_dentry, dentry, + /*dlgt*/0); + if (unlikely(err)) + goto out; + } + + inode = au_new_inode(dentry, /*must_new*/1); + if (!IS_ERR(inode)) { + d_instantiate(dentry, inode); + dir = dentry->d_parent->d_inode; /* dir inode is locked */ + IMustLock(dir); + /* or always cpup dir mtime? */ + if (au_ibstart(dir) == au_dbstart(dentry)) + au_cpup_attr_timesizes(dir); + dir->i_version++; + return 0; /* success */ + } + + err = PTR_ERR(inode); + if (!wh_dentry) + goto out; + + /* revert */ + ndx.flags = 0; + if (au_test_dlgt(au_mntflags(sb))) + au_fset_ndx(ndx.flags, DLGT); + ndx.nfsmnt = au_nfsmnt(sb, bwh); + ndx.nd = NULL; + /* ndx.br = NULL; */ + /* dir inode is locked */ + wh = au_wh_create(dentry, bwh, wh_dentry->d_parent, &ndx); + rerr = PTR_ERR(wh); + if (IS_ERR(wh)) { + AuIOErr("%.*s reverting whiteout failed(%d, %d)\n", + AuDLNPair(dentry), err, rerr); + err = -EIO; + } else + dput(wh); + + out: + AuTraceErr(err); + return err; +} + +/* + * simple tests for the adding inode operations. + * following the checks in vfs, plus the parent-child relationship. + */ +int au_may_add(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_parent, int isdir, struct au_ndx *ndx) +{ + int err, exist; + struct dentry *h_dentry; + struct inode *h_inode; + umode_t h_mode; + + LKTRTrace("%.*s/%.*s, b%d, dir %d\n", + AuDLNPair(h_parent), AuDLNPair(dentry), bindex, isdir); + + exist = !!dentry->d_inode; + h_dentry = au_h_dptr(dentry, bindex); + h_inode = h_dentry->d_inode; + if (!exist) { + err = -EEXIST; + if (unlikely(h_inode)) + goto out; + } else { + /* rename(2) case */ + err = -EIO; + if (unlikely(!h_inode || !h_inode->i_nlink)) + goto out; + + h_mode = h_inode->i_mode; + if (!isdir) { + err = -EISDIR; + if (unlikely(S_ISDIR(h_mode))) + goto out; + } else if (unlikely(!S_ISDIR(h_mode))) { + err = -ENOTDIR; + goto out; + } + } + + err = -EIO; + /* expected parent dir is locked */ + if (unlikely(h_parent != h_dentry->d_parent)) + goto out; + err = 0; + + if (au_opt_test(au_mntflags(dentry->d_sb), UDBA_INOTIFY)) { + struct dentry *h_latest; + struct qstr *qstr = &dentry->d_name; + + err = -EACCES; + if (unlikely(au_test_h_perm + (h_parent->d_inode, MAY_EXEC | MAY_WRITE, + au_ftest_ndx(ndx->flags, DLGT)))) + goto out; + + h_latest = au_sio_lkup_one(qstr->name, h_parent, qstr->len, + ndx); + err = PTR_ERR(h_latest); + if (IS_ERR(h_latest)) + goto out; + err = -EIO; + dput(h_latest); + /* fuse d_revalidate always return 0 for negative dentries */ + if (h_latest == h_dentry || au_test_fuse(h_dentry->d_sb)) + err = 0; + } + + out: + AuTraceErr(err); + return err; +} + +/* + * initial procedure of adding a new entry. + * prepare writable branch and the parent dir, lock it, + * lookup whiteout for the new entry. + */ +static struct dentry* +lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt, + struct dentry *src_dentry, struct au_pin *pin, + struct au_wr_dir_args *wr_dir_args) +{ + struct dentry *wh_dentry, *h_parent; + struct super_block *sb; + int err; + unsigned int mnt_flags; + unsigned char pin_flags; + aufs_bindex_t bstart, bcpup; + struct au_ndx ndx; + + LKTRTrace("%.*s, src %p\n", AuDLNPair(dentry), src_dentry); + + bstart = au_dbstart(dentry); + err = au_wr_dir(dentry, src_dentry, wr_dir_args); + bcpup = err; + wh_dentry = ERR_PTR(err); + if (unlikely(err < 0)) + goto out; + + sb = dentry->d_sb; + mnt_flags = au_mntflags(sb); + pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (dt && au_opt_test(mnt_flags, UDBA_INOTIFY)) + au_fset_pin(pin_flags, DO_GPARENT); + err = au_pin(pin, dentry, bcpup, pin_flags); + wh_dentry = ERR_PTR(err); + if (unlikely(err)) + goto out; + + ndx.nfsmnt = au_nfsmnt(sb, bcpup); + ndx.flags = 0; + if (au_test_dlgt(mnt_flags)) + au_fset_ndx(ndx.flags, DLGT); + ndx.nd = NULL; + /* ndx.br = NULL; */ + /* ndx.nd_file = NULL; */ + + h_parent = au_pinned_h_parent(pin); + if (!au_opt_test(mnt_flags, UDBA_NONE) && au_dbstart(dentry) == bcpup) { + struct nameidata nd; + + if (ndx.nfsmnt) { + /* todo: dirty? */ + ndx.nd = &nd; + ndx.br = au_sbr(sb, bcpup); + memset(&nd, 0, sizeof(nd)); + nd.flags = LOOKUP_CREATE; + nd.intent.open.flags = O_EXCL; + } + err = au_may_add(dentry, bcpup, h_parent, + au_ftest_wrdir(wr_dir_args->flags, ISDIR), + &ndx); + wh_dentry = ERR_PTR(err); + if (unlikely(err)) + goto out_unpin; + ndx.nd = NULL; + ndx.br = NULL; + } + + if (dt) + au_dtime_store(dt, au_pinned_parent(pin), h_parent, + au_pinned_hdir(pin), au_pinned_hgdir(pin)); + + wh_dentry = NULL; + if (/* bcpup != bstart || */ bcpup != au_dbwh(dentry)) + goto out; /* success */ + + wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, &ndx); + + out_unpin: + if (IS_ERR(wh_dentry)) + au_unpin(pin); + out: + AuTraceErrPtr(wh_dentry); + return wh_dentry; +} + +/* ---------------------------------------------------------------------- */ + +enum { Mknod, Symlink, Creat }; +struct simple_arg { + int type; + union { + struct { + int mode; + struct nameidata *nd; + } c; + struct { + const char *symname; + } s; + struct { + int mode; + dev_t dev; + } m; + } u; +}; + +static int add_simple(struct inode *dir, struct dentry *dentry, + struct simple_arg *arg) +{ + int err; + struct dentry *h_dentry, *wh_dentry, *parent; + struct inode *h_dir; + struct au_dtime dt; + struct vfsub_args vargs; + struct super_block *sb; + aufs_bindex_t bstart; + unsigned char created; + struct au_hin_ignore ign; + struct au_pin pin; + struct au_wr_dir_args wr_dir_args = { + .force_btgt = -1, + .flags = AuWrDir_ADD_ENTRY + }; + + LKTRTrace("type %d, %.*s\n", arg->type, AuDLNPair(dentry)); + IMustLock(dir); + + sb = dir->i_sb; + parent = dentry->d_parent; /* dir inode is locked */ + aufs_read_lock(dentry, AuLock_DW); + vfsub_args_init(&vargs, &ign, !!au_test_dlgt(au_mntflags(sb)), 0); + di_write_lock_parent(parent); + wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL, &pin, + &wr_dir_args); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out; + + bstart = au_dbstart(dentry); + h_dentry = au_h_dptr(dentry, bstart); + h_dir = au_pinned_h_dir(&pin); + vfsub_ign_hinode(&vargs, IN_CREATE, au_pinned_hdir(&pin)); + + switch (arg->type) { + case Creat: + AuDebugOn(au_test_nfs(h_dir->i_sb) && !arg->u.c.nd); + err = au_h_create(h_dir, h_dentry, arg->u.c.mode, &vargs, + arg->u.c.nd, au_nfsmnt(sb, bstart)); + break; + case Symlink: + err = vfsub_symlink(h_dir, h_dentry, arg->u.s.symname, + S_IALLUGO, &vargs); + break; + case Mknod: + err = vfsub_mknod(h_dir, h_dentry, arg->u.m.mode, arg->u.m.dev, + &vargs); + break; + default: + BUG(); + } + created = !err; + if (!err) + err = epilog(dir, bstart, wh_dentry, dentry); + + /* revert */ + if (unlikely(created && err && h_dentry->d_inode)) { + int rerr; + vfsub_args_reinit(&vargs); + vfsub_ign_hinode(&vargs, IN_DELETE, au_pinned_hdir(&pin)); + rerr = vfsub_unlink(h_dir, h_dentry, &vargs); + if (rerr) { + AuIOErr("%.*s revert failure(%d, %d)\n", + AuDLNPair(dentry), err, rerr); + err = -EIO; + } + /* todo: inotify will be fired to the grand parent dir? */ + au_dtime_revert(&dt); + d_drop(dentry); + } + + au_unpin(&pin); + dput(wh_dentry); + + out: + if (unlikely(err)) { + au_update_dbstart(dentry); + d_drop(dentry); + } + di_write_unlock(parent); + aufs_read_unlock(dentry, AuLock_DW); + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} + +int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) +{ + struct simple_arg arg = { + .type = Mknod, + .u.m = { + .mode = mode, + .dev = dev + } + }; + return add_simple(dir, dentry, &arg); +} + +int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) +{ + struct simple_arg arg = { + .type = Symlink, + .u.s.symname = symname + }; + return add_simple(dir, dentry, &arg); +} + +int aufs_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + struct simple_arg arg = { + .type = Creat, + .u.c = { + .mode = mode, + .nd = nd + } + }; + return add_simple(dir, dentry, &arg); +} + +/* ---------------------------------------------------------------------- */ + +struct au_link_args { + unsigned int mnt_flags; + unsigned char pin_flags; + aufs_bindex_t bdst, bsrc; + struct vfsub_args vargs; + struct au_pin pin; + struct au_hin_ignore ign; + struct dentry *h_dentry; + struct dentry *src_parent, *parent; +}; + +static int au_cpup_before_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry, struct au_link_args *a) +{ + int err; + struct inode *h_inode; + const int hinotify = au_opt_test(a->mnt_flags, UDBA_INOTIFY); + + LKTRTrace("src %.*s, i%lu, dst %.*s\n", + AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry)); + + di_read_lock_parent(a->src_parent, AuLock_IR); + err = au_test_and_cpup_dirs(src_dentry, a->bdst); + if (unlikely(err)) + goto out; + + AuDebugOn(au_dbstart(src_dentry) != a->bsrc); + h_inode = au_h_dptr(src_dentry, a->bsrc)->d_inode; + a->pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (hinotify) + au_fset_pin(a->pin_flags, DO_GPARENT); + err = au_pin(&a->pin, src_dentry, a->bdst, a->pin_flags); + if (unlikely(err)) + goto out; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + /* todo: no KEEPLINO because of noplink? */ + err = au_sio_cpup_simple(src_dentry, a->bdst, -1, + AuCpup_DTIME /* | AuCpup_KEEPLINO */); + vfsub_i_unlock(h_inode); + au_unpin(&a->pin); + + out: + di_read_unlock(a->src_parent, AuLock_IR); + AuTraceErr(err); + return err; +} + +static int au_cpup_or_link(struct dentry *src_dentry, struct au_link_args *a) +{ + int err; + struct inode *h_inode; + aufs_bindex_t bstart; + struct dentry *h_src_dentry; + + AuTraceEnter(); + AuDebugOn(au_dbstart(src_dentry) != a->bsrc); + + bstart = au_ibstart(src_dentry->d_inode); + h_inode = NULL; + if (bstart <= a->bdst) + h_inode = au_h_iptr(src_dentry->d_inode, a->bdst); + if (!h_inode || !h_inode->i_nlink) { + /* copyup src_dentry as the name of dentry. */ + au_set_dbstart(src_dentry, a->bdst); + au_set_h_dptr(src_dentry, a->bdst, dget(a->h_dentry)); + h_inode = au_h_dptr(src_dentry, a->bsrc)->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + err = au_sio_cpup_single(src_dentry, a->bdst, a->bsrc, -1, + AuCpup_KEEPLINO, a->parent); + vfsub_i_unlock(h_inode); + au_set_h_dptr(src_dentry, a->bdst, NULL); + au_set_dbstart(src_dentry, a->bsrc); + } else { + /* the inode of src_dentry already exists on a.bdst branch */ + h_src_dentry = d_find_alias(h_inode); + if (h_src_dentry) { + /* vfsub_args_reinit(&a->vargs); */ + vfsub_ign_hinode(&a->vargs, IN_CREATE, + au_pinned_hdir(&a->pin)); + err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), + a->h_dentry, &a->vargs); + dput(h_src_dentry); + } else { + AuIOErr("no dentry found for i%lu on b%d\n", + h_inode->i_ino, a->bdst); + err = -EIO; + } + } + + if (!err) + au_plink_append(src_dentry->d_sb, src_dentry->d_inode, + a->h_dentry, a->bdst); + + AuTraceErr(err); + return err; +} + +int aufs_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry) +{ + int err, rerr; + struct au_dtime dt; + struct super_block *sb; + struct au_link_args *a; + struct au_wr_dir_args wr_dir_args = { + /* .force_btgt = -1, */ + .flags = AuWrDir_ADD_ENTRY + }; + struct dentry *wh_dentry, *h_src_dentry; + struct inode *inode; + + LKTRTrace("src %.*s, i%lu, dst %.*s\n", + AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + inode = src_dentry->d_inode; + IMustLock(inode); + AuDebugOn(S_ISDIR(inode->i_mode)); + + err = -ENOMEM; + a = kzalloc(sizeof(*a), GFP_NOFS); + if (unlikely(!a)) + goto out; + + sb = dentry->d_sb; + a->parent = dentry->d_parent; /* dir inode is locked */ + aufs_read_and_write_lock2(dentry, src_dentry, /*AuLock_FLUSH*/0); + a->src_parent = dget_parent(src_dentry); + wr_dir_args.force_btgt = au_dbstart(src_dentry); + a->mnt_flags = au_mntflags(sb); + vfsub_args_init(&a->vargs, &a->ign, au_test_dlgt(a->mnt_flags), 0); + + di_write_lock_parent(a->parent); + wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt); + wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &a->pin, + &wr_dir_args); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out_unlock; + err = 0; + + a->bdst = au_dbstart(dentry); + a->h_dentry = au_h_dptr(dentry, a->bdst); + + /* todo: minor optimize, + their sb may be same while their bindex differs? */ + a->bsrc = au_dbstart(src_dentry); + if (au_opt_test(a->mnt_flags, PLINK)) { + if (a->bdst < a->bsrc + /* && h_src_dentry->d_sb != a->h_dentry->d_sb */) + err = au_cpup_or_link(src_dentry, a); + else { + h_src_dentry = au_h_dptr(src_dentry, a->bdst); + AuDebugOn(!h_src_dentry); + AuDebugOn(!h_src_dentry->d_inode); + vfsub_ign_hinode(&a->vargs, IN_CREATE, + au_pinned_hdir(&a->pin)); + err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), + a->h_dentry, &a->vargs); + } + } else { + /* + * copyup src_dentry to the branch we process, + * and then link(2) to it. + */ + if (a->bdst < a->bsrc + /* && h_src_dentry->d_sb != a->h_dentry->d_sb */) { + au_unpin(&a->pin); + di_write_unlock(a->parent); + err = au_cpup_before_link(src_dentry, dir, dentry, a); + if (!err) { + a->pin_flags + = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (au_opt_test(a->mnt_flags, UDBA_INOTIFY)) + au_fset_pin(a->pin_flags, DO_GPARENT); + di_write_lock_parent(a->parent); + err = au_pin(&a->pin, dentry, a->bdst, + a->pin_flags); + if (unlikely(err)) + goto out_wh; + } + } + if (!err) { + /* vfsub_args_reinit(&a->vargs); */ + vfsub_ign_hinode(&a->vargs, IN_CREATE, + au_pinned_hdir(&a->pin)); + h_src_dentry = au_h_dptr(src_dentry, a->bdst); + err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin), + a->h_dentry, &a->vargs); + } + } + if (unlikely(err)) + goto out_unpin; + + if (wh_dentry) { + err = au_wh_unlink_dentry(au_pinned_hdir(&a->pin), wh_dentry, + dentry, /*dlgt*/0); + if (unlikely(err)) + goto out_revert; + } + +#if 0 /* cannot support it */ + /* fuse has different memory inode for the same inode number */ + if (au_test_fuse(a->h_dentry->d_sb)) { + LKTRLabel(here); + d_drop(a->h_dentry); + /*d_drop(h_src_dentry); + d_drop(src_dentry);*/ + a->inode->i_nlink++; + a->inode->i_ctime = dir->i_ctime; + } +#endif + + dir->i_version++; + if (au_ibstart(dir) == au_dbstart(dentry)) + au_cpup_attr_timesizes(dir); + if (!d_unhashed(a->h_dentry) + /* || h_old_inode->i_nlink <= nlink */ + /* || SB_NFS(h_src_dentry->d_sb) */) { + d_instantiate(dentry, au_igrab(inode)); + inode->i_nlink++; + inode->i_ctime = dir->i_ctime; + } else + /* nfs case (< 2.6.15) */ + d_drop(dentry); + goto out_unpin; /* success */ + + out_revert: + vfsub_args_reinit(&a->vargs); + vfsub_ign_hinode(&a->vargs, IN_DELETE, au_pinned_hdir(&a->pin)); + rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), a->h_dentry, &a->vargs); + if (!rerr) + goto out_dt; + AuIOErr("%.*s reverting failed(%d, %d)\n", + AuDLNPair(dentry), err, rerr); + err = -EIO; + out_dt: + d_drop(dentry); + au_dtime_revert(&dt); + out_unpin: + au_unpin(&a->pin); + out_wh: + dput(wh_dentry); + out_unlock: + if (unlikely(err)) { + au_update_dbstart(dentry); + d_drop(dentry); + } + di_write_unlock(a->parent); + dput(a->src_parent); + aufs_read_and_write_unlock2(dentry, src_dentry); + kfree(a); + out: + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} + +int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + int err, rerr; + struct dentry *h_dentry, *wh_dentry, *parent, *opq_dentry; + struct inode *h_inode; + struct au_dtime dt; + aufs_bindex_t bindex; + unsigned char diropq, dlgt; + unsigned int mnt_flags; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct au_pin pin; + struct au_wr_dir_args wr_dir_args = { + .force_btgt = -1, + .flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR + }; + + LKTRTrace("i%lu, %.*s, mode 0%o\n", + dir->i_ino, AuDLNPair(dentry), mode); + IMustLock(dir); + + aufs_read_lock(dentry, AuLock_DW); + parent = dentry->d_parent; /* dir inode is locked */ + mnt_flags = au_mntflags(dentry->d_sb); + dlgt = !!au_test_dlgt(mnt_flags); + vfsub_args_init(&vargs, &ign, dlgt, 0); + + di_write_lock_parent(parent); + wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL, &pin, + &wr_dir_args); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out; + + bindex = au_dbstart(dentry); + h_dentry = au_h_dptr(dentry, bindex); + vfsub_ign_hinode(&vargs, IN_CREATE, au_pinned_hdir(&pin)); + err = vfsub_mkdir(au_pinned_h_dir(&pin), h_dentry, mode, &vargs); + if (unlikely(err)) + goto out_unlock; + + /* make the dir opaque */ + diropq = 0; + h_inode = h_dentry->d_inode; + if (wh_dentry || au_opt_test(mnt_flags, ALWAYS_DIROPQ)) { + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + opq_dentry = au_diropq_create(dentry, bindex, /*dlgt*/0); + vfsub_i_unlock(h_inode); + err = PTR_ERR(opq_dentry); + if (IS_ERR(opq_dentry)) + goto out_dir; + dput(opq_dentry); + diropq = 1; + } + + err = epilog(dir, bindex, wh_dentry, dentry); + if (!err) { + dir->i_nlink++; + goto out_unlock; /* success */ + } + + /* revert */ + if (diropq) { + LKTRLabel(revert opq); + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + rerr = au_diropq_remove(dentry, bindex, dlgt); + vfsub_i_unlock(h_inode); + if (rerr) { + AuIOErr("%.*s reverting diropq failed(%d, %d)\n", + AuDLNPair(dentry), err, rerr); + err = -EIO; + } + } + + out_dir: + LKTRLabel(revert dir); + vfsub_args_reinit(&vargs); + vfsub_ign_hinode(&vargs, IN_DELETE, au_pinned_hdir(&pin)); + rerr = vfsub_rmdir(au_pinned_h_dir(&pin), h_dentry, &vargs); + if (rerr) { + AuIOErr("%.*s reverting dir failed(%d, %d)\n", + AuDLNPair(dentry), err, rerr); + err = -EIO; + } + d_drop(dentry); + au_dtime_revert(&dt); + out_unlock: + au_unpin(&pin); + dput(wh_dentry); + out: + if (unlikely(err)) { + au_update_dbstart(dentry); + d_drop(dentry); + } + di_write_unlock(parent); + aufs_read_unlock(dentry, AuLock_DW); + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} diff --git a/fs/aufs/i_op_del.c b/fs/aufs/i_op_del.c new file mode 100644 index 0000000..e2ce684 --- /dev/null +++ b/fs/aufs/i_op_del.c @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode operations (del entry) + * + * $Id: i_op_del.c,v 1.71 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* returns, + * 0: wh is unnecessary + * plus: wh is necessary + * minus: error + */ +int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup) +{ + int need_wh, err; + aufs_bindex_t bstart; + struct dentry *h_dentry; + struct super_block *sb; + + LKTRTrace("%.*s, isdir %d, *bcpup %d\n", + AuDLNPair(dentry), isdir, *bcpup); + sb = dentry->d_sb; + + bstart = au_dbstart(dentry); + LKTRTrace("bcpup %d, bstart %d\n", *bcpup, bstart); + h_dentry = au_h_dptr(dentry, bstart); + if (*bcpup < 0) { + *bcpup = bstart; + if (au_test_ro(sb, bstart, dentry->d_inode)) { + err = AuWbrCopyup(au_sbi(sb), dentry); + *bcpup = err; + if (unlikely(err < 0)) + goto out; + } + } else + AuDebugOn(bstart < *bcpup + || au_test_ro(sb, *bcpup, dentry->d_inode)); + LKTRTrace("bcpup %d, bstart %d\n", *bcpup, bstart); + + if (*bcpup != bstart) { + err = au_cpup_dirs(dentry, *bcpup); + if (unlikely(err)) + goto out; + need_wh = 1; + } else { + aufs_bindex_t old_bend, new_bend, bdiropq = -1; + old_bend = au_dbend(dentry); + if (isdir) { + bdiropq = au_dbdiropq(dentry); + au_set_dbdiropq(dentry, -1); + } + need_wh = au_lkup_dentry(dentry, bstart + 1, /*type*/0, + /*nd*/NULL); + err = need_wh; + if (isdir) + au_set_dbdiropq(dentry, bdiropq); + if (unlikely(err < 0)) + goto out; + new_bend = au_dbend(dentry); + if (!need_wh && old_bend != new_bend) { + au_set_h_dptr(dentry, new_bend, NULL); + au_set_dbend(dentry, old_bend); +#if 0 /* todo: remove this? */ + } else if (!au_h_dptr(dentry, new_bend)->d_inode) { + LKTRTrace("negative\n"); + au_set_h_dptr(dentry, new_bend, NULL); + au_set_dbend(dentry, old_bend); + need_wh = 0; +#endif + } + } + LKTRTrace("need_wh %d\n", need_wh); + err = need_wh; + + out: + AuTraceErr(err); + return err; +} + +/* + * simple tests for the removal inode operations. + * following the checks in vfs, plus the parent-child relationship. + */ +int au_may_del(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_parent, int isdir, struct au_ndx *ndx) +{ + int err, exist; + struct super_block *sb; + struct dentry *h_dentry; + struct inode *h_inode; + umode_t h_mode; + + LKTRTrace("%.*s/%.*s, b%d, dir %d\n", + AuDLNPair(h_parent), AuDLNPair(dentry), bindex, isdir); + + sb = dentry->d_sb; + exist = !!dentry->d_inode; + h_dentry = au_h_dptr(dentry, bindex); + h_inode = h_dentry->d_inode; + if (exist) { + err = -ENOENT; + if (unlikely(!h_inode || !h_inode->i_nlink)) + goto out; + + h_mode = h_inode->i_mode; + if (!isdir) { + err = -EISDIR; + if (unlikely(S_ISDIR(h_mode))) + goto out; + } else if (unlikely(!S_ISDIR(h_mode))) { + err = -ENOTDIR; + goto out; + } + } else { + /* rename(2) case */ + err = -EIO; + if (unlikely(h_inode)) + goto out; + } + + err = -ENOENT; + /* expected parent dir is locked */ + if (unlikely(h_parent != h_dentry->d_parent)) + goto out; + err = 0; + + /* + * some filesystem may unlink a dir and corrupt its consistency. + * so let's try heavy test. + */ + if (1 /*au_opt_test(au_mntflags(sb), UDBA_INOTIFY)*/) { + struct dentry *h_latest; + struct qstr *qstr = &dentry->d_name; + + err = -EACCES; + if (unlikely(au_test_h_perm(h_parent->d_inode, + MAY_EXEC | MAY_WRITE, + au_ftest_ndx(ndx->flags, DLGT)))) + goto out; + + h_latest = au_sio_lkup_one(qstr->name, h_parent, qstr->len, + ndx); + err = -EIO; + if (IS_ERR(h_latest)) + goto out; + dput(h_latest); + if (h_latest == h_dentry) + err = 0; + } + + out: + AuTraceErr(err); + return err; +} + +static struct dentry * +lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup, + struct au_dtime *dt, struct au_pin *pin) +{ + struct dentry *wh_dentry, *h_parent; + struct super_block *sb; + int err, need_wh; + unsigned int mnt_flags; + unsigned char pin_flags; + aufs_bindex_t bcpup; + struct au_ndx ndx; + + LKTRTrace("%.*s, isdir %d\n", AuDLNPair(dentry), isdir); + + need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup); + err = need_wh; + wh_dentry = ERR_PTR(err); + if (unlikely(err < 0)) + goto out; + + sb = dentry->d_sb; + mnt_flags = au_mntflags(sb); + bcpup = *rbcpup; + pin_flags = AuPin_DI_LOCKED | AuPin_MNT_WRITE; + if (au_opt_test(mnt_flags, UDBA_INOTIFY)) + au_fset_pin(pin_flags, DO_GPARENT); + err = au_pin(pin, dentry, bcpup, pin_flags); + wh_dentry = ERR_PTR(err); + if (unlikely(err)) + goto out; + h_parent = au_pinned_h_parent(pin); + if (!au_opt_test(mnt_flags, UDBA_NONE) && au_dbstart(dentry) == bcpup) { + ndx.nfsmnt = au_nfsmnt(sb, bcpup); + ndx.flags = 0; + if (au_test_dlgt(mnt_flags)) + au_fset_ndx(ndx.flags, DLGT); + ndx.nd = NULL; + /* ndx.br = au_sbr(sb, bcpup); */ + /* ndx.nd_file = NULL; */ + err = au_may_del(dentry, bcpup, h_parent, isdir, &ndx); + wh_dentry = ERR_PTR(err); + if (unlikely(err)) + goto out_unpin; + } + + au_dtime_store(dt, au_pinned_parent(pin), h_parent, au_pinned_hdir(pin), + au_pinned_hgdir(pin)); + wh_dentry = NULL; + if (!need_wh) + goto out; /* success, no need to create whiteout */ + + ndx.nfsmnt = au_nfsmnt(sb, bcpup); + ndx.flags = 0; + if (au_test_dlgt(mnt_flags)) + au_fset_ndx(ndx.flags, DLGT); + ndx.nd = NULL; + /* ndx.br = NULL; */ + wh_dentry = au_wh_create(dentry, bcpup, h_parent, &ndx); + if (!IS_ERR(wh_dentry)) + goto out; /* success */ + /* returns with the parent is locked and wh_dentry is DGETed */ + + out_unpin: + au_unpin(pin); + out: + AuTraceErrPtr(wh_dentry); + return wh_dentry; +} + +static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex, + struct au_nhash *whlist, struct inode *dir) +{ + int rmdir_later, err; + struct dentry *h_dentry; + struct inode *inode, *h_inode; + struct super_block *sb; + + LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bindex); + + inode = NULL; + h_inode = NULL; + sb = dentry->d_sb; + if (au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) { + inode = dentry->d_inode; + h_inode = au_h_iptr(inode, bindex); + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + } + h_dentry = au_h_dptr(dentry, bindex); + err = au_whtmp_ren(dir, bindex, h_dentry); + if (inode) { + /* todo: bad approach? */ + if (!err) + au_hin_suspend(au_hi(inode, bindex)); + vfsub_i_unlock(h_inode); + } + if (unlikely(err)) + goto out; + + if (!au_test_nfs(h_dentry->d_sb)) { + const int dirwh = au_sbi(sb)->si_dirwh; + rmdir_later = (dirwh <= 1); + if (!rmdir_later) + rmdir_later = au_nhash_test_longer_wh(whlist, bindex, + dirwh); + if (rmdir_later) + return rmdir_later; + } + + err = au_whtmp_rmdir(dir, bindex, h_dentry, whlist); + if (unlikely(err)) { + AuIOErr("rmdir %.*s, b%d failed, %d. ignored\n", + AuDLNPair(h_dentry), bindex, err); + /* we do not revert the inotify watch */ + err = 0; + } + + out: + AuTraceErr(err); + return err; +} + +static void epilog(struct inode *dir, struct dentry *dentry, + aufs_bindex_t bindex) +{ + /* todo: unnecessary? */ + d_drop(dentry); + dentry->d_inode->i_ctime = dir->i_ctime; + + if (atomic_read(&dentry->d_count) == 1) { + au_set_h_dptr(dentry, au_dbstart(dentry), NULL); + au_update_dbstart(dentry); + } + if (au_ibstart(dir) == bindex) + au_cpup_attr_timesizes(dir); + dir->i_version++; +} + +/* revert flags */ +#define AuRev_DLGT 1 +#define au_ftest_rev(flags, name) ((flags) & AuRev_##name) +#define au_fset_rev(flags, name) { (flags) |= AuRev_##name; } +#define au_fclr_rev(flags, name) { (flags) &= ~AuRev_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuRev_DLGT +#define AuRev_DLGT 0 +#endif + +static int do_revert(int err, struct inode *dir, aufs_bindex_t bwh, + struct dentry *wh_dentry, struct dentry *dentry, + struct au_dtime *dt, unsigned int flags) +{ + int rerr; + + rerr = au_wh_unlink_dentry(au_hi(dir, bwh), wh_dentry, dentry, + au_ftest_rev(flags, DLGT)); + if (!rerr) { + au_set_dbwh(dentry, bwh); + au_dtime_revert(dt); + return 0; + } + + AuIOErr("%.*s reverting whiteout failed(%d, %d)\n", + AuDLNPair(dentry), err, rerr); + return -EIO; +} + +/* ---------------------------------------------------------------------- */ + +int aufs_unlink(struct inode *dir, struct dentry *dentry) +{ + int err; + struct inode *inode, *h_dir; + struct dentry *parent, *wh_dentry, *h_dentry; + struct au_dtime dt; + aufs_bindex_t bwh, bindex, bstart; + unsigned char dlgt; + struct super_block *sb; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct au_pin pin; + + LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + inode = dentry->d_inode; + if (unlikely(!inode)) + return -ENOENT; /* possible? */ + IMustLock(inode); + + aufs_read_lock(dentry, AuLock_DW); + parent = dentry->d_parent; /* dir inode is locked */ + di_write_lock_parent(parent); + + bstart = au_dbstart(dentry); + bwh = au_dbwh(dentry); + bindex = -1; + wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt, &pin); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out; + + sb = dir->i_sb; + dlgt = !!au_test_dlgt(au_mntflags(sb)); + AuDebugOn(au_dbstart(dentry) != bstart); + h_dentry = au_h_dptr(dentry, bstart); + dget(h_dentry); + + if (bindex == bstart) { + vfsub_args_init(&vargs, &ign, dlgt, 0); + vfsub_ign_hinode(&vargs, IN_DELETE, au_pinned_hdir(&pin)); + h_dir = au_pinned_h_dir(&pin); + err = vfsub_unlink(h_dir, h_dentry, &vargs); + } else { + /* dir inode is locked */ + AuDebugOn(!wh_dentry + || wh_dentry->d_parent != au_h_dptr(parent, bindex)); + h_dir = wh_dentry->d_parent->d_inode; + IMustLock(h_dir); + err = 0; + } + + if (!err) { + inode->i_nlink--; +#if 0 /* todo: update plink? */ + if (!inode->i_nlink + && au_opt_test(p->a.mnt_flags, PLINK) + && au_plink_test(sb, inode) + /* && atomic_read(&inode->i_count) == 2) */) { + au_debug_on(); + DbgInode(inode); + au_debug_off(); + } +#endif + /* + * although this is not a dir, + * set it here since we need to detect the dead inode. + */ + if (!inode->i_nlink) + inode->i_flags |= S_DEAD; + epilog(dir, dentry, bindex); + + /* update target timestamps */ + if (bindex == bstart) { + au_update_fuse_h_inode(NULL, h_dentry); /*ignore*/ + inode->i_ctime = h_dentry->d_inode->i_ctime; + } else + /* todo: this timestamp may be reverted later */ + inode->i_ctime = h_dir->i_ctime; + goto out_unlock; /* success */ + } + + /* revert */ + if (wh_dentry) { + int rerr; + unsigned int rev_flags; + + rev_flags = 0; + if (dlgt) + au_fset_rev(rev_flags, DLGT); + rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt, + rev_flags); + if (rerr) + err = rerr; + } + + out_unlock: + au_unpin(&pin); + dput(wh_dentry); + dput(h_dentry); + out: + di_write_unlock(parent); + aufs_read_unlock(dentry, AuLock_DW); + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} + +int aufs_rmdir(struct inode *dir, struct dentry *dentry) +{ + int err, rmdir_later; + struct inode *inode; + struct dentry *parent, *wh_dentry, *h_dentry; + struct au_dtime dt; + aufs_bindex_t bwh, bindex, bstart; + struct au_whtmp_rmdir_args *args; + struct au_nhash *whlist; + struct super_block *sb; + unsigned int mnt_flags; + struct au_pin pin; + + LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + inode = dentry->d_inode; + err = -ENOENT; /* possible? */ + if (unlikely(!inode)) + goto out; + IMustLock(inode); + + whlist = au_nhash_new(GFP_NOFS); + err = PTR_ERR(whlist); + if (IS_ERR(whlist)) + goto out; + + err = -ENOMEM; + args = kmalloc(sizeof(*args), GFP_NOFS); + if (unlikely(!args)) + goto out_whlist; + + aufs_read_lock(dentry, AuLock_DW); + parent = dentry->d_parent; /* dir inode is locked */ + di_write_lock_parent(parent); + err = au_test_empty(dentry, whlist); + if (unlikely(err)) + goto out_args; + + bstart = au_dbstart(dentry); + bwh = au_dbwh(dentry); + bindex = -1; + wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &dt, &pin); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out_args; + + AuDebugOn(au_dbstart(dentry) != bstart); + h_dentry = au_h_dptr(dentry, bstart); + dget(h_dentry); + + rmdir_later = 0; + if (bindex == bstart) { + err = renwh_and_rmdir(dentry, bstart, whlist, dir); + if (err > 0) { + rmdir_later = err; + err = 0; + } + } else { + /* dir inode is locked */ + AuDebugOn(!wh_dentry + || wh_dentry->d_parent != au_h_dptr(parent, bindex)); + IMustLock(wh_dentry->d_parent->d_inode); + err = 0; + } + + sb = dentry->d_sb; + mnt_flags = au_mntflags(sb); + if (!err) { + if (au_opt_test(mnt_flags, UDBA_INOTIFY) && rmdir_later) + au_reset_hinotify(inode, /*flags*/0); + inode->i_nlink = 0; + inode->i_flags |= S_DEAD; + au_set_dbdiropq(dentry, -1); + epilog(dir, dentry, bindex); + + if (rmdir_later) { + au_whtmp_kick_rmdir(dir, bstart, h_dentry, whlist, + args); + args = NULL; + } + + goto out_unlock; /* success */ + } + + /* revert */ + LKTRLabel(revert); + if (wh_dentry) { + int rerr; + unsigned int rev_flags; + + rev_flags = 0; + if (au_test_dlgt(mnt_flags)) + au_fset_rev(rev_flags, DLGT); + rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt, + rev_flags); + if (rerr) + err = rerr; + } + + out_unlock: + au_unpin(&pin); + dput(wh_dentry); + dput(h_dentry); + out_args: + di_write_unlock(parent); + aufs_read_unlock(dentry, AuLock_DW); + kfree(args); + out_whlist: + au_nhash_del(whlist); + out: + AuTraceErr(err); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} diff --git a/fs/aufs/i_op_ren.c b/fs/aufs/i_op_ren.c new file mode 100644 index 0000000..89706ca --- /dev/null +++ b/fs/aufs/i_op_ren.c @@ -0,0 +1,1261 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode operation (rename entry) + * todo: this is crazy monster + * + * $Id: i_op_ren.c,v 1.82 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +enum { SRC, DST }; + +#define AuRen_ISDIR 1 +#define AuRen_ISSAMEDIR (1 << 1) +#define AuRen_WHSRC (1 << 2) +#define AuRen_WHDST (1 << 3) +#define AuRen_DLGT (1 << 4) +#define AuRen_VFSLOCK (1 << 5) +#define AuRen_PINNED (1 << 6) +#define AuRen_MNT_WRITE 0 //(1 << 7) +#define au_ftest_ren(flags, name) ((flags) & AuRen_##name) +#define au_fset_ren(flags, name) { (flags) |= AuRen_##name; } +#define au_fclr_ren(flags, name) { (flags) &= ~AuRen_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuRen_DLGT +#define AuRen_DLGT 0 +#endif + +struct au_ren_args { + /* original args */ + struct dentry *src_dentry, *dentry; + struct inode *src_dir, *dir; + + struct dentry *h_dentry[2], *h_parent[2], *h_trap, *h_locked[2]; + /* todo: remove them */ + struct dentry *parent[2], *gparent[2]; + struct au_pin pin[2]; + struct au_nhash whlist; + aufs_bindex_t btgt, bstart[2]; + /* do_rename() only */ + unsigned char need_diropq, bycpup; + struct super_block *sb; + unsigned int flags; + unsigned int mnt_flags; + struct au_ndx ndx; + + /* do_rename() only */ +#ifdef CONFIG_AUFS_BR_NFS + struct au_hin_ignore ign[3]; +#else + struct au_hin_ignore ign[2]; +#endif + struct vfsub_args vargs; + struct au_whtmp_rmdir_args *thargs; + struct dentry *wh_dentry[2], *h_dst, *h_src; +}; + +/* ---------------------------------------------------------------------- */ + +#define RevertFailure(fmt, args...) do { \ + AuIOErrWhck("revert failure: " fmt " (%d, %d)\n", \ + ##args, err, rerr); \ + err = -EIO; \ + } while (0) + +static noinline_for_stack +void au_ren_rev_diropq(int err, struct au_ren_args *a) +{ + int rerr; + struct inode *h_inode; + + /* lock inode simply since inotify is not set to h_inode. */ + h_inode = au_h_dptr(a->src_dentry, a->btgt)->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + rerr = au_diropq_remove(a->src_dentry, a->btgt, + au_ftest_ren(a->flags, DLGT)); + vfsub_i_unlock(h_inode); + if (rerr) + RevertFailure("remove diropq %.*s", AuDLNPair(a->src_dentry)); +} + +static noinline_for_stack +void au_ren_rev_rename(int err, struct au_ren_args *a) +{ + int rerr; + struct dentry *h_d; + struct qstr *name = &a->src_dentry->d_name; + + h_d = au_lkup_one(name->name, a->h_parent[SRC], name->len, &a->ndx); + rerr = PTR_ERR(h_d); + if (IS_ERR(h_d)) { + RevertFailure("au_lkup_one %.*s", AuDLNPair(a->src_dentry)); + return; + } + + AuDebugOn(h_d->d_inode); + vfsub_args_reinit(&a->vargs); + vfsub_ign_hinode(&a->vargs, IN_MOVED_FROM, + au_pinned_hdir(a->pin + DST)); + vfsub_ign_hinode(&a->vargs, IN_MOVED_TO, au_pinned_hdir(a->pin + SRC)); + rerr = vfsub_rename(au_pinned_h_dir(a->pin + DST), + au_h_dptr(a->src_dentry, a->btgt), + au_pinned_h_dir(a->pin + SRC), h_d, &a->vargs); + d_drop(h_d); + dput(h_d); + /* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */ + if (rerr) + RevertFailure("rename %.*s", AuDLNPair(a->src_dentry)); +} + +static noinline_for_stack +void au_ren_rev_cpup(int err, struct au_ren_args *a) +{ + int rerr; + + vfsub_args_reinit(&a->vargs); + vfsub_ign_hinode(&a->vargs, IN_DELETE, au_pinned_hdir(a->pin + DST)); + rerr = vfsub_unlink(au_pinned_h_dir(a->pin + DST), a->h_dentry[DST], + &a->vargs); + au_set_h_dptr(a->src_dentry, a->btgt, NULL); + au_set_dbstart(a->src_dentry, a->bstart[SRC]); + if (rerr) + RevertFailure("unlink %.*s", AuDLNPair(a->h_dentry[DST])); +} + +static noinline_for_stack +void au_ren_rev_whtmp(int err, struct au_ren_args *a) +{ + int rerr; + struct dentry *h_d; + struct inode *h_inode; + struct qstr *name = &a->dentry->d_name; + + h_d = au_lkup_one(name->name, a->h_parent[DST], name->len, &a->ndx); + rerr = PTR_ERR(h_d); + if (IS_ERR(h_d)) { + RevertFailure("lookup %.*s", AuLNPair(name)); + return; + } + if (h_d->d_inode) { + d_drop(h_d); + dput(h_d); + return; + } + + h_inode = a->h_dst->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + au_hin_resume(au_hi(a->src_dentry->d_inode, a->btgt)); + vfsub_i_unlock(h_inode); + vfsub_args_reinit(&a->vargs); + vfsub_ign_hinode(&a->vargs, IN_MOVED_TO | IN_MOVED_FROM, + au_pinned_hdir(a->pin + DST)); + rerr = vfsub_rename(au_pinned_h_dir(a->pin + DST), a->h_dst, + au_pinned_h_dir(a->pin + DST), h_d, &a->vargs); + d_drop(h_d); + dput(h_d); + if (!rerr) { + au_set_h_dptr(a->dentry, a->btgt, NULL); + au_set_h_dptr(a->dentry, a->btgt, dget(a->h_dst)); + } else + RevertFailure("rename %.*s", AuDLNPair(a->h_dst)); +} + +static noinline_for_stack +void au_ren_rev_whsrc(int err, struct au_ren_args *a) +{ + int rerr; + + rerr = au_wh_unlink_dentry(au_pinned_hdir(a->pin + SRC), + a->wh_dentry[SRC], a->src_dentry, /*dlgt*/0); + if (rerr) + RevertFailure("unlink %.*s", AuDLNPair(a->wh_dentry[SRC])); +} +#undef RevertFailure + +/* ---------------------------------------------------------------------- */ + +static /* noinline_for_stack */ +int au_ren_or_cpup(struct au_ren_args *a) +{ + int err; + + AuTraceEnter(); + + if (au_dbstart(a->src_dentry) == a->btgt) { + if (a->need_diropq && au_dbdiropq(a->src_dentry) == a->btgt) + a->need_diropq = 0; + vfsub_ign_hinode(&a->vargs, IN_MOVED_FROM, + au_pinned_hdir(a->pin + SRC)); + vfsub_ign_hinode(&a->vargs, IN_MOVED_TO, + au_pinned_hdir(a->pin + DST)); + /* nfs_rename() calls d_delete() */ + if (au_test_nfs(au_pinned_h_dir(a->pin + DST)->i_sb) + && a->h_dentry[DST]->d_inode + && (S_ISDIR(a->h_dentry[DST]->d_inode->i_mode) + || atomic_read(&a->h_dentry[DST]->d_count) <= 2)) + vfsub_ign_hinode(&a->vargs, IN_DELETE, + au_pinned_hdir(a->pin + DST)); + AuDebugOn(au_dbstart(a->src_dentry) != a->btgt); + err = vfsub_rename(au_pinned_h_dir(a->pin + SRC), + au_h_dptr(a->src_dentry, a->btgt), + au_pinned_h_dir(a->pin + DST), + a->h_dentry[DST], &a->vargs); + } else { + struct inode *h_inode = a->h_dentry[SRC]->d_inode; + + a->bycpup = 1; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + au_set_dbstart(a->src_dentry, a->btgt); + au_set_h_dptr(a->src_dentry, a->btgt, dget(a->h_dentry[DST])); + err = au_sio_cpup_single(a->src_dentry, a->btgt, a->bstart[SRC], + -1, !AuCpup_DTIME, a->parent[DST]); + if (unlikely(err)) { + au_set_h_dptr(a->src_dentry, a->btgt, NULL); + au_set_dbstart(a->src_dentry, a->bstart[SRC]); + } + vfsub_i_unlock(h_inode); + } + + return err; +} + +static /* noinline_for_stack */ +int au_ren_del_whtmp(struct au_ren_args *a) +{ + int err; + + AuTraceEnter(); + + if (au_test_nfs(a->h_dst->d_sb) + || !au_nhash_test_longer_wh(&a->whlist, a->btgt, + au_sbi(a->sb)->si_dirwh)) { + err = au_whtmp_rmdir(a->dir, a->btgt, a->h_dst, &a->whlist); + if (unlikely(err)) + AuWarn("failed removing whtmp dir %.*s (%d), " + "ignored.\n", AuDLNPair(a->h_dst), err); + } else { + au_whtmp_kick_rmdir(a->dir, a->btgt, a->h_dst, &a->whlist, + a->thargs); + dput(a->h_dst); + a->thargs = NULL; + } + + return 0; +} + +static /* noinline_for_stack */ +int au_ren_diropq(struct au_ren_args *a) +{ + int err; + struct dentry *diropq; + struct inode *h_inode; + + AuTraceEnter(); + + err = 0; + h_inode = au_h_dptr(a->src_dentry, a->btgt)->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + diropq = au_diropq_create(a->src_dentry, a->btgt, + au_ftest_ren(a->flags, DLGT)); + vfsub_i_unlock(h_inode); + if (IS_ERR(diropq)) + err = PTR_ERR(diropq); + dput(diropq); + + return err; +} + +static /* noinline_for_stack */ +int do_rename(struct au_ren_args *a) +{ + int err; + aufs_bindex_t bindex, bend; + struct dentry *h_d; + + LKTRTrace("%.*s/%.*s, %.*s/%.*s, " + "hd{%p, %p}, hp{%p, %p}, wh %p, btgt %d, bstart{%d, %d}, " + "flags 0x%x\n", + AuDLNPair(a->parent[SRC]), AuDLNPair(a->src_dentry), + AuDLNPair(a->parent[DST]), AuDLNPair(a->dentry), + a->h_dentry[SRC], a->h_dentry[DST], + a->h_parent[SRC], a->h_parent[DST], + &a->whlist, a->btgt, + a->bstart[SRC], a->bstart[DST], + a->flags); + + /* prepare workqueue args */ + if (au_ftest_ren(a->flags, ISDIR) && a->h_dentry[DST]->d_inode) { + err = -ENOMEM; + a->thargs = kmalloc(sizeof(*a->thargs), GFP_NOFS); + if (unlikely(!a->thargs)) + goto out; + a->h_dst = dget(a->h_dentry[DST]); + } + + a->ndx.nfsmnt = au_nfsmnt(a->sb, a->btgt); + if (au_ftest_ren(a->flags, DLGT)) + au_fset_ndx(a->ndx.flags, DLGT); + + /* create whiteout for src_dentry */ + if (au_ftest_ren(a->flags, WHSRC)) { + a->wh_dentry[SRC] = au_wh_create(a->src_dentry, a->btgt, + a->h_parent[SRC], &a->ndx); + err = PTR_ERR(a->wh_dentry[SRC]); + if (IS_ERR(a->wh_dentry[SRC])) + goto out_thargs; + } + + /* lookup whiteout for dentry */ + if (au_ftest_ren(a->flags, WHDST)) { + h_d = au_wh_lkup(a->h_parent[DST], &a->dentry->d_name, &a->ndx); + err = PTR_ERR(h_d); + if (IS_ERR(h_d)) + goto out_whsrc; + if (!h_d->d_inode) + dput(h_d); + else + a->wh_dentry[DST] = h_d; + } + + /* rename dentry to tmpwh */ + if (a->thargs) { + struct au_hinode *hinode; + + AuDbgDentry(a->h_dentry[DST]); + err = au_whtmp_ren(a->dir, a->btgt, a->h_dentry[DST]); + if (unlikely(err)) + goto out_whdst; + AuDbgDentry(a->h_dentry[DST]); + hinode = au_hi(a->dentry->d_inode, a->btgt); + /* todo: bad approach? */ + vfsub_i_lock_nested(hinode->hi_inode, AuLsc_I_CHILD); + au_hin_suspend(hinode); + vfsub_i_unlock(hinode->hi_inode); + au_set_h_dptr(a->dentry, a->btgt, NULL); + AuDbgDentry(a->h_dentry[DST]); + err = au_lkup_neg(a->dentry, a->btgt); + if (unlikely(err)) + goto out_whtmp; + a->h_dentry[DST] = au_h_dptr(a->dentry, a->btgt); + } + + /* cpup src */ + if (a->h_dentry[DST]->d_inode && a->bstart[SRC] != a->btgt) { + struct inode *h_inode = a->h_dentry[SRC]->d_inode; + + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + err = au_sio_cpup_simple(a->src_dentry, a->btgt, -1, + !AuCpup_DTIME); + vfsub_i_unlock(h_inode); + if (unlikely(err)) + goto out_whtmp; + } + + /* rename by vfs_rename or cpup */ + a->need_diropq = au_ftest_ren(a->flags, ISDIR) + && (a->wh_dentry[DST] + || au_dbdiropq(a->dentry) == a->btgt + /* hide the lower to keep xino */ + || a->btgt < au_dbend(a->dentry) + || au_opt_test(a->mnt_flags, ALWAYS_DIROPQ)); + a->bycpup = 0; + vfsub_args_init(&a->vargs, a->ign, au_ftest_ren(a->flags, DLGT), 0); + err = au_ren_or_cpup(a); + if (unlikely(err)) + goto out_whtmp; + + /* make dir opaque */ + if (a->need_diropq) { + err = au_ren_diropq(a); + if (unlikely(err)) + goto out_rename; + } + + /* update target timestamps */ + AuDebugOn(au_dbstart(a->src_dentry) != a->btgt); + a->h_src = au_h_dptr(a->src_dentry, a->btgt); + au_update_fuse_h_inode(NULL, a->h_src); /*ignore*/ + /* src_dentry->d_inode->i_atime = h_src->d_inode->i_atime; */ + a->src_dentry->d_inode->i_ctime = a->h_src->d_inode->i_ctime; + + /* remove whiteout for dentry */ + if (a->wh_dentry[DST]) { + err = au_wh_unlink_dentry(au_pinned_hdir(a->pin + DST), + a->wh_dentry[DST], a->dentry, + /*dlgt*/0); + if (unlikely(err)) + goto out_diropq; + } + + /* remove whtmp */ + if (a->thargs) + /* ignore this error */ + au_ren_del_whtmp(a); + + err = 0; + goto out_success; + + out_diropq: + if (a->need_diropq) + au_ren_rev_diropq(err, a); + out_rename: + if (!a->bycpup) + au_ren_rev_rename(err, a); + else + au_ren_rev_cpup(err, a); + out_whtmp: + if (a->thargs) + au_ren_rev_whtmp(err, a); + out_whdst: + dput(a->wh_dentry[DST]); + a->wh_dentry[DST] = NULL; + out_whsrc: + if (a->wh_dentry[SRC]) + au_ren_rev_whsrc(err, a); + d_drop(a->src_dentry); + bend = au_dbend(a->src_dentry); + for (bindex = au_dbstart(a->src_dentry); bindex <= bend; bindex++) { + h_d = au_h_dptr(a->src_dentry, bindex); + if (h_d) + d_drop(h_d); + } + d_drop(a->dentry); + bend = au_dbend(a->dentry); + for (bindex = au_dbstart(a->dentry); bindex <= bend; bindex++) { + h_d = au_h_dptr(a->dentry, bindex); + if (h_d) + d_drop(h_d); + } + au_update_dbstart(a->dentry); + if (a->thargs) + d_drop(a->h_dst); + out_success: + dput(a->wh_dentry[SRC]); + dput(a->wh_dentry[DST]); + out_thargs: + if (a->thargs) { + dput(a->h_dst); + kfree(a->thargs); + } + out: + AuTraceErr(err); + return err; +} + +/* + * test if @dentry dir can be rename destination or not. + * success means, it is a logically empty dir. + */ +static int may_rename_dstdir(struct dentry *dentry, aufs_bindex_t btgt, + struct au_nhash *whlist) +{ + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + return au_test_empty(dentry, whlist); +} + +/* + * test if @dentry dir can be rename source or not. + * if it can, return 0 and @children is filled. + * success means, + * - or, it is a logically empty dir. + * - or, it exists on writable branch and has no children including whiteouts + * on the lower branch. + */ +static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt) +{ + int err; + aufs_bindex_t bstart; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + bstart = au_dbstart(dentry); + if (bstart != btgt) { + struct au_nhash *whlist; + + whlist = au_nhash_new(GFP_NOFS); + err = PTR_ERR(whlist); + if (IS_ERR(whlist)) + goto out; + err = au_test_empty(dentry, whlist); + au_nhash_del(whlist); + goto out; + } + + if (bstart == au_dbtaildir(dentry)) + return 0; /* success */ + + err = au_test_empty_lower(dentry); + + out: + if (/* unlikely */(err == -ENOTEMPTY)) { + AuWarn1("renaming dir who has child(ren) on multiple branches," + " is not supported\n"); + err = -EXDEV; + } + AuTraceErr(err); + return err; +} + +/* mainly for link(2) and rename(2) */ +int au_wbr(struct dentry *dentry, aufs_bindex_t btgt) +{ + aufs_bindex_t bdiropq, bwh; + struct dentry *parent; + + LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), btgt); + parent = dentry->d_parent; + IMustLock(parent->d_inode); /* dir is locked */ + + bdiropq = au_dbdiropq(parent); + bwh = au_dbwh(dentry); + if (au_br_rdonly(au_sbr(dentry->d_sb, btgt)) + || (0 <= bdiropq && bdiropq < btgt) + || (0 <= bwh && bwh < btgt)) + btgt = -1; + + LKTRTrace("btgt %d\n", btgt); + return btgt; +} + +/* + * simple tests for rename. + * following the checks in vfs, plus the parent-child relationship. + */ +static int au_may_ren(struct au_ren_args *a) +{ + int err; + struct inode *h_inode; + + AuTraceEnter(); + + if (a->bstart[SRC] == a->btgt) { + err = au_may_del(a->src_dentry, a->btgt, a->h_parent[SRC], + au_ftest_ren(a->flags, ISDIR), &a->ndx); + if (unlikely(err)) + goto out; + err = -EINVAL; + if (unlikely(a->h_dentry[SRC] == a->h_trap)) + goto out; + } + + err = 0; + if (a->bstart[DST] != a->btgt) + goto out; + + err = -EIO; + h_inode = a->h_dentry[DST]->d_inode; + if (!a->dentry->d_inode) { + if (unlikely(h_inode)) + goto out; + err = au_may_add(a->dentry, a->btgt, a->h_parent[DST], + au_ftest_ren(a->flags, ISDIR), &a->ndx); + } else { + if (unlikely(!h_inode || !h_inode->i_nlink)) + goto out; + err = au_may_del(a->dentry, a->btgt, a->h_parent[DST], + au_ftest_ren(a->flags, ISDIR), &a->ndx); + if (unlikely(err)) + goto out; + err = -ENOTEMPTY; + if (unlikely(a->h_dentry[DST] == a->h_trap)) + goto out; + err = 0; + } + + out: + if (unlikely(err == -ENOENT || err == -EEXIST)) + err = -EIO; + AuTraceErr(err); + return err; +} + +/* + * locking order + * (VFS) + * - src_dir and dir by lock_rename() + * - inode if exitsts + * (aufs) + * - lock all + * + src_dentry and dentry by aufs_read_and_write_lock2() which calls, + * + si_read_lock + * + di_write_lock2_child() + * + di_write_lock_child() + * + ii_write_lock_child() + * + di_write_lock_child2() + * + ii_write_lock_child2() + * + src_parent and parent + * + di_write_lock_parent() + * + ii_write_lock_parent() + * + di_write_lock_parent2() + * + ii_write_lock_parent2() + * + if udab=inotify is specified, lock grand parents (crazy) + * + di_read_lock_gparent() + * + ii_read_lock_gparent() + * + di_read_lock_gparent2() + * + ii_read_lock_gparent2() + * + mutex_lock(s_vfs_rename_mutex) + * + mutex_lock_gparent() + * + mutex_lock_gparent2() + * + mutex_lock_parent() + * + mutex_lock_parent2() + * + else lower src_dir and dir by vfsub_lock_rename() + * + verify the every relations between child, parent and grand parent. if any + * of them failed, unlock all and return -EBUSY. + */ +static void au_ren_pin_init(struct au_pin *first, struct dentry *d1, + struct au_pin *next, struct dentry *d2, + aufs_bindex_t bindex) +{ + AuTraceEnter(); + + /* AuLsc_DI_PARENT3 is for higher gparent initially */ + au_pin_init(first, d1, bindex, AuLsc_DI_PARENT2, AuLsc_I_PARENT2, + AuPin_DI_LOCKED | AuPin_DO_GPARENT); + /* AuLsc_DI_PARENT4 is for lower gparent initially */ + au_pin_init(next, d2, bindex, AuLsc_DI_PARENT3, AuLsc_I_PARENT4, + AuPin_DI_LOCKED | AuPin_DO_GPARENT); +} + +static void au_ren_fake_pin(struct au_ren_args *a) +{ + int i; + struct au_pin1 *p; + struct inode *h_i; + + AuTraceEnter(); + + /* they increment the ref counter */ + for (i = 0; i < 2; i++) { + p = a->pin[i].pin + AuPin_PARENT; + au_pin_set_parent(a->pin + i, a->parent[i]); + dput(a->parent[i]); + h_i = a->h_parent[i]->d_inode; + au_pin_set_h_dir(a->pin + i, h_i); + iput(h_i); + + if (!a->gparent[i]) { + au_pin_set_gparent(a->pin + i, NULL); + au_pin_set_h_gdir(a->pin + i, NULL); + } else { + au_pin_set_gparent(a->pin + i, a->gparent[i]); + dput(a->gparent[i]); + h_i = au_h_iptr(a->gparent[i]->d_inode, a->btgt); + au_pin_set_h_gdir(a->pin + i, h_i); + iput(h_i); + } + } +} + +/* crazy */ +/* cf. i_op.c: au_do_pin() */ +static int au_ren_pin4(int higher, int lower, struct au_ren_args *a) +{ + int err, i, lsc; + struct au_pin *p; + struct au_pin1 *p4[4]; + struct inode *h_dir; + +#if 0 + lktr_set_pid(current->pid, LktrArrayPid); + LKTRTrace("i%lu, %.*s, i%lu, %.*s\n", + a->src_dir->i_ino, AuDLNPair(a->src_dentry), + a->dir->i_ino, AuDLNPair(a->dentry)); + lktr_clear_pid(current->pid, LktrArrayPid); +#endif + LKTRTrace("%d, %d\n", higher, lower); + + err = 0; + p = a->pin + higher; + p4[0] = au_pin_gp(p); /* highest */ + p4[1] = p->pin + AuPin_PARENT; + p = a->pin + lower; + p4[2] = au_pin_gp(p); + p4[3] = p->pin + AuPin_PARENT; + + if (a->gparent[higher]) { + au_pin_do_set_parent(p4[0], a->gparent[higher]); + au_pin_do_set_dentry(p4[0], a->parent[higher]); + } + au_pin_do_set_parent(p4[1], a->parent[higher]); + if (a->gparent[lower]) { + au_pin_do_set_parent(p4[2], a->gparent[lower]); + au_pin_do_set_dentry(p4[2], a->parent[lower]); + } + au_pin_do_set_parent(p4[3], a->parent[lower]); + +#if 0 + lktr_set_pid(current->pid, LktrArrayPid); + for (i = 0; i < 4; i++) + AuDbgDentry(p4[i]->parent); +#endif + DiMustWriteLock(p4[3]->parent); + di_write_unlock(p4[1]->parent); + if (p4[2]->parent) + di_read_lock_parent2(p4[2]->parent, AuLock_IR); + di_write_lock_parent3(p4[1]->parent); + if (p4[0]->parent) + di_read_lock_parent4(p4[0]->parent, AuLock_IR); + //lktr_clear_pid(current->pid, LktrArrayPid); + + vfsub_lock_rename_mutex(au_sbr_sb(a->dentry->d_sb, a->btgt)); + au_fset_ren(a->flags, VFSLOCK); + + lsc = AuLsc_I_PARENT; + for (i = 0; i < 4; i++, lsc++) { + if (p4[i]->parent) { + h_dir = au_h_iptr(p4[i]->parent->d_inode, a->btgt); + au_pin_do_set_h_dir(p4[i], h_dir); + vfsub_i_lock_nested(p4[i]->h_dir, lsc); + } + } + + AuTraceErr(err); + return err; +} + +static struct dentry *au_ren_pin3(int higher, int lower, struct au_ren_args *a) +{ + struct dentry *h_trap; + struct au_pin *p; + int err; + + LKTRTrace("%d, %d\n", higher, lower); + + p = a->pin + higher; + AuDebugOn(!au_pin_gp(p)); + au_fset_pin(au_pin_gp(p)->flags, VFS_RENAME); + err = au_do_pin(p->pin + AuPin_PARENT, au_pin_gp(p)); + h_trap = ERR_PTR(err); + if (unlikely(err)) + goto out; + p = a->pin + lower; + au_fclr_pin(p->pin[AuPin_PARENT].flags, DO_GPARENT); + err = au_do_pin(p->pin + AuPin_PARENT, NULL); + h_trap = ERR_PTR(err); + if (unlikely(err)) { + p = a->pin + higher; + au_do_unpin(p->pin + AuPin_PARENT, au_pin_gp(p)); + goto out; + } + h_trap = au_pinned_h_parent(p); + + out: + AuTraceErrPtr(h_trap); + return h_trap; +} + +static struct dentry *au_ren_pin(struct au_ren_args *a) +{ + struct dentry *h_trap; + struct inode *h_gdir; + int err, i, same_gp; + + //lktr_set_pid(current->pid, LktrArrayPid); + AuTraceEnter(); + AuDebugOn(!au_opt_test(a->mnt_flags, UDBA_INOTIFY)); + + /* gdir is not locked */ + same_gp = 0; + if (!IS_ROOT(a->parent[SRC])) + a->gparent[SRC] = dget_parent(a->parent[SRC]); + if (!IS_ROOT(a->parent[DST])) { + a->gparent[DST] = dget_parent(a->parent[DST]); + same_gp = (a->gparent[SRC] == a->gparent[DST]); + } + + /* + * patterns + * - gparent[SRC] is parent[DST] + * - parent[SRC] is gparent[DST] + * - gparent[SRC] is gparent[DST] + * - gparent[SRC] is a descendant of parent[DST] + * - parent[SRC] is an ancestor of gparent[DST] + * - not within grand parent range + */ + err = 0; + h_trap = ERR_PTR(-EBUSY); + if (a->gparent[SRC] == a->parent[DST]) { + //AuDbg("here\n"); + LKTRLabel(here); + au_ren_pin_init(a->pin + DST, a->dentry, a->pin + SRC, + a->src_dentry, a->btgt); + h_trap = au_ren_pin3(DST, SRC, a); + if (!IS_ERR(h_trap)) { + h_gdir = au_pinned_h_dir(a->pin + DST); + err = au_verify_parent(a->h_parent[SRC], h_gdir); + if (unlikely(err)) + h_trap = ERR_PTR(-EBUSY); + } + } else if (a->parent[SRC] == a->gparent[DST] || same_gp) { + //AuDbg("here\n"); + LKTRLabel(here); + au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST, + a->dentry, a->btgt); + h_trap = au_ren_pin3(SRC, DST, a); + if (!IS_ERR(h_trap)) { + if (!same_gp) + h_gdir = au_pinned_h_dir(a->pin + SRC); + else + h_gdir = au_pinned_h_gdir(a->pin + SRC); + err = au_verify_parent(a->h_parent[DST], h_gdir); + if (unlikely(err)) + h_trap = ERR_PTR(-EBUSY); + } + } else if (a->gparent[SRC] + && (h_trap = au_test_subdir(a->gparent[SRC], + a->parent[DST]))) { + //AuDbg("here\n"); + LKTRLabel(here); + au_ren_pin_init(a->pin + DST, a->dentry, a->pin + SRC, + a->src_dentry, a->btgt); + if (a->gparent[DST]) { + err = au_ren_pin4(DST, SRC, a); + if (unlikely(err)) + h_trap = ERR_PTR(err); + } else { + struct dentry *t; + t = au_ren_pin3(DST, SRC, a); + AuDebugOn(t == h_trap); + } +#if 1 + } else if (a->gparent[DST] + && (h_trap = au_test_subdir(a->gparent[DST], + a->parent[SRC]))) { + /* todo: is this really necessary? */ + //AuDbg("here\n"); + LKTRLabel(here); + au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST, + a->dentry, a->btgt); + if (a->gparent[SRC]) { + err = au_ren_pin4(SRC, DST, a); + if (unlikely(err)) + h_trap = ERR_PTR(err); + } else { + struct dentry *t; + t = au_ren_pin3(SRC, DST, a); + AuDebugOn(t == h_trap); + } +#endif + } else /* if (a->gparent[DST] + && (h_trap = au_test_subdir(a->gparent[DST], + a->parent[SRC]))) */ { + //AuDbg("here\n"); + LKTRLabel(here); + h_trap = NULL; + if (a->gparent[DST]) + h_trap = au_test_subdir(a->gparent[DST], + a->parent[SRC]); + au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST, + a->dentry, a->btgt); + err = au_ren_pin4(SRC, DST, a); + if (unlikely(err)) + h_trap = ERR_PTR(err); + } + au_fset_ren(a->flags, PINNED); + + if (!IS_ERR(h_trap)) { + err = 0; + for (i = 0; !err && i < 2; i++) { + h_gdir = au_pinned_h_gdir(a->pin + i); + if (h_gdir) + err = au_verify_parent(a->h_parent[i], h_gdir); + } + if (unlikely(err)) { + h_trap = ERR_PTR(err); + //AuDbg("here\n"); + } + } + + dput(a->gparent[SRC]); + dput(a->gparent[DST]); + memset(a->gparent, 0, sizeof(a->gparent)); + AuTraceErrPtr(h_trap); + //lktr_clear_pid(current->pid, LktrArrayPid); + return h_trap; +} + +static void au_ren_unlock(struct au_ren_args *a) +{ + int i; + + AuTraceEnter(); + + if (au_ftest_ren(a->flags, MNT_WRITE)) + au_mnt_drop_write(au_sbr_mnt(a->dentry->d_sb, a->btgt)); + if (a->h_locked[0]) + vfsub_unlock_rename(a->h_locked[0], a->h_locked[1]); + if (au_ftest_ren(a->flags, PINNED)) { + au_unpin(a->pin + SRC); + au_unpin(a->pin + DST); + memset(a->gparent, 0, sizeof(a->gparent)); + } + if (au_ftest_ren(a->flags, VFSLOCK)) + vfsub_unlock_rename_mutex(au_sbr_sb(a->dentry->d_sb, a->btgt)); + for (i = 0; i < 2; i++) + if (a->gparent[i]) { + di_read_unlock(a->gparent[i], AuLock_IR); + dput(a->gparent[i]); + } +} + +static int au_ren_lock(struct au_ren_args *a) +{ + int err; + const int hinotify = au_opt_test(a->mnt_flags, UDBA_INOTIFY); + + AuTraceEnter(); + + err = 0; + if (!hinotify + || (au_ftest_ren(a->flags, ISSAMEDIR) && IS_ROOT(a->parent[SRC]))) { + au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST, + a->dentry, a->btgt); + LKTRLabel(here); + a->h_locked[0] = a->h_parent[SRC]; + a->h_locked[1] = a->h_parent[DST]; + a->h_trap = vfsub_lock_rename(a->h_locked[0], a->h_locked[1]); + au_ren_fake_pin(a); + } else if (au_ftest_ren(a->flags, ISSAMEDIR) + && !IS_ROOT(a->parent[SRC])) { + /* this and next block should not be compiled when + hinotify is disabled */ + /* irregular/tricky rename lock */ + LKTRLabel(here); + au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST, + a->dentry, a->btgt); + a->gparent[SRC] = dget_parent(a->parent[SRC]); + di_read_lock_parent2(a->gparent[SRC], AuLock_IR); + a->h_locked[0] = a->h_parent[SRC]; + a->h_locked[1] = dget_parent(a->h_parent[SRC]); + a->h_trap = vfsub_lock_rename(a->h_locked[0], a->h_locked[1]); + err = au_verify_parent(a->h_parent[SRC], + a->h_locked[1]->d_inode); + dput(a->h_locked[1]); + if (!err) + au_ren_fake_pin(a); + } else { + /* 3 or 4 dir locks. crazy */ + LKTRLabel(here); + a->h_trap = au_ren_pin(a); + if (IS_ERR(a->h_trap)) + err = PTR_ERR(a->h_trap); + } + + if (!err && au_dbstart(a->src_dentry) == a->btgt) + err = au_verify_parent(a->h_dentry[SRC], + a->h_parent[SRC]->d_inode); + if (!err && au_dbstart(a->dentry) == a->btgt) + err = au_verify_parent(a->h_dentry[DST], + a->h_parent[DST]->d_inode); + if (!err) { + err = au_br_want_write(au_sbr(a->dentry->d_sb, a->btgt)); + if (unlikely(err)) + goto out_unlock; + au_fset_ren(a->flags, MNT_WRITE); + goto out; /* success */ + } + + err = -EBUSY; + + out_unlock: + au_ren_unlock(a); + out: + AuTraceErr(err); + return err; +} + +int aufs_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry) +{ + int err; + aufs_bindex_t bend, bindex; + unsigned char do_dt_dstdir, hinotify; + struct inode *inode[2]; + enum { PARENT, CHILD }; + /* reduce stack space */ + struct { + struct au_ren_args a; + struct au_dtime dt[2][2]; + } *p; + struct au_wr_dir_args wr_dir_args = { + /* .force_btgt = -1, */ + .flags = AuWrDir_ADD_ENTRY + }; + + LKTRTrace("i%lu, %.*s, i%lu, %.*s\n", + src_dir->i_ino, AuDLNPair(src_dentry), + dir->i_ino, AuDLNPair(dentry)); + AuDebugOn(IS_ROOT(src_dentry) || IS_ROOT(dentry)); + IMustLock(src_dir); + IMustLock(dir); + inode[DST] = dentry->d_inode; + if (inode[DST]) { + IMustLock(inode[DST]); + au_igrab(inode[DST]); + } + + err = -ENOMEM; + BUILD_BUG_ON(sizeof(*p) > PAGE_SIZE); + p = kzalloc(sizeof(*p), GFP_NOFS); + if (unlikely(!p)) + goto out; + + err = -ENOTDIR; + p->a.src_dir = src_dir; + p->a.src_dentry = src_dentry; + p->a.dir = dir; + p->a.dentry = dentry; + p->a.sb = src_dentry->d_sb; + inode[SRC] = src_dentry->d_inode; + p->a.flags = 0; + if (S_ISDIR(inode[SRC]->i_mode)) { + au_fset_ren(p->a.flags, ISDIR); + if (unlikely(inode[DST] && !S_ISDIR(inode[DST]->i_mode))) + goto out_free; + aufs_read_and_write_lock2(dentry, src_dentry, AuLock_DIR); + } else + aufs_read_and_write_lock2(dentry, src_dentry, 0); + + p->a.mnt_flags = au_mntflags(p->a.sb); + if (au_test_dlgt(p->a.mnt_flags)) + au_fset_ren(p->a.flags, DLGT); + p->a.parent[SRC] = src_dentry->d_parent; /* dir inode is locked */ + p->a.parent[DST] = dentry->d_parent; /* dir inode is locked */ + au_fset_ren(p->a.flags, ISSAMEDIR); /* temporary */ + di_write_lock_parent(p->a.parent[DST]); + + /* which branch we process */ + p->a.bstart[SRC] = au_dbstart(src_dentry); + p->a.bstart[DST] = au_dbstart(dentry); + if (au_ftest_ren(p->a.flags, ISDIR)) + au_fset_wrdir(wr_dir_args.flags, ISDIR); + wr_dir_args.force_btgt = p->a.bstart[SRC]; + if (dentry->d_inode && p->a.bstart[DST] < p->a.bstart[SRC]) + wr_dir_args.force_btgt = p->a.bstart[DST]; + wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt); + err = au_wr_dir(dentry, src_dentry, &wr_dir_args); + p->a.btgt = err; + if (unlikely(err < 0)) + goto out_unlock; + + /* are they available to be renamed */ + err = 0; + au_nhash_init(&p->a.whlist); + if (au_ftest_ren(p->a.flags, ISDIR) && inode[DST]) { + au_set_dbstart(dentry, p->a.bstart[DST]); + err = may_rename_dstdir(dentry, p->a.btgt, &p->a.whlist); + au_set_dbstart(dentry, p->a.btgt); + } + p->a.h_dentry[DST] = au_h_dptr(dentry, au_dbstart(dentry)); + if (unlikely(err)) + goto out_unlock; + /* todo: minor optimize, + their sb may be same while their bindex differs? */ + p->a.h_dentry[SRC] = au_h_dptr(src_dentry, au_dbstart(src_dentry)); + if (au_ftest_ren(p->a.flags, ISDIR)) { + err = may_rename_srcdir(src_dentry, p->a.btgt); + if (unlikely(err)) + goto out_children; + } + + /* prepare the writable parent dir on the same branch */ + if (p->a.bstart[DST] == p->a.btgt) { + au_fset_ren(p->a.flags, WHDST); + } else { + err = au_cpup_dirs(dentry, p->a.btgt); + if (unlikely(err)) + goto out_children; + } + + if (src_dir != dir) { + /* + * this temporary unlock is safe, + * because both dir->i_mutex are locked. + */ + di_write_unlock(p->a.parent[DST]); + di_write_lock_parent(p->a.parent[SRC]); + err = au_wr_dir_need_wh + (src_dentry, au_ftest_ren(p->a.flags, ISDIR), + &p->a.btgt); + di_write_unlock(p->a.parent[SRC]); + di_write_lock2_parent(p->a.parent[SRC], p->a.parent[DST], + /*isdir*/1); + au_fclr_ren(p->a.flags, ISSAMEDIR); + } else + err = au_wr_dir_need_wh + (src_dentry, au_ftest_ren(p->a.flags, ISDIR), + &p->a.btgt); + if (unlikely(err < 0)) + goto out_children; + if (err) + au_fset_ren(p->a.flags, WHSRC); + + hinotify = au_opt_test(p->a.mnt_flags, UDBA_INOTIFY); + p->a.h_parent[SRC] = au_h_dptr(p->a.parent[SRC], p->a.btgt); + p->a.h_parent[DST] = au_h_dptr(p->a.parent[DST], p->a.btgt); + err = au_ren_lock(&p->a); + if (unlikely(err)) + goto out_children; + + if (!au_opt_test(p->a.mnt_flags, UDBA_NONE)) { + p->a.ndx.nfsmnt = au_nfsmnt(p->a.sb, p->a.btgt); + if (au_ftest_ren(p->a.flags, DLGT)) + au_fset_ndx(p->a.ndx.flags, DLGT); + err = au_may_ren(&p->a); + if (unlikely(err)) + goto out_hdir; + memset(&p->a.ndx, 0, sizeof(p->a.ndx)); + } + + /* store timestamps to be revertible */ + au_dtime_store(p->dt[PARENT] + SRC, p->a.parent[SRC], + p->a.h_parent[SRC], au_pinned_hdir(p->a.pin + SRC), + au_pinned_hgdir(p->a.pin + SRC) + /* hgdir[SRC] */); + if (!au_ftest_ren(p->a.flags, ISSAMEDIR)) + au_dtime_store(p->dt[PARENT] + DST, p->a.parent[DST], + p->a.h_parent[DST], + au_pinned_hdir(p->a.pin + DST), + au_pinned_hgdir(p->a.pin + DST) + /* hgdir[DST] */); + do_dt_dstdir = 0; + if (au_ftest_ren(p->a.flags, ISDIR)) { + au_dtime_store(p->dt[CHILD] + SRC, src_dentry, + p->a.h_dentry[SRC], au_hi(inode[SRC], p->a.btgt), + au_pinned_hdir(p->a.pin + SRC)); + if (p->a.h_dentry[DST]->d_inode) { + do_dt_dstdir = 1; + au_dtime_store(p->dt[CHILD] + DST, dentry, + p->a.h_dentry[DST], + au_hi(inode[DST], p->a.btgt), + au_pinned_hdir(p->a.pin + DST)); + } + } + + err = do_rename(&p->a); + if (unlikely(err)) + goto out_dt; + + /* update dir attributes */ + dir->i_version++; + if (au_ftest_ren(p->a.flags, ISDIR)) { + /* is this updating defined in POSIX? */ + //vfsub_i_lock(inode[SRC]); + au_cpup_attr_timesizes(inode[SRC]); + //vfsub_i_unlock(inode[SRC]); + + au_cpup_attr_nlink(dir, /*force*/1); + if (inode[DST]) { + inode[DST]->i_nlink = 0; + au_cpup_attr_timesizes(inode[DST]); + } + } + if (au_ibstart(dir) == p->a.btgt) + au_cpup_attr_timesizes(dir); + + if (!au_ftest_ren(p->a.flags, ISSAMEDIR)) { + src_dir->i_version++; + if (au_ftest_ren(p->a.flags, ISDIR)) + au_cpup_attr_nlink(src_dir, /*force*/1); + if (au_ibstart(src_dir) == p->a.btgt) + au_cpup_attr_timesizes(src_dir); + } + + /* todo: simple d_drop(src_dentry) is not enough? */ + /* dput/iput all lower dentries */ + au_set_dbwh(src_dentry, -1); + bend = au_dbend(src_dentry); + for (bindex = p->a.btgt + 1; bindex <= bend; bindex++) { + struct dentry *hd; + hd = au_h_dptr(src_dentry, bindex); + if (hd) + au_set_h_dptr(src_dentry, bindex, NULL); + } + au_set_dbend(src_dentry, p->a.btgt); + + if (au_opt_test(p->a.mnt_flags, PLINK) + && au_plink_test(src_dentry->d_sb, inode[SRC])) + goto out_hdir; /* success */ + bend = au_ibend(inode[SRC]); + for (bindex = p->a.btgt + 1; bindex <= bend; bindex++) { + struct inode *hi; + hi = au_h_iptr(inode[SRC], bindex); + if (hi) { + au_xino_write0(p->a.sb, bindex, hi->i_ino, 0); + /* ignore this error */ + au_set_h_iptr(inode[SRC], bindex, NULL, 0); + } + } + au_set_ibend(inode[SRC], p->a.btgt); + goto out_hdir; /* success */ + + out_dt: + au_dtime_revert(p->dt[PARENT] + SRC); + if (!au_ftest_ren(p->a.flags, ISSAMEDIR)) + au_dtime_revert(p->dt[PARENT] + DST); + if (au_ftest_ren(p->a.flags, ISDIR) && err != -EIO) { + struct dentry *hd; + + hd = p->dt[CHILD][SRC].dt_h_dentry; + vfsub_i_lock_nested(hd->d_inode, AuLsc_I_CHILD); + au_dtime_revert(p->dt[CHILD] + SRC); + vfsub_i_unlock(hd->d_inode); + if (do_dt_dstdir) { + hd = p->dt[CHILD][DST].dt_h_dentry; + vfsub_i_lock_nested(hd->d_inode, AuLsc_I_CHILD); + au_dtime_revert(p->dt[CHILD] + DST); + vfsub_i_unlock(hd->d_inode); + } + } + out_hdir: + au_ren_unlock(&p->a); + out_children: + au_nhash_fin(&p->a.whlist); + out_unlock: + if (unlikely(err && au_ftest_ren(p->a.flags, ISDIR))) { + au_update_dbstart(dentry); + d_drop(dentry); + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + if (!err) { + d_move(src_dentry, dentry); + if (inode[DST] + && (inode[DST]->i_nlink == 1 + || au_ftest_ren(p->a.flags, ISDIR))) + inode[DST]->i_flags |= S_DEAD; + } +#endif + if (au_ftest_ren(p->a.flags, ISSAMEDIR)) + di_write_unlock(p->a.parent[DST]); + else + di_write_unlock2(p->a.parent[SRC], p->a.parent[DST]); + aufs_read_and_write_unlock2(dentry, src_dentry); + out_free: + kfree(p); + out: + iput(inode[DST]); + AuTraceErr(err); + //lktr_clear_pid(current->pid, LktrArrayPid); + if (unlikely(err == -EBUSY && au_test_nfsd(current))) + err = -ESTALE; + return err; +} diff --git a/fs/aufs/iinfo.c b/fs/aufs/iinfo.c new file mode 100644 index 0000000..f6cf01c --- /dev/null +++ b/fs/aufs/iinfo.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode private data + * + * $Id: iinfo.c,v 1.59 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +struct au_iinfo *au_ii(struct inode *inode) +{ + struct au_iinfo *iinfo; + + iinfo = &(container_of(inode, struct aufs_icntnr, vfs_inode)->iinfo); + /* bad_inode case */ + if (unlikely(!iinfo->ii_hinode)) + return NULL; + AuDebugOn(!iinfo->ii_hinode + /* || au_sbi(inode->i_sb)->si_bend < iinfo->ii_bend */ + || iinfo->ii_bend < iinfo->ii_bstart); + return iinfo; +} + +struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex) +{ + struct inode *hidden_inode; + + /* lock free root dinfo/inode */ + if (inode->i_ino != AUFS_ROOT_INO) { + IiMustAnyLock(inode); + AuDebugOn(bindex < 0 || au_ibend(inode) < bindex); + } else { + SiMustAnyLock(inode->i_sb); + AuDebugOn(bindex < 0 || au_sbend(inode->i_sb) < bindex); + } + hidden_inode = au_ii(inode)->ii_hinode[0 + bindex].hi_inode; + AuDebugOn(hidden_inode && atomic_read(&hidden_inode->i_count) <= 0); + return hidden_inode; +} + +aufs_bindex_t au_ii_br_id(struct inode *inode, aufs_bindex_t bindex) +{ + IiMustAnyLock(inode); + AuDebugOn(bindex < 0 + || au_ibend(inode) < bindex + || !au_ii(inode)->ii_hinode[0 + bindex].hi_inode); + return au_ii(inode)->ii_hinode[0 + bindex].hi_id; +} + +/* todo: hard/soft set? */ +void au_set_ibstart(struct inode *inode, aufs_bindex_t bindex) +{ + struct au_iinfo *iinfo = au_ii(inode); + struct inode *h_inode; + + IiMustWriteLock(inode); + AuDebugOn(au_sbend(inode->i_sb) < bindex); + iinfo->ii_bstart = bindex; + h_inode = iinfo->ii_hinode[bindex + 0].hi_inode; + if (h_inode) + au_cpup_igen(inode, h_inode); +} + +unsigned int au_hi_flags(struct inode *inode, int isdir) +{ + unsigned int flags; + const unsigned int mnt_flags = au_mntflags(inode->i_sb); + + flags = 0; + if (au_opt_test_xino(mnt_flags)) + au_fset_hi(flags, XINO); + if (isdir && au_opt_test(mnt_flags, UDBA_INOTIFY)) + au_fset_hi(flags, NOTIFY); + return flags; +} + +void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex, + struct inode *h_inode, unsigned int flags) +{ + struct au_hinode *hinode; + struct inode *hi; + struct au_iinfo *iinfo = au_ii(inode); + + LKTRTrace("i%lu, b%d, hi%lu, flags 0x%x\n", + inode->i_ino, bindex, h_inode ? h_inode->i_ino : 0, flags); + IiMustWriteLock(inode); + hinode = iinfo->ii_hinode + bindex; + hi = hinode->hi_inode; + AuDebugOn(bindex < au_ibstart(inode) || au_ibend(inode) < bindex); + AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0); + AuDebugOn(h_inode && hi); + + if (hi) + au_hiput(hinode); + hinode->hi_inode = h_inode; + if (h_inode) { + int err; + struct super_block *sb = inode->i_sb; + + if (bindex == iinfo->ii_bstart) + au_cpup_igen(inode, h_inode); + hinode->hi_id = au_sbr_id(sb, bindex); + if (au_ftest_hi(flags, XINO)) { + struct au_xino_entry xinoe = { + .ino = inode->i_ino, + /* .h_gen = h_inode->i_generation */ + }; + err = au_xino_write(sb, bindex, h_inode->i_ino, &xinoe); + if (unlikely(err)) + AuIOErr1("failed au_xino_write() %d\n", err); + } + + if (au_ftest_hi(flags, NOTIFY) + && au_br_hinotifyable(au_sbr_perm(sb, bindex))) { + err = au_hin_alloc(hinode, inode, h_inode); + if (unlikely(err)) + AuIOErr1("au_hin_alloc() %d\n", err); + } + } +} + +void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex, + struct dentry *h_wh) +{ + struct au_hinode *hinode; + + IiMustWriteLock(inode); + hinode = au_ii(inode)->ii_hinode + bindex; + AuDebugOn(hinode->hi_whdentry); + hinode->hi_whdentry = h_wh; +} + +void au_update_iigen(struct inode *inode) +{ + AuDebugOn(!inode->i_sb); + atomic_set(&au_ii(inode)->ii_generation, au_sigen(inode->i_sb)); + /* smp_mb(); */ /* atomic_set */ +} + +/* it may be called at remount time, too */ +void au_update_brange(struct inode *inode, int do_put_zero) +{ + struct au_iinfo *iinfo; + + LKTRTrace("i%lu, %d\n", inode->i_ino, do_put_zero); + IiMustWriteLock(inode); + + iinfo = au_ii(inode); + if (!iinfo || iinfo->ii_bstart < 0) + return; + + if (do_put_zero) { + aufs_bindex_t bindex; + for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; + bindex++) { + struct inode *h_i; + h_i = iinfo->ii_hinode[0 + bindex].hi_inode; + if (h_i && !h_i->i_nlink) + au_set_h_iptr(inode, bindex, NULL, 0); + } + } + + iinfo->ii_bstart = -1; + while (++iinfo->ii_bstart <= iinfo->ii_bend) + if (iinfo->ii_hinode[0 + iinfo->ii_bstart].hi_inode) + break; + if (iinfo->ii_bstart > iinfo->ii_bend) { + iinfo->ii_bstart = -1; + iinfo->ii_bend = -1; + return; + } + + iinfo->ii_bend++; + while (0 <= --iinfo->ii_bend) + if (iinfo->ii_hinode[0 + iinfo->ii_bend].hi_inode) + break; + AuDebugOn(iinfo->ii_bstart > iinfo->ii_bend || iinfo->ii_bend < 0); +} + +/* ---------------------------------------------------------------------- */ + +int au_iinfo_init(struct inode *inode) +{ + struct au_iinfo *iinfo; + struct super_block *sb; + int nbr, i; + + sb = inode->i_sb; + AuDebugOn(!sb); + iinfo = &(container_of(inode, struct aufs_icntnr, vfs_inode)->iinfo); + AuDebugOn(iinfo->ii_hinode); + nbr = au_sbend(sb) + 1; + if (nbr <= 0) + nbr = 1; + iinfo->ii_hinode = kcalloc(nbr, sizeof(*iinfo->ii_hinode), GFP_NOFS); + if (iinfo->ii_hinode) { + for (i = 0; i < nbr; i++) + iinfo->ii_hinode[i].hi_id = -1; + atomic_set(&iinfo->ii_generation, au_sigen(sb)); + /* smp_mb(); */ /* atomic_set */ + au_rw_init_nolock(&iinfo->ii_rwsem); + iinfo->ii_bstart = -1; + iinfo->ii_bend = -1; + iinfo->ii_vdir = NULL; + return 0; + } + return -ENOMEM; +} + +static int au_iinfo_write0(struct super_block *sb, struct au_hinode *hinode, + ino_t ino) +{ + int err, locked; + aufs_bindex_t bindex; + + err = 0; + locked = si_noflush_read_trylock(sb); /* crucio! */ + bindex = au_br_index(sb, hinode->hi_id); + if (bindex >= 0) + err = au_xino_write0(sb, bindex, hinode->hi_inode->i_ino, ino); + /* error action? */ + if (locked) + si_read_unlock(sb); + return err; +} + +void au_iinfo_fin(struct inode *inode) +{ + struct au_iinfo *iinfo; + aufs_bindex_t bend; + unsigned char unlinked; + struct au_hinode *hi; + struct super_block *sb; + ino_t ino; + + iinfo = au_ii(inode); + /* bad_inode case */ + if (!iinfo) + return; + + if (iinfo->ii_vdir) + au_vdir_free(iinfo->ii_vdir); + + if (iinfo->ii_bstart >= 0) { + sb = inode->i_sb; + unlinked = !inode->i_nlink; + ino = 0; + if (unlinked) + ino = inode->i_ino; + hi = iinfo->ii_hinode + iinfo->ii_bstart; + bend = iinfo->ii_bend; + while (iinfo->ii_bstart++ <= bend) { + if (hi->hi_inode) { + if (unlinked || !hi->hi_inode->i_nlink) { + au_iinfo_write0(sb, hi, ino); + /* ignore this error */ + ino = 0; + } + au_hiput(hi); + } + hi++; + } + } + + kfree(iinfo->ii_hinode); + au_rwsem_destroy(&iinfo->ii_rwsem); +} diff --git a/fs/aufs/inode.c b/fs/aufs/inode.c new file mode 100644 index 0000000..023fb27 --- /dev/null +++ b/fs/aufs/inode.c @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode functions + * + * $Id: inode.c,v 1.55 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +int au_refresh_hinode_self(struct inode *inode) +{ + int err, new_sz, update; + struct inode *first; + struct au_hinode *p, *q, tmp; + struct super_block *sb; + struct au_iinfo *iinfo; + aufs_bindex_t bindex, bend, new_bindex; + + LKTRTrace("i%lu\n", inode->i_ino); + IiMustWriteLock(inode); + + err = -ENOMEM; + update = 0; + sb = inode->i_sb; + bend = au_sbend(sb); + new_sz = sizeof(*iinfo->ii_hinode) * (bend + 1); + iinfo = au_ii(inode); + p = au_kzrealloc(iinfo->ii_hinode, sizeof(*p) * (iinfo->ii_bend + 1), + new_sz, GFP_NOFS); + if (unlikely(!p)) + goto out; + + iinfo->ii_hinode = p; + p = iinfo->ii_hinode + iinfo->ii_bstart; + first = p->hi_inode; + err = 0; + for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; + bindex++, p++) { + if (!p->hi_inode) + continue; + + new_bindex = au_br_index(sb, p->hi_id); + if (new_bindex == bindex) + continue; + if (new_bindex < 0) { + update++; + au_hiput(p); + p->hi_inode = NULL; + continue; + } + + if (new_bindex < iinfo->ii_bstart) + iinfo->ii_bstart = new_bindex; + if (iinfo->ii_bend < new_bindex) + iinfo->ii_bend = new_bindex; + /* swap two hidden inode, and loop again */ + q = iinfo->ii_hinode + new_bindex; + tmp = *q; + *q = *p; + *p = tmp; + if (tmp.hi_inode) { + bindex--; + p--; + } + } + au_update_brange(inode, /*do_put_zero*/0); + + if (unlikely(err)) + goto out; + + if (1 || first != au_h_iptr(inode, iinfo->ii_bstart)) + au_cpup_attr_all(inode, /*force*/0); + if (update && S_ISDIR(inode->i_mode)) + inode->i_version++; + au_update_iigen(inode); + + out: + AuTraceErr(err); + return err; +} + +int au_refresh_hinode(struct inode *inode, struct dentry *dentry) +{ + int err, update; + struct inode *first; + struct au_hinode *p; + struct super_block *sb; + struct au_iinfo *iinfo; + aufs_bindex_t bindex, bend; + unsigned char isdir; + unsigned int flags; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + IiMustWriteLock(inode); + + err = au_refresh_hinode_self(inode); + if (unlikely(err)) + goto out; + + sb = dentry->d_sb; + bend = au_sbend(sb); + iinfo = au_ii(inode); + update = 0; + p = iinfo->ii_hinode + iinfo->ii_bstart; + first = p->hi_inode; + isdir = S_ISDIR(inode->i_mode); + flags = au_hi_flags(inode, isdir); + bend = au_dbend(dentry); + for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) { + struct inode *hi; + struct dentry *hd; + + hd = au_h_dptr(dentry, bindex); + if (!hd || !hd->d_inode) + continue; + + if (iinfo->ii_bstart <= bindex && bindex <= iinfo->ii_bend) { + hi = au_h_iptr(inode, bindex); + if (hi) { + if (hi == hd->d_inode) + continue; + err = -ESTALE; + break; + } + } + if (bindex < iinfo->ii_bstart) + iinfo->ii_bstart = bindex; + if (iinfo->ii_bend < bindex) + iinfo->ii_bend = bindex; + au_set_h_iptr(inode, bindex, au_igrab(hd->d_inode), flags); + update++; + } + au_update_brange(inode, /*do_put_zero*/0); + + if (unlikely(err)) + goto out; + + if (1 || first != au_h_iptr(inode, iinfo->ii_bstart)) + au_cpup_attr_all(inode, /*force*/0); + if (update && isdir) + inode->i_version++; + au_update_iigen(inode); + + out: + AuTraceErr(err); + return err; +} + +static int set_inode(struct inode *inode, struct dentry *dentry) +{ + int err; + struct dentry *h_dentry; + struct inode *h_inode; + umode_t mode; + aufs_bindex_t bindex, bstart, btail; + unsigned char isdir; + struct au_iinfo *iinfo; + unsigned int flags; + + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(dentry)); + AuDebugOn(!(inode->i_state & I_NEW)); + IiMustWriteLock(inode); + bstart = au_dbstart(dentry); + h_dentry = au_h_dptr(dentry, bstart); + AuDebugOn(!h_dentry); + h_inode = h_dentry->d_inode; + AuDebugOn(!h_inode); + + err = 0; + isdir = 0; + mode = h_inode->i_mode; + switch (mode & S_IFMT) { + case S_IFREG: + btail = au_dbtail(dentry); + inode->i_op = &aufs_iop; + inode->i_fop = &aufs_file_fop; + inode->i_mapping->a_ops = &aufs_aop; + break; + case S_IFDIR: + isdir = 1; + btail = au_dbtaildir(dentry); + inode->i_op = &aufs_dir_iop; + inode->i_fop = &aufs_dir_fop; + break; + case S_IFLNK: + btail = au_dbtail(dentry); + inode->i_op = &aufs_symlink_iop; + /* inode->i_fop = &aufs_file_fop; */ + break; + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + case S_IFSOCK: + btail = au_dbtail(dentry); + inode->i_op = &aufs_iop; + init_special_inode(inode, mode, + au_h_rdev(h_inode, /*h_mnt*/NULL, h_dentry)); + break; + default: + AuIOErr("Unknown file type 0%o\n", mode); + err = -EIO; + goto out; + } + + /* do not set inotify for whiteouted dirs (SHWH mode) */ + flags = au_hi_flags(inode, isdir); + if (au_opt_test(au_mntflags(dentry->d_sb), SHWH) + && au_ftest_hi(flags, NOTIFY) + && dentry->d_name.len > AUFS_WH_PFX_LEN + && !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) + au_fclr_hi(flags, NOTIFY); + iinfo = au_ii(inode); + iinfo->ii_bstart = bstart; + iinfo->ii_bend = btail; + for (bindex = bstart; bindex <= btail; bindex++) { + h_dentry = au_h_dptr(dentry, bindex); + if (!h_dentry) + continue; + AuDebugOn(!h_dentry->d_inode); + au_set_h_iptr(inode, bindex, au_igrab(h_dentry->d_inode), + flags); + } + au_cpup_attr_all(inode, /*force*/1); + + out: + AuTraceErr(err); + return err; +} + +/* successful returns with iinfo write_locked */ +/* todo: return with unlocked? */ +static int reval_inode(struct inode *inode, struct dentry *dentry, int *matched) +{ + int err; + struct inode *h_inode, *h_dinode; + aufs_bindex_t bindex, bend; + + LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(dentry)); + + *matched = 0; + + /* + * before this function, if aufs got any iinfo lock, it must be only + * one, the parent dir. + * it can happen by UDBA and the obsoleted inode number. + */ + err = -EIO; + if (unlikely(inode->i_ino == parent_ino(dentry))) + goto out; + + h_dinode = au_h_dptr(dentry, au_dbstart(dentry))->d_inode; + //vfsub_i_lock_nested(inode, AuLsc_I_CHILD); + ii_write_lock_new_child(inode); + /* it happend */ + if (unlikely(IS_DEADDIR(inode))) + goto out_unlock; + + err = 0; + bend = au_ibend(inode); + for (bindex = au_ibstart(inode); bindex <= bend; bindex++) { + h_inode = au_h_iptr(inode, bindex); + if (h_inode && h_inode == h_dinode) { + /* && (ibs != bstart + || !au_test_higen(inode, h_inode))); */ + *matched = 1; + err = 0; + if (au_iigen(inode) != au_digen(dentry)) + err = au_refresh_hinode(inode, dentry); + break; + } + } + + out_unlock: + if (unlikely(err)) + ii_write_unlock(inode); + //vfsub_i_unlock(inode); + out: + AuTraceErr(err); + return err; +} + +/* successful returns with iinfo write_locked */ +/* todo: return with unlocked? */ +struct inode *au_new_inode(struct dentry *dentry, int must_new) +{ + struct inode *inode, *h_inode; + struct dentry *h_dentry; + struct super_block *sb; + ino_t h_ino; + int err, match; + aufs_bindex_t bstart; + struct au_xino_entry xinoe; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + sb = dentry->d_sb; + bstart = au_dbstart(dentry); + h_dentry = au_h_dptr(dentry, bstart); + AuDebugOn(!h_dentry); + h_inode = h_dentry->d_inode; + AuDebugOn(!h_inode); + + h_ino = h_inode->i_ino; + err = au_xino_read(sb, bstart, h_ino, &xinoe); + inode = ERR_PTR(err); + if (unlikely(err)) + goto out; + new_ino: + if (!xinoe.ino) { + xinoe.ino = au_xino_new_ino(sb); + if (!xinoe.ino) { + inode = ERR_PTR(-EIO); + goto out; + } + } + + LKTRTrace("i%lu\n", (unsigned long)xinoe.ino); + err = -ENOMEM; + inode = iget_locked(sb, xinoe.ino); + if (unlikely(!inode)) + goto out; + err = PTR_ERR(inode); + if (IS_ERR(inode)) + goto out; + err = -ENOMEM; + if (unlikely(is_bad_inode(inode))) + goto out_iput; + + LKTRTrace("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW)); + if (inode->i_state & I_NEW) { + sb->s_op->read_inode(inode); + if (!is_bad_inode(inode)) { + ii_write_lock_new_child(inode); + err = set_inode(inode, dentry); + //err = -1; + } + unlock_new_inode(inode); + if (!err) + goto out; /* success */ + if (!is_bad_inode(inode)) + ii_write_unlock(inode); + goto out_iput; + } else if (!must_new) { + err = reval_inode(inode, dentry, &match); + if (!err) + goto out; /* success */ + else if (match) + goto out_iput; + } + + if (unlikely(au_test_unique_ino(h_dentry, h_ino))) + AuWarn1("Un-notified UDBA or repeatedly renamed dir," + " b%d, %s, %.*s, hi%lu, i%lu.\n", + bstart, au_sbtype(h_dentry->d_sb), AuDLNPair(dentry), + (unsigned long)h_ino, (unsigned long)xinoe.ino); + xinoe.ino = 0; + err = au_xino_write0(sb, bstart, h_ino, 0); + if (!err) { + iput(inode); + goto new_ino; + } + /* force noxino? */ + + out_iput: + iput(inode); + inode = ERR_PTR(err); + out: + AuTraceErrPtr(inode); + return inode; +} + +/* ---------------------------------------------------------------------- */ + +int au_test_ro(struct super_block *sb, aufs_bindex_t bindex, + struct inode *inode) +{ + int err; + + err = au_br_rdonly(au_sbr(sb, bindex)); + + /* pseudo-link after flushed may out of bounds */ + if (!err + && inode + && au_ibstart(inode) <= bindex + && bindex <= au_ibend(inode)) { + /* + * permission check is unnecessary since vfsub routine + * will be called later + */ + struct inode *hi = au_h_iptr(inode, bindex); + if (hi) + err = IS_IMMUTABLE(hi) ? -EROFS : 0; + } + + AuTraceErr(err); + return err; +} + +int au_test_h_perm(struct inode *h_inode, int mask, int dlgt) +{ + if (!current->fsuid) + return 0; + /* todo: fake nameidata? */ + return vfsub_permission(h_inode, mask, NULL, dlgt); +} + +int au_test_h_perm_sio(struct inode *h_inode, int mask, int dlgt) +{ + if (au_test_nfs(h_inode->i_sb) + && (mask & MAY_WRITE) + && S_ISDIR(h_inode->i_mode)) + mask |= MAY_READ; /* force permission check */ + return au_test_h_perm(h_inode, mask, dlgt); +} diff --git a/fs/aufs/inode.h b/fs/aufs/inode.h new file mode 100644 index 0000000..7f7e9a3 --- /dev/null +++ b/fs/aufs/inode.h @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * inode operations + * + * $Id: inode.h,v 1.66 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_INODE_H__ +#define __AUFS_INODE_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include +#include "misc.h" + +struct au_hinode; +struct au_vdir; +struct au_iinfo { + atomic_t ii_generation; + struct super_block *ii_hsb1; /* no get/put */ + + struct au_rwsem ii_rwsem; + aufs_bindex_t ii_bstart, ii_bend; + __u32 ii_higen; + struct au_hinode *ii_hinode; + struct au_vdir *ii_vdir; +}; + +struct aufs_icntnr { + struct au_iinfo iinfo; + struct inode vfs_inode; +}; + +/* au_pin flags */ +#define AuPin_DI_LOCKED 1 +#define AuPin_DO_GPARENT (1 << 1) +#define AuPin_MNT_WRITE 0 //(1 << 2) +#define AuPin_VFS_RENAME (1 << 3) +/* will be set automatically */ +#define AuPin_VERIFY (1 << 4) +#define au_ftest_pin(flags, name) ((flags) & AuPin_##name) +#define au_fset_pin(flags, name) { (flags) |= AuPin_##name; } +#define au_fclr_pin(flags, name) { (flags) &= ~AuPin_##name; } + +struct au_pin1 { + /* input */ + struct dentry *dentry; + unsigned char lsc_di, lsc_hi, flags; + aufs_bindex_t bindex; + + /* output */ + struct dentry *parent; + struct inode *h_dir; +}; + +enum {AuPin_PARENT, AuPin_GPARENT}; +struct au_pin { +#ifdef CONFIG_AUFS_HINOTIFY + struct au_pin1 pin[2]; +#else + struct au_pin1 pin[1]; /* no grand parent */ +#endif +}; + +/* ---------------------------------------------------------------------- */ + +/* inode.c */ +int au_refresh_hinode_self(struct inode *inode); +int au_refresh_hinode(struct inode *inode, struct dentry *dentry); +struct inode *au_new_inode(struct dentry *dentry, int must_new); +int au_test_ro(struct super_block *sb, aufs_bindex_t bindex, + struct inode *inode); +int au_test_h_perm(struct inode *h_inode, int mask, int dlgt); +int au_test_h_perm_sio(struct inode *h_inode, int mask, int dlgt); + +/* i_op.c */ +extern struct inode_operations aufs_iop, aufs_symlink_iop, aufs_dir_iop; + +/* au_wr_dir flags */ +#define AuWrDir_ADD_ENTRY 1 +#define AuWrDir_ISDIR (1 << 1) +#define au_ftest_wrdir(flags, name) ((flags) & AuWrDir_##name) +#define au_fset_wrdir(flags, name) { (flags) |= AuWrDir_##name; } +#define au_fclr_wrdir(flags, name) { (flags) &= ~AuWrDir_##name; } + +struct au_wr_dir_args { + aufs_bindex_t force_btgt; + unsigned char flags; +}; +int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry, + struct au_wr_dir_args *args); + +void au_pin_init(struct au_pin *args, struct dentry *dentry, + aufs_bindex_t bindex, int lsc_di, int lsc_hi, + unsigned char flags); +int au_pin(struct au_pin *args, struct dentry *dentry, aufs_bindex_t bindex, + unsigned char flags) __must_check; +int au_do_pin(struct au_pin1 *p, struct au_pin1 *gp) __must_check; +void au_do_unpin(struct au_pin1 *p, struct au_pin1 *gp); + +/* i_op_add.c */ +struct au_ndx; +int au_may_add(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_parent, int isdir, struct au_ndx *ndx); +int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev); +int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname); +int aufs_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd); +int aufs_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry); +int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode); + +/* i_op_del.c */ +int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup); +int au_may_del(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_parent, int isdir, struct au_ndx *ndx); +int aufs_unlink(struct inode *dir, struct dentry *dentry); +int aufs_rmdir(struct inode *dir, struct dentry *dentry); + +/* i_op_ren.c */ +int au_wbr(struct dentry *dentry, aufs_bindex_t btgt); +int aufs_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry); + +#ifdef CONFIG_AUFS_DLGT +/* dlgt.c */ +int au_security_inode_permission(struct inode *h_inode, int mask, + struct nameidata *fake_nd, int dlgt); +#else +static inline +int au_security_inode_permission(struct inode *h_inode, int mask, + struct nameidata *fake_nd, int dlgt) +{ + return security_inode_permission(h_inode, mask, fake_nd); +} +#endif /* CONFIG_AUFS_DLGT */ + +#ifdef CONFIG_AUFS_GETATTR +/* getattr.c */ +int aufs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st); +#endif + +#if 0 /* reserved for future use */ +/* xattr.c */ +int aufs_setxattr(struct dentry *dentry, const char *name, const void *value, + size_t sz, int flags); +ssize_t aufs_getxattr(struct dentry *dentry, const char *name, void *value, + size_t sz); +ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t sz); +int aufs_removexattr(struct dentry *dentry, const char *name); +#endif + +/* iinfo.c */ +struct au_iinfo *au_ii(struct inode *inode); +struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex); +aufs_bindex_t au_ii_br_id(struct inode *inode, aufs_bindex_t bindex); + +void au_set_ibstart(struct inode *inode, aufs_bindex_t bindex); +void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex, + struct dentry *h_wh); +unsigned int au_hi_flags(struct inode *inode, int isdir); + +/* hinode flags */ +#define AuHi_XINO 1 +#define AuHi_NOTIFY (1 << 1) +#define au_ftest_hi(flags, name) ((flags) & AuHi_##name) +#define au_fset_hi(flags, name) { (flags) |= AuHi_##name; } +#define au_fclr_hi(flags, name) { (flags) &= ~AuHi_##name; } +#ifndef CONFIG_AUFS_HINOTIFY +#undef AuHi_NOTIFY +#define AuHi_NOTIFY 0 +#endif + +void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex, + struct inode *h_inode, unsigned int flags); + +void au_update_iigen(struct inode *inode); +void au_update_brange(struct inode *inode, int do_put_zero); + +int au_iinfo_init(struct inode *inode); +void au_iinfo_fin(struct inode *inode); + +/* plink.c */ +#ifdef CONFIG_AUFS_DEBUG +void au_plink_list(struct super_block *sb); +#else +static inline void au_plink_list(struct super_block *sb) +{ + /* nothing */ +} +#endif +int au_plink_test(struct super_block *sb, struct inode *inode); +struct dentry *au_plink_lkup(struct super_block *sb, aufs_bindex_t bindex, + struct inode *inode); +void au_plink_append(struct super_block *sb, struct inode *inode, + struct dentry *h_dentry, aufs_bindex_t bindex); +void au_plink_put(struct super_block *sb); +void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id); + +/* ---------------------------------------------------------------------- */ + +/* lock subclass for iinfo */ +enum { + AuLsc_II_CHILD, /* child first */ + AuLsc_II_CHILD2, /* rename(2), link(2), and cpup at hinotify */ + AuLsc_II_CHILD3, /* copyup dirs */ + AuLsc_II_PARENT, /* see AuLsc_I_PARENT in vfsub.h */ + AuLsc_II_PARENT2, + AuLsc_II_PARENT3, + AuLsc_II_PARENT4, + AuLsc_II_NEW_CHILD, +}; + +/* + * ii_read_lock_child, ii_write_lock_child, + * ii_read_lock_child2, ii_write_lock_child2, + * ii_read_lock_child3, ii_write_lock_child3, + * ii_read_lock_parent, ii_write_lock_parent, + * ii_read_lock_parent2, ii_write_lock_parent2, + * ii_read_lock_parent3, ii_write_lock_parent3, + * ii_read_lock_parent4, ii_write_lock_parent4, + * ii_read_lock_new_child, ii_write_lock_new_child, + */ +#define AuReadLockFunc(name, lsc) \ +static inline void ii_read_lock_##name(struct inode *i) \ +{ \ + au_dbg_locking_ii_reg(i, 0, AuLsc_II_##lsc); \ + au_rw_read_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \ + au_dbg_locking_ii_unreg(i, 0); \ + au_dbg_locked_ii_reg(i, 0, AuLsc_II_##lsc); \ +} + +#define AuWriteLockFunc(name, lsc) \ +static inline void ii_write_lock_##name(struct inode *i) \ +{ \ + au_dbg_locking_ii_reg(i, 1, AuLsc_II_##lsc); \ + au_rw_write_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \ + au_dbg_locking_ii_unreg(i, 1); \ + au_dbg_locked_ii_reg(i, 1, AuLsc_II_##lsc); \ +} + +#define AuRWLockFuncs(name, lsc) \ + AuReadLockFunc(name, lsc) \ + AuWriteLockFunc(name, lsc) + +AuRWLockFuncs(child, CHILD); +AuRWLockFuncs(child2, CHILD2); +AuRWLockFuncs(child3, CHILD3); +AuRWLockFuncs(parent, PARENT); +AuRWLockFuncs(parent2, PARENT2); +AuRWLockFuncs(parent3, PARENT3); +AuRWLockFuncs(parent4, PARENT4); +AuRWLockFuncs(new_child, NEW_CHILD); + +#undef AuReadLockFunc +#undef AuWriteLockFunc +#undef AuRWLockFuncs + +/* + * ii_read_unlock, ii_write_unlock, ii_downgrade_lock + */ +AuSimpleUnlockRwsemFuncs(ii_do, struct inode *i, au_ii(i)->ii_rwsem); +static inline void ii_read_unlock(struct inode *i) +{ + ii_do_read_unlock(i); + au_dbg_locked_ii_unreg(i, 0); +} + +static inline void ii_write_unlock(struct inode *i) +{ + ii_do_write_unlock(i); + au_dbg_locked_ii_unreg(i, 0); +} + +static inline void ii_downgrade_lock(struct inode *i) +{ + ii_do_downgrade_lock(i); +} + +/* to debug easier, do not make them inlined functions */ +#define IiMustReadLock(i) do { \ + SiMustAnyLock((i)->i_sb); \ + AuRwMustReadLock(&au_ii(i)->ii_rwsem); \ +} while (0) + +#define IiMustWriteLock(i) do { \ + SiMustAnyLock((i)->i_sb); \ + AuRwMustWriteLock(&au_ii(i)->ii_rwsem); \ +} while (0) + +#define IiMustAnyLock(i) do { \ + SiMustAnyLock((i)->i_sb); \ + AuRwMustAnyLock(&au_ii(i)->ii_rwsem); \ +} while (0) + +#define IiMustNoWaiters(i) AuRwMustNoWaiters(&au_ii(i)->ii_rwsem) + +/* ---------------------------------------------------------------------- */ + +static inline struct inode *au_igrab(struct inode *inode) +{ + if (inode) { + AuDebugOn(!atomic_read(&inode->i_count)); + atomic_inc_return(&inode->i_count); + } + return inode; +} + +/* ---------------------------------------------------------------------- */ + +static inline aufs_bindex_t au_ibstart(struct inode *inode) +{ + /* lock free root dinfo/inode */ + if (inode->i_ino != AUFS_ROOT_INO) + IiMustAnyLock(inode); + else + SiMustAnyLock(inode->i_sb); + return au_ii(inode)->ii_bstart; +} + +static inline aufs_bindex_t au_ibend(struct inode *inode) +{ + /* lock free root dinfo/inode */ + if (inode->i_ino != AUFS_ROOT_INO) + IiMustAnyLock(inode); + else + SiMustAnyLock(inode->i_sb); + return au_ii(inode)->ii_bend; +} + +static inline struct au_vdir *au_ivdir(struct inode *inode) +{ + IiMustAnyLock(inode); + AuDebugOn(!S_ISDIR(inode->i_mode)); + return au_ii(inode)->ii_vdir; +} + +static inline struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex) +{ + struct au_hinode *hinode; + IiMustAnyLock(inode); + hinode = au_ii(inode)->ii_hinode + bindex; + return hinode->hi_whdentry; +} + +static inline void au_set_ibend(struct inode *inode, aufs_bindex_t bindex) +{ + IiMustWriteLock(inode); + AuDebugOn(au_sbend(inode->i_sb) < bindex || bindex < au_ibstart(inode)); + au_ii(inode)->ii_bend = bindex; +} + +static inline void au_set_ivdir(struct inode *inode, struct au_vdir *vdir) +{ + IiMustWriteLock(inode); + AuDebugOn(!S_ISDIR(inode->i_mode) || (au_ii(inode)->ii_vdir && vdir)); + au_ii(inode)->ii_vdir = vdir; +} + +static inline void au_hiput(struct au_hinode *hinode) +{ + au_hin_free(hinode); + dput(hinode->hi_whdentry); + iput(hinode->hi_inode); +} + +static inline struct au_hinode *au_hi(struct inode *inode, aufs_bindex_t bindex) +{ + /* todo: this lock check causes some unnecessary locks in callers. */ + IiMustAnyLock(inode); + return au_ii(inode)->ii_hinode + bindex; +} + +/* tiny test for inode number */ +/* tmpfs generation is too rough */ +static inline int au_test_higen(struct inode *inode, struct inode *h_inode) +{ + struct au_iinfo *iinfo; + + IiMustAnyLock(inode); + + iinfo = au_ii(inode); + return !(iinfo->ii_hsb1 == h_inode->i_sb + && iinfo->ii_higen == h_inode->i_generation); +} + +static inline au_gen_t au_iigen(struct inode *inode) +{ + return atomic_read(&au_ii(inode)->ii_generation); +} + +#ifdef CONFIG_AUFS_HINOTIFY +static inline au_gen_t au_iigen_dec(struct inode *inode) +{ + /* AuDbg("i%lu\n", inode->i_ino); */ + return atomic_dec_return(&au_ii(inode)->ii_generation); +} +#endif + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_HINOTIFY +static inline struct au_pin1 *au_pin_gp(struct au_pin *args) +{ + return args->pin + AuPin_GPARENT; +} + +/* hinotify.c */ +void au_unpin_gp(struct au_pin *args); + +#else + +static inline struct au_pin1 *au_pin_gp(struct au_pin *args) +{ + return NULL; +} + +static inline void au_unpin_gp(struct au_pin *args) +{ + /* empty */ +} +#endif /* HINOTIFY */ + +static inline void au_unpin(struct au_pin *args) +{ + au_do_unpin(args->pin + AuPin_PARENT, au_pin_gp(args)); +} + +static inline +struct au_hinode *au_do_pinned_hdir(struct au_pin1 *pin) +{ + if (pin && pin->parent) + return au_hi(pin->parent->d_inode, pin->bindex); + return NULL; +} + +struct dentry *au_do_pinned_h_parent(struct au_pin1 *pin); + +static inline struct dentry *au_do_pinned_parent(struct au_pin1 *pin) +{ + if (pin) + return pin->parent; + return NULL; +} + +static inline struct inode *au_do_pinned_h_dir(struct au_pin1 *pin) +{ + if (pin) + return pin->h_dir; + return NULL; +} + +static inline +void au_pin_do_set_dentry(struct au_pin1 *pin, struct dentry *dentry) +{ + if (pin) + pin->dentry = dentry; +} + +static inline +void au_pin_do_set_parent(struct au_pin1 *pin, struct dentry *parent) +{ + if (pin) { + dput(pin->parent); + pin->parent = dget(parent); + } +} + +static inline void au_pin_do_set_h_dir(struct au_pin1 *pin, struct inode *h_dir) +{ + if (pin) { + iput(pin->h_dir); + pin->h_dir = au_igrab(h_dir); + } +} + +static inline +void au_pin_do_set_parent_lflag(struct au_pin1 *pin, unsigned char lflag) +{ + if (pin) { + /* dirty macros require brackets */ + if (lflag) { + au_fset_pin(pin->flags, DI_LOCKED); + } else { + au_fclr_pin(pin->flags, DI_LOCKED); + } + } +} + +static inline +struct au_hinode *au_pinned_hdir(struct au_pin *args) +{ + return au_do_pinned_hdir(args->pin + AuPin_PARENT); +} + +static inline +struct au_hinode *au_pinned_hgdir(struct au_pin *args) +{ + return au_do_pinned_hdir(au_pin_gp(args)); +} + +static inline +struct dentry *au_pinned_h_parent(struct au_pin *args) +{ + return au_do_pinned_h_parent(args->pin + AuPin_PARENT); +} + +#if 0 /* reserved for future use */ +static inline +struct dentry *au_pinned_h_gparent(struct au_pin *args) +{ + return au_do_pinned_h_parent(au_pin_gp(args)); +} +#endif + +static inline +struct dentry *au_pinned_parent(struct au_pin *args) +{ + return au_do_pinned_parent(args->pin + AuPin_PARENT); +} + +static inline +struct dentry *au_pinned_gparent(struct au_pin *args) +{ + return au_do_pinned_parent(au_pin_gp(args)); +} + +static inline +struct inode *au_pinned_h_dir(struct au_pin *args) +{ + return au_do_pinned_h_dir(args->pin + AuPin_PARENT); +} + +static inline +struct inode *au_pinned_h_gdir(struct au_pin *args) +{ + return au_do_pinned_h_dir(au_pin_gp(args)); +} + +static inline void au_pin_set_parent(struct au_pin *args, struct dentry *d) +{ + au_pin_do_set_parent(args->pin + AuPin_PARENT, d); +} + +static inline void au_pin_set_gparent(struct au_pin *args, struct dentry *d) +{ + au_pin_do_set_parent(au_pin_gp(args), d); +} + +static inline void au_pin_set_h_dir(struct au_pin *args, struct inode *h_dir) +{ + au_pin_do_set_h_dir(args->pin + AuPin_PARENT, h_dir); +} + +static inline void au_pin_set_h_gdir(struct au_pin *args, struct inode *h_dir) +{ + au_pin_do_set_h_dir(au_pin_gp(args), h_dir); +} + +static inline +void au_pin_set_parent_lflag(struct au_pin *args, unsigned char lflag) +{ + au_pin_do_set_parent_lflag(args->pin + AuPin_PARENT, lflag); +} + +static inline +void au_pin_set_gparent_lflag(struct au_pin *args, unsigned char lflag) +{ + au_pin_do_set_parent_lflag(au_pin_gp(args), lflag); +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_INODE_H__ */ diff --git a/fs/aufs/misc.c b/fs/aufs/misc.c new file mode 100644 index 0000000..7caf178 --- /dev/null +++ b/fs/aufs/misc.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * $Id: misc.c,v 1.65 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp) +{ + void *q; + + LKTRTrace("p %p, nused %d, sz %d\n", p, nused, new_sz); + AuDebugOn(new_sz <= 0); + if (new_sz <= nused) + return p; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) + q = krealloc(p, new_sz, gfp); + if (q) + memset(q + nused, 0, new_sz - nused); + return q; +#else + LKTRTrace("ksize %d\n", ksize(p)); + if (new_sz <= ksize(p)) { + memset(p + nused, 0, new_sz - nused); + return p; + } + + q = kmalloc(new_sz, gfp); + //q = NULL; + if (unlikely(!q)) + return NULL; + memcpy(q, p, nused); + memset(q + nused, 0, new_sz - nused); + //smp_mb(); + kfree(p); + return q; +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) */ +} + +/* ---------------------------------------------------------------------- */ + +struct nameidata *au_dup_nd(struct au_sbinfo *sbinfo, struct nameidata *dst, + struct nameidata *src) +{ + LKTRTrace("src %p\n", src); + + if (src) { + *dst = *src; + dst->flags &= ~LOOKUP_PARENT; + if (sbinfo->si_wbr_create == AuWbrCreate_TDP) { + if ((dst->flags & LOOKUP_CREATE) + && !(dst->intent.open.flags & O_CREAT)) + dst->flags &= ~LOOKUP_CREATE; + } else { + dst->flags &= ~LOOKUP_CREATE; + dst->intent.open.flags &= ~O_CREAT; + } + } else + dst = NULL; + + return dst; +} + +struct nameidata *au_fake_dm(struct nameidata *fake_nd, struct nameidata *nd, + struct super_block *sb, aufs_bindex_t bindex) +{ + LKTRTrace("nd %p, b%d\n", nd, bindex); + + if (!nd) + return NULL; + + DiMustAnyLock(nd->dentry); + + fake_nd->dentry = NULL; + fake_nd->mnt = NULL; + + if (bindex <= au_dbend(nd->dentry)) + fake_nd->dentry = au_h_dptr(nd->dentry, bindex); + if (fake_nd->dentry) { + dget(fake_nd->dentry); + fake_nd->mnt = au_sbr_mnt(sb, bindex); + AuDebugOn(!fake_nd->mnt); + mntget(fake_nd->mnt); + } else + fake_nd = ERR_PTR(-ENOENT); + + AuTraceErrPtr(fake_nd); + return fake_nd; +} + +void au_fake_dm_release(struct nameidata *fake_nd) +{ + if (fake_nd) + path_release(fake_nd); +} + +int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode, + struct vfsub_args *vargs, struct nameidata *nd, + struct vfsmount *nfsmnt) +{ + int err; + + LKTRTrace("hi%lu, %.*s, 0%o, nd %d, nfsmnt %d\n", + h_dir->i_ino, AuDLNPair(h_dentry), mode, !!nd, !!nfsmnt); + + err = -ENOSYS; + if (!nfsmnt) + err = vfsub_create(h_dir, h_dentry, mode, /*nd*/NULL, vargs); + else { + struct nameidata fake_nd; + + if (nd) + fake_nd = *nd; + else + memset(&fake_nd, 0, sizeof(fake_nd)); + fake_nd.dentry = dget(h_dentry); + fake_nd.mnt = mntget(nfsmnt); + fake_nd.flags = LOOKUP_CREATE; + fake_nd.intent.open.flags = O_CREAT | FMODE_READ; + fake_nd.intent.open.create_mode = mode; + + err = vfsub_create(h_dir, h_dentry, mode, &fake_nd, vargs); + path_release(&fake_nd); + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* empty_zero_page is not exported before 2.6.24 */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) +static char *au_zp_alloc(void) +{ + return page_address(ZERO_PAGE(0)); +} + +static void au_zp_free(char *p) +{ + /* empty */ +} +#else +static char *au_zp_alloc(void) +{ + return (void *)get_zeroed_page(GFP_NOFS); +} + +static void au_zp_free(char *p) +{ + if (p) + free_page((unsigned long)p); +} +#endif + +int au_copy_file(struct file *dst, struct file *src, loff_t len, + struct au_hinode *hdir, struct super_block *sb, + struct vfsub_args *vargs) +{ + int err, all_zero, do_kfree; + unsigned long blksize; + char *buf, *zp; + /* reduce stack usage */ + struct iattr *ia; + + LKTRTrace("%.*s, %.*s\n", + AuDLNPair(dst->f_dentry), AuDLNPair(src->f_dentry)); + AuDebugOn(!(dst->f_mode & FMODE_WRITE)); +#ifdef CONFIG_AUFS_DEBUG + { + struct dentry *parent; + parent = dget_parent(dst->f_dentry); + IMustLock(parent->d_inode); + dput(parent); + } +#endif + + err = -ENOMEM; + zp = au_zp_alloc(); + if (unlikely(!zp)) + goto out; + blksize = dst->f_dentry->d_sb->s_blocksize; + if (!blksize || PAGE_SIZE < blksize) + blksize = PAGE_SIZE; + LKTRTrace("blksize %lu\n", blksize); + do_kfree = (blksize != PAGE_SIZE && blksize >= sizeof(*ia)); + if (do_kfree) + buf = kmalloc(blksize, GFP_NOFS); + else + buf = (void *)__get_free_page(GFP_NOFS); + if (unlikely(!buf)) + goto out; + +#ifdef CONFIG_AUFS_DEBUG + if (len > (1 << 22)) + AuWarn("copying a large file %lld\n", (long long)len); +#endif + err = 0; + all_zero = 0; + src->f_pos = 0; + dst->f_pos = 0; + while (len) { + size_t sz, rbytes, wbytes; + char *p; + + LKTRTrace("len %lld\n", len); + sz = blksize; + if (len < blksize) + sz = len; + + /* support LSM and notify */ + rbytes = 0; + /* todo: signal_pending? */ + while (!rbytes || err == -EAGAIN || err == -EINTR) { + rbytes = vfsub_read_k(src, buf, sz, &src->f_pos, + vfsub_ftest(vargs->flags, DLGT)); + err = rbytes; + } + if (unlikely(err < 0)) + break; + + all_zero = 0; + if (len >= rbytes && rbytes == blksize) { +#if 1 + all_zero = !memcmp(buf, zp, rbytes); +#else /* reserved for future use */ + unsigned long long *ullp; + size_t n, i; + + all_zero = 1; + ullp = (void *)buf; + n = rbytes / sizeof(*ullp); + i = n; + while (n-- > 0 && all_zero) + all_zero = !*ullp++; + p = (void *)ullp; + i *= sizeof(*ullp); + for (; all_zero && i < rbytes; i++) + all_zero = !*p++; +#endif + } + if (!all_zero) { + wbytes = rbytes; + p = buf; + while (wbytes) { + size_t b; + /* support LSM and notify */ + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_MODIFY, hdir); + b = vfsub_write_k(dst, p, wbytes, &dst->f_pos, + vargs); + err = b; + /* todo: signal_pending? */ + if (unlikely(err == -EAGAIN || err == -EINTR)) + continue; + if (unlikely(err < 0)) + break; + wbytes -= b; + p += b; + } + } else { + loff_t res; + LKTRLabel(hole); + res = vfsub_llseek(dst, rbytes, SEEK_CUR); + err = res; + if (unlikely(res < 0)) + break; + } + len -= rbytes; + err = 0; + } + + /* the last block may be a hole */ + if (!err && all_zero) { + struct dentry *h_d = dst->f_dentry; + struct inode *h_i = h_d->d_inode; + + LKTRLabel(last hole); + do { + /* todo: signal_pending? */ + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_MODIFY, hdir); + err = vfsub_write_k(dst, "\0", 1, &dst->f_pos, vargs); + } while (err == -EAGAIN || err == -EINTR); + if (err == 1) { + ia = (void *)buf; + ia->ia_size = dst->f_pos; + ia->ia_valid = ATTR_SIZE | ATTR_FILE; + ia->ia_file = dst; + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, vfsub_events_notify_change(ia), + hdir); + vfsub_i_lock_nested(h_i, AuLsc_I_CHILD2); + err = vfsub_notify_change(h_d, ia, vargs); + vfsub_i_unlock(h_i); + } + } + if (do_kfree) + kfree(buf); + else + free_page((unsigned long)buf); + + out: + au_zp_free(zp); + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/misc.h b/fs/aufs/misc.h new file mode 100644 index 0000000..d50bed1 --- /dev/null +++ b/fs/aufs/misc.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * $Id: misc.h,v 1.50 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_MISC_H__ +#define __AUFS_MISC_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#define I_MUTEX_QUOTA 0 +#define lockdep_off() do {} while (0) +#define lockdep_on() do {} while (0) +#define mutex_lock_nested(mtx, lsc) mutex_lock(mtx) +#define down_write_nested(rw, lsc) down_write(rw) +#define down_read_nested(rw, lsc) down_read(rw) +#endif + +/* ---------------------------------------------------------------------- */ + +typedef unsigned int au_gen_t; +/* see linux/include/linux/jiffies.h */ +#define AuGenYounger(a, b) ((int)(b) - (int)(a) < 0) +#define AuGenOlder(a, b) AufsGenYounger(b, a) + +/* ---------------------------------------------------------------------- */ + +struct au_splhead { + spinlock_t spin; + struct list_head head; +}; + +static inline void au_spl_init(struct au_splhead *spl) +{ + spin_lock_init(&spl->spin); + INIT_LIST_HEAD(&spl->head); +} + +static inline void au_spl_add(struct list_head *list, struct au_splhead *spl) +{ + spin_lock(&spl->spin); + list_add(list, &spl->head); + spin_unlock(&spl->spin); +} + +static inline void au_spl_del(struct list_head *list, struct au_splhead *spl) +{ + spin_lock(&spl->spin); + list_del(list); + spin_unlock(&spl->spin); +} + +/* ---------------------------------------------------------------------- */ + +struct au_rwsem { + struct rw_semaphore rwsem; +#ifdef CONFIG_AUFS_DEBUG + atomic_t rcnt; +#endif +}; + +#ifdef CONFIG_AUFS_DEBUG +#define AuDbgRcntInit(rw) do { \ + atomic_set(&(rw)->rcnt, 0); \ + smp_mb(); /* atomic set */ \ +} while (0) + +#define AuDbgRcntInc(rw) atomic_inc_return(&(rw)->rcnt) +#define AuDbgRcntDec(rw) WARN_ON(atomic_dec_return(&(rw)->rcnt) < 0) +#else +#define AuDbgRcntInit(rw) do {} while (0) +#define AuDbgRcntInc(rw) do {} while (0) +#define AuDbgRcntDec(rw) do {} while (0) +#endif /* CONFIG_AUFS_DEBUG */ + +#define au_rwsem_destroy(rw) AuDebugOn(rwsem_is_locked(&(rw)->rwsem)) + +static inline void au_rw_init_nolock(struct au_rwsem *rw) +{ + AuDbgRcntInit(rw); + init_rwsem(&rw->rwsem); +} + +static inline void au_rw_init_wlock(struct au_rwsem *rw) +{ + au_rw_init_nolock(rw); + down_write(&rw->rwsem); +} + +static inline void au_rw_init_wlock_nested(struct au_rwsem *rw, + unsigned int lsc) +{ + au_rw_init_nolock(rw); + down_write_nested(&rw->rwsem, lsc); +} + +static inline void au_rw_read_lock(struct au_rwsem *rw) +{ + down_read(&rw->rwsem); + AuDbgRcntInc(rw); +} + +static inline void au_rw_read_lock_nested(struct au_rwsem *rw, unsigned int lsc) +{ + down_read_nested(&rw->rwsem, lsc); + AuDbgRcntInc(rw); +} + +static inline void au_rw_read_unlock(struct au_rwsem *rw) +{ + AuDbgRcntDec(rw); + up_read(&rw->rwsem); +} + +static inline void au_rw_dgrade_lock(struct au_rwsem *rw) +{ + AuDbgRcntInc(rw); + downgrade_write(&rw->rwsem); +} + +static inline void au_rw_write_lock(struct au_rwsem *rw) +{ + down_write(&rw->rwsem); +} + +static inline void au_rw_write_lock_nested(struct au_rwsem *rw, + unsigned int lsc) +{ + down_write_nested(&rw->rwsem, lsc); +} + +static inline void au_rw_write_unlock(struct au_rwsem *rw) +{ + up_write(&rw->rwsem); +} + +/* why is not _nested version defined */ +static inline int au_rw_read_trylock(struct au_rwsem *rw) +{ + int ret = down_read_trylock(&rw->rwsem); + if (ret) + AuDbgRcntInc(rw); + return ret; +} + +static inline int au_rw_write_trylock(struct au_rwsem *rw) +{ + return down_write_trylock(&rw->rwsem); +} + +#undef AuDbgRcntInit +#undef AuDbgRcntInc +#undef AuDbgRcntDec + +/* to debug easier, do not make them inlined functions */ +#define AuRwMustNoWaiters(rw) AuDebugOn(!list_empty(&(rw)->rwsem.wait_list)) +#define AuRwMustAnyLock(rw) AuDebugOn(down_write_trylock(&(rw)->rwsem)) +#ifdef CONFIG_AUFS_DEBUG +#define AuRwMustReadLock(rw) do { \ + AuRwMustAnyLock(rw); \ + AuDebugOn(!atomic_read(&(rw)->rcnt)); \ +} while (0) + +#define AuRwMustWriteLock(rw) do { \ + AuRwMustAnyLock(rw); \ + AuDebugOn(atomic_read(&(rw)->rcnt)); \ +} while (0) +#else +#define AuRwMustReadLock(rw) AuRwMustAnyLock(rw) +#define AuRwMustWriteLock(rw) AuRwMustAnyLock(rw) +#endif /* CONFIG_AUFS_DEBUG */ + +#define AuSimpleLockRwsemFuncs(prefix, param, rwsem) \ +static inline void prefix##_read_lock(param) \ +{ au_rw_read_lock(&(rwsem)); } \ +static inline void prefix##_write_lock(param) \ +{ au_rw_write_lock(&(rwsem)); } \ +static inline int prefix##_read_trylock(param) \ +{ return au_rw_read_trylock(&(rwsem)); } \ +static inline int prefix##_write_trylock(param) \ +{ return au_rw_write_trylock(&(rwsem)); } +/* static inline void prefix##_read_trylock_nested(param, lsc) +{au_rw_read_trylock_nested(&(rwsem, lsc));} +static inline void prefix##_write_trylock_nestd(param, lsc) +{au_rw_write_trylock_nested(&(rwsem), nested);} */ + +#define AuSimpleUnlockRwsemFuncs(prefix, param, rwsem) \ +static inline void prefix##_read_unlock(param) \ +{ au_rw_read_unlock(&(rwsem)); } \ +static inline void prefix##_write_unlock(param) \ +{ au_rw_write_unlock(&(rwsem)); } \ +static inline void prefix##_downgrade_lock(param) \ +{ au_rw_dgrade_lock(&(rwsem)); } + +#define AuSimpleRwsemFuncs(prefix, param, rwsem) \ + AuSimpleLockRwsemFuncs(prefix, param, rwsem) \ + AuSimpleUnlockRwsemFuncs(prefix, param, rwsem) + +/* ---------------------------------------------------------------------- */ + +void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp); + +struct au_sbinfo; +struct nameidata *au_dup_nd(struct au_sbinfo *sbinfo, struct nameidata *dst, + struct nameidata *src); + +struct nameidata *au_fake_dm(struct nameidata *fake_nd, struct nameidata *nd, + struct super_block *sb, aufs_bindex_t bindex); +void au_fake_dm_release(struct nameidata *fake_nd); +struct vfsub_args; +int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode, + struct vfsub_args *vargs, struct nameidata *nd, + struct vfsmount *nfsmnt); + +struct au_hinode; +int au_copy_file(struct file *dst, struct file *src, loff_t len, + struct au_hinode *hdir, struct super_block *sb, + struct vfsub_args *vargs); + +#endif /* __KERNEL__ */ +#endif /* __AUFS_MISC_H__ */ diff --git a/fs/aufs/module.c b/fs/aufs/module.c new file mode 100644 index 0000000..030fa3b --- /dev/null +++ b/fs/aufs/module.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * module global variables and operations + * + * $Id: module.c,v 1.38 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +/* + * aufs caches + */ +struct kmem_cache *au_cachep[AuCache_Last]; +static int __init create_cache(void) +{ + au_cachep[AuCache_DINFO] = AuCache(au_dinfo); + if (au_cachep[AuCache_DINFO]) + au_cachep[AuCache_ICNTNR] = AuCache(aufs_icntnr); + if (au_cachep[AuCache_ICNTNR]) + au_cachep[AuCache_FINFO] = AuCache(au_finfo); + //au_cachep[AuCache_FINFO] = NULL; + if (au_cachep[AuCache_FINFO]) + au_cachep[AuCache_VDIR] = AuCache(au_vdir); + if (au_cachep[AuCache_VDIR]) + au_cachep[AuCache_DEHSTR] = AuCache(au_vdir_dehstr); + if (au_cachep[AuCache_DEHSTR]) + return 0; + + return -ENOMEM; +} + +static void destroy_cache(void) +{ + int i; + for (i = 0; i < AuCache_Last; i++) + if (au_cachep[i]) { + kmem_cache_destroy(au_cachep[i]); + au_cachep[i] = NULL; + } +} + +/* ---------------------------------------------------------------------- */ + +char au_esc_chars[0x20 + 3]; /* 0x01-0x20, backslash, del, and NULL */ +int au_dir_roflags; + +/* + * functions for module interface. + */ +MODULE_LICENSE("GPL"); +/* MODULE_LICENSE("GPL v2"); */ +MODULE_AUTHOR("Junjiro Okajima"); +MODULE_DESCRIPTION(AUFS_NAME " -- Another unionfs"); +MODULE_VERSION(AUFS_VERSION); + +/* it should be 'byte', but param_set_byte() prints it by "%c" */ +short aufs_nwkq = AUFS_NWKQ_DEF; +MODULE_PARM_DESC(nwkq, "the number of workqueue thread, " AUFS_WKQ_NAME); +module_param_named(nwkq, aufs_nwkq, short, S_IRUGO); + +int sysaufs_brs; +MODULE_PARM_DESC(brs, "use /fs/aufs/sbi_*/brN"); +module_param_named(brs, sysaufs_brs, int, S_IRUGO); + +/* ---------------------------------------------------------------------- */ + +static int __init aufs_init(void) +{ + int err, i; + char *p; + + au_debug_init(); +#ifdef CONFIG_AUFS_INO_T_64 + BUILD_BUG_ON(sizeof(ino_t) != sizeof(long long)); +#else + BUILD_BUG_ON(sizeof(ino_t) != sizeof(int)); +#endif + + p = au_esc_chars; + for (i = 1; i <= ' '; i++) + *p++ = i; + *p++ = '\\'; + *p++ = '\x7f'; + *p = 0; + + au_dir_roflags = au_file_roflags(O_DIRECTORY | O_LARGEFILE); + + err = -EINVAL; + if (unlikely(aufs_nwkq <= 0)) + goto out; + + err = sysaufs_init(); + if (unlikely(err)) + goto out; + err = au_wkq_init(); + if (unlikely(err)) + goto out_sysaufs; + err = au_inotify_init(); + if (unlikely(err)) + goto out_wkq; + err = au_sysrq_init(); + if (unlikely(err)) + goto out_inotify; + + err = create_cache(); + if (unlikely(err)) + goto out_sysrq; + + err = register_filesystem(&aufs_fs_type); + if (unlikely(err)) + goto out_cache; + pr_info(AUFS_NAME " " AUFS_VERSION "\n"); + return 0; /* success */ + + out_cache: + destroy_cache(); + out_sysrq: + au_sysrq_fin(); + out_inotify: + au_inotify_fin(); + out_wkq: + au_wkq_fin(); + out_sysaufs: + sysaufs_fin(); + out: + AuTraceErr(err); + return err; +} + +static void __exit aufs_exit(void) +{ + unregister_filesystem(&aufs_fs_type); + destroy_cache(); + + au_sysrq_fin(); + au_inotify_fin(); + au_wkq_fin(); + sysaufs_fin(); +} + +module_init(aufs_init); +module_exit(aufs_exit); + +/* ---------------------------------------------------------------------- */ + +/* fake Kconfig */ +#if 1 + +#if AUFS_BRANCH_MAX > 511 && PAGE_SIZE > 4096 +#warning pagesize is larger than 4kb, \ + CONFIG_AUFS_BRANCH_MAX_511 or smaller is recommended. +#endif + +#ifdef CONFIG_AUFS_SYSAUFS +#ifndef CONFIG_SYSFS +#error CONFIG_AUFS_SYSAUFS requires CONFIG_SYSFS. +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#error CONFIG_AUFS_SYSAUFS requires linux-2.6.18 and later. +#endif +#endif /* CONFIG_AUFS_SYSAUFS */ + +#ifdef CONFIG_AUFS_HINOTIFY +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#error CONFIG_AUFS_HINOTIFY is supported in linux-2.6.18 and later. +#endif +#ifndef CONFIG_INOTIFY +#error enable CONFIG_INOTIFY to use CONFIG_AUFS_HINOTIFY. +#endif +#endif /* CONFIG_AUFS_HINOTIFY */ + +#ifdef CONFIG_AUFS_EXPORT +#if !defined(CONFIG_EXPORTFS) && !defined(CONFIG_EXPORTFS_MODULE) +#error CONFIG_AUFS_EXPORT requires CONFIG_EXPORTFS +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#error CONFIG_AUFS_EXPORT requires linux-2.6.18 and later. +#endif +#if defined(CONFIG_EXPORTFS_MODULE) && defined(CONFIG_AUFS) +#error need CONFIG_EXPORTFS = y to link aufs statically with CONFIG_AUFS_EXPORT +#endif +#endif /* CONFIG_AUFS_EXPORT */ + +#ifdef CONFIG_AUFS_SEC_PERM_PATCH +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24) +#warning CONFIG_AUFS_SEC_PERM_PATCH does not support before linux-2.6.24. +#endif +#ifndef CONFIG_SECURITY +#warning AUFS_SEC_PERM_PATCH is unnecessary since SECURITY is disabled. +#endif +#ifdef CONFIG_AUFS +#warning AUFS_SEC_PERM_PATCH is unnecessary since AUFS is not a module. +#endif +#endif + +#if defined(CONFIG_AUFS_SPLICE_PATCH) \ + && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 23) +#error CONFIG_AUFS_SPLICE_PATCH is supported linux-2.6.23 and later. +#endif + +#ifdef CONFIG_AUFS_LHASH_PATCH +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +#error CONFIG_AUFS_LHASH_PATCH does not support before linux-2.6.19. +#endif +#if !defined(CONFIG_NFS_FS) && !defined(CONFIG_NFS_FS_MODULE) +#warning CONFIG_AUFS_LHASH_PATCH is unnecessary since CONFIG_NFS_FS is disabled. +#endif +#endif + +#ifdef CONFIG_AUFS_PUT_FILP_PATCH +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +#warning CONFIG_AUFS_PUT_FILP_PATCH does not support before linux-2.6.19. +#endif +#ifndef CONFIG_NFS_V4 +#warning AUFS_PUT_FILP_PATCH is unnecessary since NFS_V4 is disabled. +#endif +#ifdef CONFIG_AUFS +#warning AUFS_PUT_FILP_PATCH is unnecessary since AUFS is not a module. +#endif +#endif /* CONFIG_AUFS_PUT_FILP_PATCH */ + +#ifdef CONFIG_AUFS_FSYNC_SUPER_PATCH +#ifdef CONFIG_AUFS +#warning AUFS_FSYNC_SUPER_PATCH is unnecessary since AUFS is not a module. +#endif +#endif + +#ifdef CONFIG_DENY_WRITE_ACCESS_PATCH +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 17) +#warning AUFS_DENY_WRITE_ACCESS_PATCH is supported in linux-2.6.17 and later. +#endif +#ifdef CONFIG_AUFS +#warning AUFS_DENY_WRITE_ACCESS_PATCH is unnecessary since AUFS is not a module. +#endif +#endif + +#ifdef CONFIG_AUFS_KSIZE_PATCH +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) +#warning CONFIG_AUFS_KSIZE_PATCH is unnecessary for linux-2.6.22 and later. +#endif +#ifdef CONFIG_AUFS +#warning CONFIG_AUFS_KSIZE_PATCH is unnecessary since AUFS is not a module. +#endif +#endif + +#ifdef CONFIG_AUFS_WORKAROUND_FUSE +#if !defined(CONFIG_FUSE_FS) && !defined(CONFIG_FUSE_FS_MODULE) +#warning CONFIG_AUFS_WORKAROUND_FUSE is enabled while FUSE is disabled. +#endif +#endif + +#ifdef CONFIG_AUFS_DEBUG_LOCK +#ifndef CONFIG_AUFS_MAGIC_SYSRQ +#warning CONFIG_AUFS_DEBUG_LOCK is enabled but CONFIG_AUFS_MAGIC_SYSRQ. +#endif +#endif + +#ifdef CONFIG_AUFS_COMPAT +#warning CONFIG_AUFS_COMPAT will be removed in the near future. +#endif + +#ifdef CONFIG_AUFS_UNIONFS23_PATCH +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) \ + && !defined(CONFIG_AUFS_SPLICE_PATCH) +#error mis-config. AUFS_UNIONFS23_PATCH is enabled but AUFS_SPLICE_PATCH. +#endif +#endif + +#ifdef CONFIG_DEBUG_PROVE_LOCKING +#if MAX_LOCKDEP_SUBCLASSES < AuLsc_I_End +#warning lockdep will not work since aufs uses deeper locks. +#endif +#endif + +#endif diff --git a/fs/aufs/module.h b/fs/aufs/module.h new file mode 100644 index 0000000..6fc1a1a --- /dev/null +++ b/fs/aufs/module.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * module initialization and module-global + * + * $Id: module.h,v 1.22 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_MODULE_H__ +#define __AUFS_MODULE_H__ + +#ifdef __KERNEL__ + +#include + +/* ---------------------------------------------------------------------- */ + +/* module parameters */ +extern short aufs_nwkq; +extern int sysaufs_brs; + +/* ---------------------------------------------------------------------- */ + +extern char au_esc_chars[]; +extern int au_dir_roflags; + +/* kmem cache */ +enum { + AuCache_DINFO, + AuCache_ICNTNR, + AuCache_FINFO, + AuCache_VDIR, + AuCache_DEHSTR, +#ifdef CONFIG_AUFS_HINOTIFY + AuCache_HINOTIFY, +#endif + AuCache_Last +}; + +extern struct kmem_cache *au_cachep[]; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +#define AuCacheArgs(type, sz) (type), (sz), 0, SLAB_RECLAIM_ACCOUNT, NULL +#else +#define AuCacheArgs(type, sz) (type), (sz), 0, SLAB_RECLAIM_ACCOUNT, NULL, NULL +#endif +#define AuCache(type) \ + kmem_cache_create(AuCacheArgs(#type, sizeof(struct type))) + +/* ---------------------------------------------------------------------- */ + +#define AuCacheFuncs(name, index) \ +static inline void *au_cache_alloc_##name(void) \ +{ return kmem_cache_alloc(au_cachep[index], GFP_NOFS); } \ +static inline void au_cache_free_##name(void *p) \ +{ kmem_cache_free(au_cachep[index], p); } + +AuCacheFuncs(dinfo, AuCache_DINFO); +AuCacheFuncs(icntnr, AuCache_ICNTNR); +AuCacheFuncs(finfo, AuCache_FINFO); +AuCacheFuncs(vdir, AuCache_VDIR); +AuCacheFuncs(dehstr, AuCache_DEHSTR); + +#endif /* __KERNEL__ */ +#endif /* __AUFS_MODULE_H__ */ diff --git a/fs/aufs/opts.c b/fs/aufs/opts.c new file mode 100644 index 0000000..c4e1412 --- /dev/null +++ b/fs/aufs/opts.c @@ -0,0 +1,1622 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * mount options/flags + * + * $Id: opts.c,v 1.72 2009/01/26 06:24:05 sfjro Exp $ + */ + +#include /* a distribution requires */ +#include +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +enum { + Opt_br, + Opt_add, Opt_del, Opt_mod, Opt_reorder, Opt_append, Opt_prepend, + Opt_idel, Opt_imod, Opt_ireorder, + Opt_dirwh, Opt_rdcache, Opt_deblk, Opt_nhash, Opt_rendir, + Opt_xino, Opt_zxino, Opt_noxino, + Opt_trunc_xino, Opt_trunc_xino_v, Opt_notrunc_xino, + Opt_trunc_xino_path, Opt_itrunc_xino, + Opt_xinodir, + Opt_trunc_xib, Opt_notrunc_xib, + Opt_dirperm1, Opt_nodirperm1, + Opt_shwh, Opt_noshwh, + Opt_plink, Opt_noplink, Opt_list_plink, Opt_clean_plink, + Opt_udba, + /* Opt_lock, Opt_unlock, */ + Opt_cmd, Opt_cmd_args, + Opt_diropq_a, Opt_diropq_w, + Opt_warn_perm, Opt_nowarn_perm, + Opt_wbr_copyup, Opt_wbr_create, + Opt_coo, + Opt_dlgt, Opt_nodlgt, + Opt_refrof, Opt_norefrof, + Opt_verbose, Opt_noverbose, + Opt_sum, Opt_nosum, Opt_wsum, + Opt_tail, Opt_ignore, Opt_ignore_silent, Opt_err +}; + +static match_table_t options = { + {Opt_br, "br=%s"}, + {Opt_br, "br:%s"}, + + {Opt_add, "add=%d:%s"}, + {Opt_add, "add:%d:%s"}, + {Opt_add, "ins=%d:%s"}, + {Opt_add, "ins:%d:%s"}, + {Opt_append, "append=%s"}, + {Opt_append, "append:%s"}, + {Opt_prepend, "prepend=%s"}, + {Opt_prepend, "prepend:%s"}, + + {Opt_del, "del=%s"}, + {Opt_del, "del:%s"}, + /* {Opt_idel, "idel:%d"}, */ + {Opt_mod, "mod=%s"}, + {Opt_mod, "mod:%s"}, + {Opt_imod, "imod:%d:%s"}, + + {Opt_dirwh, "dirwh=%d"}, + {Opt_dirwh, "dirwh:%d"}, + + {Opt_xino, "xino=%s"}, + {Opt_xino, "xino:%s"}, +#if 0 /* def CONFIG_AUFS_EXPORT */ /* reserved for futur use */ + {Opt_xinodir, "xinodir=%s"}, + {Opt_xinodir, "xinodir:%s"}, +#endif + {Opt_noxino, "noxino"}, + {Opt_trunc_xino, "trunc_xino"}, + {Opt_trunc_xino_v, "trunc_xino_v=%d:%d"}, + {Opt_notrunc_xino, "notrunc_xino"}, + {Opt_trunc_xino_path, "trunc_xino=%s"}, + {Opt_trunc_xino_path, "trunc_xino:%s"}, + {Opt_itrunc_xino, "itrunc_xino=%d"}, + {Opt_itrunc_xino, "itrunc_xino:%d"}, + /* {Opt_zxino, "zxino=%s"}, */ + {Opt_trunc_xib, "trunc_xib"}, + {Opt_notrunc_xib, "notrunc_xib"}, + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) + {Opt_plink, "plink"}, + {Opt_noplink, "noplink"}, +#ifdef CONFIG_AUFS_DEBUG + {Opt_list_plink, "list_plink"}, +#endif + {Opt_clean_plink, "clean_plink"}, +#endif + + {Opt_udba, "udba=%s"}, + + {Opt_diropq_a, "diropq=always"}, + {Opt_diropq_a, "diropq=a"}, + {Opt_diropq_w, "diropq=whiteouted"}, + {Opt_diropq_w, "diropq=w"}, + + {Opt_warn_perm, "warn_perm"}, + {Opt_nowarn_perm, "nowarn_perm"}, + +#ifdef CONFIG_AUFS_DLGT + {Opt_dlgt, "dlgt"}, + {Opt_dirperm1, "dirperm1"}, +#endif + {Opt_nodlgt, "nodlgt"}, + {Opt_nodirperm1, "nodirperm1"}, + +#ifdef CONFIG_AUFS_SHWH + {Opt_shwh, "shwh"}, +#endif + {Opt_noshwh, "noshwh"}, + + {Opt_rendir, "rendir=%d"}, + {Opt_rendir, "rendir:%d"}, + + {Opt_refrof, "refrof"}, + {Opt_norefrof, "norefrof"}, + + {Opt_verbose, "verbose"}, + {Opt_verbose, "v"}, + {Opt_noverbose, "noverbose"}, + {Opt_noverbose, "quiet"}, + {Opt_noverbose, "q"}, + {Opt_noverbose, "silent"}, + + {Opt_sum, "sum"}, + {Opt_nosum, "nosum"}, + {Opt_wsum, "wsum"}, + + {Opt_rdcache, "rdcache=%d"}, + {Opt_rdcache, "rdcache:%d"}, + + {Opt_coo, "coo=%s"}, + + {Opt_wbr_create, "create=%s"}, + {Opt_wbr_create, "create:%s"}, + {Opt_wbr_create, "create_policy=%s"}, + {Opt_wbr_create, "create_policy:%s"}, + {Opt_wbr_copyup, "cpup=%s"}, + {Opt_wbr_copyup, "cpup:%s"}, + {Opt_wbr_copyup, "copyup=%s"}, + {Opt_wbr_copyup, "copyup:%s"}, + {Opt_wbr_copyup, "copyup_policy=%s"}, + {Opt_wbr_copyup, "copyup_policy:%s"}, + + /* internal use for the scripts */ + {Opt_ignore_silent, "si=%s"}, + +#if 0 /* reserved for future use */ + {Opt_deblk, "deblk=%d"}, + {Opt_deblk, "deblk:%d"}, + {Opt_nhash, "nhash=%d"}, + {Opt_nhash, "nhash:%d"}, +#endif + + {Opt_br, "dirs=%s"}, + {Opt_ignore, "debug=%d"}, + {Opt_ignore, "delete=whiteout"}, + {Opt_ignore, "delete=all"}, + {Opt_ignore, "imap=%s"}, + + {Opt_err, NULL} +}; + +/* ---------------------------------------------------------------------- */ + +static au_parser_pattern_t au_parser_pattern(int val, struct match_token *token) +{ + while (token->pattern) { + if (token->token == val) + return token->pattern; + token++; + } + BUG(); + return "??"; +} + +/* ---------------------------------------------------------------------- */ + +#define RW "rw" +#define RO "ro" +#define WH "wh" +#define RR "rr" +#define NoLinkWH "nolwh" + +static match_table_t brperms = { + {AuBrPerm_RR, RR}, + {AuBrPerm_RO, RO}, + {AuBrPerm_RW, RW}, + + {AuBrPerm_RRWH, RR "+" WH}, + {AuBrPerm_ROWH, RO "+" WH}, + {AuBrPerm_RWNoLinkWH, RW "+" NoLinkWH}, + + {AuBrPerm_ROWH, "nfsro"}, + {AuBrPerm_RO, NULL} +}; + +static noinline_for_stack int br_perm_val(char *perm) +{ + int val; + substring_t args[MAX_OPT_ARGS]; + + AuDebugOn(!perm || !*perm); + LKTRTrace("perm %s\n", perm); + val = match_token(perm, brperms, args); + AuTraceErr(val); + return val; +} + +au_parser_pattern_t au_optstr_br_perm(int brperm) +{ + return au_parser_pattern(brperm, brperms); +} + +/* ---------------------------------------------------------------------- */ + +static match_table_t udbalevel = { + {AuOpt_UDBA_REVAL, "reval"}, +#ifdef CONFIG_AUFS_HINOTIFY + {AuOpt_UDBA_INOTIFY, "inotify"}, +#endif + {AuOpt_UDBA_NONE, "none"}, + {-1, NULL} +}; + +static noinline_for_stack int udba_val(char *str) +{ + substring_t args[MAX_OPT_ARGS]; + return match_token(str, udbalevel, args); +} + +au_parser_pattern_t au_optstr_udba(int udba) +{ + return au_parser_pattern(udba, udbalevel); +} + +/* ---------------------------------------------------------------------- */ + +static match_table_t coolevel = { + {AuOpt_COO_NONE, "none"}, + {AuOpt_COO_LEAF, "leaf"}, + {AuOpt_COO_ALL, "all"}, + {-1, NULL} +}; + +static noinline_for_stack int coo_val(char *str) +{ + substring_t args[MAX_OPT_ARGS]; + return match_token(str, coolevel, args); +} + +au_parser_pattern_t au_optstr_coo(int coo) +{ + return au_parser_pattern(coo, coolevel); +} + +/* ---------------------------------------------------------------------- */ + +static match_table_t au_wbr_create_policy = { + {AuWbrCreate_TDP, "tdp"}, + {AuWbrCreate_TDP, "top-down-parent"}, + {AuWbrCreate_RR, "rr"}, + {AuWbrCreate_RR, "round-robin"}, + {AuWbrCreate_MFS, "mfs"}, + {AuWbrCreate_MFS, "most-free-space"}, + {AuWbrCreate_MFSV, "mfs:%d"}, + {AuWbrCreate_MFSV, "most-free-space:%d"}, + + {AuWbrCreate_MFSRR, "mfsrr:%d"}, + {AuWbrCreate_MFSRRV, "mfsrr:%d:%d"}, + {AuWbrCreate_PMFS, "pmfs"}, + {AuWbrCreate_PMFSV, "pmfs:%d"}, + + {-1, NULL} +}; + +/* cf. linux/lib/parser.c */ +static int au_match_ull(substring_t *s, unsigned long long *result) +{ + int err; + unsigned int len; + char a[32]; + char *endp; + + err = -ERANGE; + len = s->to - s->from; + if (len + 1 <= sizeof(a)) { + memcpy(a, s->from, len); + a[len] = '\0'; +#if 1 + *result = simple_strtoull(a, &endp, 0); + err = 0; + if (endp == a) + err = -EINVAL; +#else + char *next; + *result = memparse(a, &next); + err = *result; + if (!IS_ERR((void *)err)) + err = 0; +#endif + } + return err; +} + +static int au_wbr_mfs_wmark(substring_t *arg, char *str, + struct au_opt_wbr_create *create) +{ + int err; + unsigned long long ull; + + err = 0; + if (!au_match_ull(arg, &ull)) + create->mfsrr_watermark = ull; + else { + AuErr("bad integer in %s\n", str); + err = -EINVAL; + } + + AuTraceErr(err); + return err; +} + +static int au_wbr_mfs_sec(substring_t *arg, char *str, + struct au_opt_wbr_create *create) +{ + int n, err; + + err = 0; + if (!match_int(arg, &n) && 0 <= n) + create->mfs_second = n; + else { + AuErr("bad integer in %s\n", str); + err = -EINVAL; + } + + AuTraceErr(err); + return err; +} + +static noinline_for_stack +int au_wbr_create_val(char *str, struct au_opt_wbr_create *create) +{ + int err, e; + substring_t args[MAX_OPT_ARGS]; + + err = match_token(str, au_wbr_create_policy, args); + create->wbr_create = err; + switch (err) { + case AuWbrCreate_MFSRRV: + e = au_wbr_mfs_wmark(&args[0], str, create); + if (!e) + e = au_wbr_mfs_sec(&args[1], str, create); + if (unlikely(e)) + err = e; + break; + case AuWbrCreate_MFSRR: + e = au_wbr_mfs_wmark(&args[0], str, create); + if (unlikely(e)) { + err = e; + break; + } + /*FALLTHROUGH*/ + case AuWbrCreate_MFS: + case AuWbrCreate_PMFS: + create->mfs_second = AUFS_MFS_SECOND_DEF; + break; + case AuWbrCreate_MFSV: + case AuWbrCreate_PMFSV: + e = au_wbr_mfs_sec(&args[0], str, create); + if (unlikely(e)) + err = e; + break; + } + + return err; +} + +au_parser_pattern_t au_optstr_wbr_create(int wbr_create) +{ + return au_parser_pattern(wbr_create, au_wbr_create_policy); +} + +static match_table_t au_wbr_copyup_policy = { + {AuWbrCopyup_TDP, "tdp"}, + {AuWbrCopyup_TDP, "top-down-parent"}, + {AuWbrCopyup_BUP, "bup"}, + {AuWbrCopyup_BUP, "bottom-up-parent"}, + {AuWbrCopyup_BU, "bu"}, + {AuWbrCopyup_BU, "bottom-up"}, + {-1, NULL} +}; + +static noinline_for_stack int au_wbr_copyup_val(char *str) +{ + substring_t args[MAX_OPT_ARGS]; + return match_token(str, au_wbr_copyup_policy, args); +} + +au_parser_pattern_t au_optstr_wbr_copyup(int wbr_copyup) +{ + return au_parser_pattern(wbr_copyup, au_wbr_copyup_policy); +} + +/* ---------------------------------------------------------------------- */ + +static const int lkup_dirflags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY; + +static void dump_opts(struct au_opts *opts) +{ +#ifdef CONFIG_AUFS_DEBUG + /* reduce stack space */ + union { + struct au_opt_add *add; + struct au_opt_del *del; + struct au_opt_mod *mod; + struct au_opt_xino *xino; + struct au_opt_xinodir *xinodir; + struct au_opt_xino_itrunc *xino_itrunc; + struct au_opt_wbr_create *create; + } u; + struct au_opt *opt; + + AuTraceEnter(); + + opt = opts->opt; + while (/* opt < opts_tail && */ opt->type != Opt_tail) { + switch (opt->type) { + case Opt_add: + u.add = &opt->add; + LKTRTrace("add {b%d, %s, 0x%x, %p}\n", + u.add->bindex, u.add->path, u.add->perm, + u.add->nd.dentry); + break; + case Opt_del: + case Opt_idel: + u.del = &opt->del; + LKTRTrace("del {%s, %p}\n", u.del->path, u.del->h_root); + break; + case Opt_mod: + case Opt_imod: + u.mod = &opt->mod; + LKTRTrace("mod {%s, 0x%x, %p}\n", + u.mod->path, u.mod->perm, u.mod->h_root); + break; + case Opt_append: + u.add = &opt->add; + LKTRTrace("append {b%d, %s, 0x%x, %p}\n", + u.add->bindex, u.add->path, u.add->perm, + u.add->nd.dentry); + break; + case Opt_prepend: + u.add = &opt->add; + LKTRTrace("prepend {b%d, %s, 0x%x, %p}\n", + u.add->bindex, u.add->path, u.add->perm, + u.add->nd.dentry); + break; + case Opt_dirwh: + LKTRTrace("dirwh %d\n", opt->dirwh); + break; + case Opt_rdcache: + LKTRTrace("rdcache %d\n", opt->rdcache); + break; + case Opt_xino: + u.xino = &opt->xino; + LKTRTrace("xino {%s %.*s}\n", + u.xino->path, + AuDLNPair(u.xino->file->f_dentry)); + break; +#if 0 /* reserved for future use */ + case Opt_xinodir: + u.xinodir = &opt->xinodir; + LKTRTrace("xinodir {%s %.*s}\n", + u.xinodir->name, + AuDLNPair(u.xinodir->path.dentry)); + break; +#endif + case Opt_trunc_xino: + LKTRLabel(trunc_xino); + break; + case Opt_notrunc_xino: + LKTRLabel(notrunc_xino); + break; + case Opt_trunc_xino_path: + case Opt_itrunc_xino: + u.xino_itrunc = &opt->xino_itrunc; + LKTRTrace("trunc_xino %d\n", u.xino_itrunc->bindex); + break; + + case Opt_noxino: + LKTRLabel(noxino); + break; + case Opt_trunc_xib: + LKTRLabel(trunc_xib); + break; + case Opt_notrunc_xib: + LKTRLabel(notrunc_xib); + break; + case Opt_dirperm1: + LKTRLabel(dirperm1); + break; + case Opt_nodirperm1: + LKTRLabel(nodirperm1); + break; + case Opt_shwh: + LKTRLabel(shwh); + break; + case Opt_noshwh: + LKTRLabel(noshwh); + break; + case Opt_plink: + LKTRLabel(plink); + break; + case Opt_noplink: + LKTRLabel(noplink); + break; + case Opt_list_plink: + LKTRLabel(list_plink); + break; + case Opt_clean_plink: + LKTRLabel(clean_plink); + break; + case Opt_udba: + LKTRTrace("udba %d, %s\n", + opt->udba, au_optstr_udba(opt->udba)); + break; + case Opt_diropq_a: + LKTRLabel(diropq_a); + break; + case Opt_diropq_w: + LKTRLabel(diropq_w); + break; + case Opt_warn_perm: + LKTRLabel(warn_perm); + break; + case Opt_nowarn_perm: + LKTRLabel(nowarn_perm); + break; + case Opt_dlgt: + LKTRLabel(dlgt); + break; + case Opt_nodlgt: + LKTRLabel(nodlgt); + break; + case Opt_refrof: + LKTRLabel(refrof); + break; + case Opt_norefrof: + LKTRLabel(norefrof); + break; + case Opt_verbose: + LKTRLabel(verbose); + break; + case Opt_noverbose: + LKTRLabel(noverbose); + break; + case Opt_sum: + LKTRLabel(sum); + break; + case Opt_nosum: + LKTRLabel(nosum); + break; + case Opt_wsum: + LKTRLabel(wsum); + break; + case Opt_coo: + LKTRTrace("coo %d, %s\n", + opt->coo, au_optstr_coo(opt->coo)); + break; + case Opt_wbr_create: + u.create = &opt->wbr_create; + LKTRTrace("create %d, %s\n", u.create->wbr_create, + au_optstr_wbr_create(u.create->wbr_create)); + switch (u.create->wbr_create) { + case AuWbrCreate_MFSV: + case AuWbrCreate_PMFSV: + LKTRTrace("%d sec\n", u.create->mfs_second); + break; + case AuWbrCreate_MFSRR: + LKTRTrace("%llu watermark\n", + u.create->mfsrr_watermark); + break; + case AuWbrCreate_MFSRRV: + LKTRTrace("%llu watermark, %d sec\n", + u.create->mfsrr_watermark, + u.create->mfs_second); + break; + } + break; + case Opt_wbr_copyup: + LKTRTrace("copyup %d, %s\n", opt->wbr_copyup, + au_optstr_wbr_copyup(opt->wbr_copyup)); + break; + default: + BUG(); + } + opt++; + } +#endif +} + +void au_opts_free(struct au_opts *opts) +{ + struct au_opt *opt; + + AuTraceEnter(); + + opt = opts->opt; + while (opt->type != Opt_tail) { + switch (opt->type) { + case Opt_add: + case Opt_append: + case Opt_prepend: + path_release(&opt->add.nd); + break; + case Opt_del: + case Opt_idel: + dput(opt->del.h_root); + break; + case Opt_mod: + case Opt_imod: + dput(opt->mod.h_root); + break; + case Opt_xino: + fput(opt->xino.file); + break; +#if 0 /* reserved for future use */ + case Opt_xinodir: + path_put(&opt->xinodir.path); + break; +#endif + } + opt++; + } +} + +static int opt_add(struct au_opt *opt, char *opt_str, unsigned long sb_flags, + struct super_block *sb, aufs_bindex_t bindex) +{ + int err; + struct au_opt_add *add = &opt->add; + char *p; + + LKTRTrace("%s, b%d\n", opt_str, bindex); + + add->bindex = bindex; + add->perm = AuBrPerm_Last; + add->path = opt_str; + p = strchr(opt_str, '='); + if (p) { + *p++ = 0; + if (*p) + add->perm = br_perm_val(p); + } + + /* LSM may detect it */ + /* do not superio. */ + err = vfsub_path_lookup(add->path, lkup_dirflags, &add->nd); + if (!err) { + if (!p /* && add->perm == AuBrPerm_Last */) { + add->perm = AuBrPerm_RO; + if (au_test_def_rr(add->nd.dentry->d_sb)) + add->perm = AuBrPerm_RR; + if (!bindex && !(sb_flags & MS_RDONLY)) + add->perm = AuBrPerm_RW; +#ifdef CONFIG_AUFS_COMPAT + add->perm = AuBrPerm_RW; +#endif + } + opt->type = Opt_add; + goto out; + } + AuErr("lookup failed %s (%d)\n", add->path, err); + err = -EINVAL; + + out: + AuTraceErr(err); + return err; +} + +/* called without aufs lock */ +int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts) +{ + int err, n, token; + struct dentry *root; + struct au_opt *opt, *opt_tail; + char *opt_str, *p; + aufs_bindex_t bindex, bend; + unsigned char skipped; + union { + struct au_opt_del *del; + struct au_opt_mod *mod; + struct au_opt_xino *xino; + struct au_opt_xinodir *xinodir; + struct au_opt_xino_itrunc *xino_itrunc; + struct au_opt_wbr_create *create; + } u; + struct file *file; + /* reduce the stack space */ + struct { + substring_t args[MAX_OPT_ARGS]; + struct nameidata nd; + } *a; + + LKTRTrace("%s, nopts %d\n", str, opts->max_opt); + + err = -ENOMEM; + a = kmalloc(sizeof(*a), GFP_NOFS); + if (unlikely(!a)) + goto out; + + root = sb->s_root; + err = 0; + bindex = 0; + opt = opts->opt; + opt_tail = opt + opts->max_opt - 1; + opt->type = Opt_tail; + while (!err && (opt_str = strsep(&str, ",")) && *opt_str) { + err = -EINVAL; + token = match_token(opt_str, options, a->args); + LKTRTrace("%s, token %d, a->args[0]{%p, %p}\n", + opt_str, token, a->args[0].from, a->args[0].to); + + skipped = 0; + switch (token) { + case Opt_br: + err = 0; + while (!err && (opt_str = strsep(&a->args[0].from, ":")) + && *opt_str) { + err = opt_add(opt, opt_str, opts->sb_flags, sb, + bindex++); + if (unlikely(!err && ++opt > opt_tail)) { + err = -E2BIG; + break; + } + opt->type = Opt_tail; + skipped = 1; + } + break; + case Opt_add: + if (unlikely(match_int(&a->args[0], &n))) { + AuErr("bad integer in %s\n", opt_str); + break; + } + bindex = n; + err = opt_add(opt, a->args[1].from, opts->sb_flags, sb, + bindex); + break; + case Opt_append: + err = opt_add(opt, a->args[0].from, opts->sb_flags, sb, + /*dummy bindex*/1); + if (!err) + opt->type = token; + break; + case Opt_prepend: + err = opt_add(opt, a->args[0].from, opts->sb_flags, sb, + /*bindex*/0); + if (!err) + opt->type = token; + break; + case Opt_del: + u.del = &opt->del; + u.del->path = a->args[0].from; + LKTRTrace("del path %s\n", u.del->path); + /* LSM may detect it */ + /* do not superio. */ + err = vfsub_path_lookup(u.del->path, lkup_dirflags, + &a->nd); + if (unlikely(err)) { + AuErr("lookup failed %s (%d)\n", + u.del->path, err); + break; + } + u.del->h_root = dget(a->nd.dentry); + path_release(&a->nd); + opt->type = token; + break; +#if 0 /* reserved for future use */ + case Opt_idel: + u.del = &opt->del; + u.del->path = "(indexed)"; + if (unlikely(match_int(&a->args[0], &n))) { + AuErr("bad integer in %s\n", opt_str); + break; + } + bindex = n; + aufs_read_lock(root, AuLock_FLUSH); + if (bindex < 0 || au_sbend(sb) < bindex) { + AuErr("out of bounds, %d\n", bindex); + aufs_read_unlock(root, !AuLock_IR); + break; + } + err = 0; + u.del->h_root = dget(au_h_dptr(root, bindex)); + opt->type = token; + aufs_read_unlock(root, !AuLock_IR); + break; +#endif + case Opt_mod: + u.mod = &opt->mod; + u.mod->path = a->args[0].from; + p = strchr(u.mod->path, '='); + if (unlikely(!p)) { + AuErr("no permssion %s\n", opt_str); + break; + } + *p++ = 0; + u.mod->perm = br_perm_val(p); + LKTRTrace("mod path %s, perm 0x%x, %s\n", + u.mod->path, u.mod->perm, p); + /* LSM may detect it */ + /* do not superio. */ + err = vfsub_path_lookup(u.mod->path, lkup_dirflags, + &a->nd); + if (unlikely(err)) { + AuErr("lookup failed %s (%d)\n", + u.mod->path, err); + break; + } + u.mod->h_root = dget(a->nd.dentry); + path_release(&a->nd); + opt->type = token; + break; +#ifdef IMOD /* reserved for future use */ + case Opt_imod: + u.mod = &opt->mod; + u.mod->path = "(indexed)"; + if (unlikely(match_int(&a->args[0], &n))) { + AuErr("bad integer in %s\n", opt_str); + break; + } + bindex = n; + aufs_read_lock(root, AuLock_FLUSH); + if (bindex < 0 || au_sbend(sb) < bindex) { + AuErr("out of bounds, %d\n", bindex); + aufs_read_unlock(root, !AuLock_IR); + break; + } + u.mod->perm = br_perm_val(a->args[1].from); + LKTRTrace("mod path %s, perm 0x%x, %s\n", + u.mod->path, u.mod->perm, a->args[1].from); + err = 0; + u.mod->h_root = dget(au_h_dptr(root, bindex)); + opt->type = token; + aufs_read_unlock(root, !AuLock_IR); + break; +#endif + case Opt_xino: + u.xino = &opt->xino; + file = au_xino_create(sb, a->args[0].from, /*silent*/0); + err = PTR_ERR(file); + if (IS_ERR(file)) + break; + err = -EINVAL; + if (unlikely(file->f_dentry->d_sb == sb)) { + fput(file); + AuErr("%s must be outside\n", a->args[0].from); + break; + } + err = 0; + u.xino->file = file; + u.xino->path = a->args[0].from; + opt->type = token; + break; + +#if 0 /* def CONFIG_AUFS_EXPORT */ /* reserved for futur use */ + case Opt_xinodir: + u.xinodir = &opt->xinodir; + u.xinodir->name = a->args[0].from; + err = vfsub_path_lookup(u.xinodir->name, lkup_dirflags, + &a->nd); + if (unlikely(err)) { + AuErr("lookup failed %s (%d)\n", + u.xinodir->name, err); + break; + } + u.xinodir->path = a->nd.path; + /* do not path_put() */ + opt->type = token; + break; +#endif + + case Opt_trunc_xino_path: + u.xino_itrunc = &opt->xino_itrunc; + p = a->args[0].from; + LKTRTrace("trunc_xino path %s\n", p); + /* LSM may detect it */ + /* do not superio. */ + err = vfsub_path_lookup(p, lkup_dirflags, &a->nd); + if (unlikely(err)) { + AuErr("lookup failed %s (%d)\n", p , err); + break; + } + u.xino_itrunc->bindex = -1; + aufs_read_lock(root, AuLock_FLUSH); + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) { + if (au_h_dptr(root, bindex) == a->nd.dentry) { + u.xino_itrunc->bindex = bindex; + break; + } + } + aufs_read_unlock(root, !AuLock_IR); + path_release(&a->nd); + if (unlikely(u.xino_itrunc->bindex < 0)) { + AuErr("no such branch %s\n", p); + err = -EINVAL; + break; + } + opt->type = token; + break; + + case Opt_itrunc_xino: + u.xino_itrunc = &opt->xino_itrunc; + if (unlikely(match_int(&a->args[0], &n))) { + AuErr("bad integer in %s\n", opt_str); + break; + } + u.xino_itrunc->bindex = n; + aufs_read_lock(root, AuLock_FLUSH); + if (n < 0 || au_sbend(sb) < n) { + AuErr("out of bounds, %d\n", n); + aufs_read_unlock(root, !AuLock_IR); + break; + } + aufs_read_unlock(root, !AuLock_IR); + err = 0; + opt->type = token; + break; + + case Opt_dirwh: + if (unlikely(match_int(&a->args[0], &opt->dirwh))) + break; + err = 0; + opt->type = token; + break; + + case Opt_rdcache: + if (unlikely(match_int(&a->args[0], &opt->rdcache))) + break; + err = 0; + opt->type = token; + break; + + case Opt_shwh: + if (opts->sb_flags & MS_RDONLY) { + err = 0; + opt->type = token; + } else + AuErr("shwh requires ro\n"); + break; + + case Opt_trunc_xino: + case Opt_notrunc_xino: + case Opt_noxino: + case Opt_trunc_xib: + case Opt_notrunc_xib: + case Opt_dirperm1: + case Opt_nodirperm1: + case Opt_noshwh: + case Opt_plink: + case Opt_noplink: + case Opt_list_plink: + case Opt_clean_plink: + case Opt_diropq_a: + case Opt_diropq_w: + case Opt_warn_perm: + case Opt_nowarn_perm: + case Opt_dlgt: + case Opt_nodlgt: + case Opt_refrof: + case Opt_norefrof: + case Opt_verbose: + case Opt_noverbose: + case Opt_sum: + case Opt_nosum: + case Opt_wsum: + err = 0; + opt->type = token; + break; + + case Opt_udba: + opt->udba = udba_val(a->args[0].from); + if (opt->udba >= 0) { + err = 0; + opt->type = token; + } else + AuErr("wrong value, %s\n", opt_str); + break; + + case Opt_wbr_create: + u.create = &opt->wbr_create; + u.create->wbr_create + = au_wbr_create_val(a->args[0].from, u.create); + if (u.create->wbr_create >= 0) { + err = 0; + opt->type = token; + } else + AuErr("wrong value, %s\n", opt_str); + break; + case Opt_wbr_copyup: + opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from); + if (opt->wbr_copyup >= 0) { + err = 0; + opt->type = token; + } else + AuErr("wrong value, %s\n", opt_str); + break; + + case Opt_coo: + opt->coo = coo_val(a->args[0].from); + if (opt->coo >= 0) { + if (opt->coo == AuOpt_COO_NONE + || !(opts->sb_flags & MS_RDONLY)) { + err = 0; + opt->type = token; + } else + AuErr("bad %s for readonly mount\n", + opt_str); + } else + AuErr("wrong value, %s\n", opt_str); + break; + + case Opt_ignore: +#ifndef CONFIG_AUFS_COMPAT + AuWarn("ignored %s\n", opt_str); +#endif + case Opt_ignore_silent: + skipped = 1; + err = 0; + break; + case Opt_err: + AuErr("unknown option %s\n", opt_str); + break; + } + + if (!err && !skipped) { + if (unlikely(++opt > opt_tail)) { + err = -E2BIG; + opt--; + opt->type = Opt_tail; + break; + } + opt->type = Opt_tail; + } + } + + kfree(a); + dump_opts(opts); + if (unlikely(err)) + au_opts_free(opts); + + out: + AuTraceErr(err); + return err; +} + +/* + * returns, + * plus: processed without an error + * zero: unprocessed + */ +static int au_opt_simple(struct super_block *sb, struct au_opt *opt, + struct au_opts *opts) +{ + int err; + struct au_sbinfo *sbinfo; + struct au_opt_wbr_create *create; + + AuTraceEnter(); + + err = 1; /* handled */ + sbinfo = au_sbi(sb); + switch (opt->type) { + case Opt_udba: + sbinfo->si_mntflags &= ~AuOptMask_UDBA; + sbinfo->si_mntflags |= opt->udba; + opts->given_udba |= opt->udba; + break; + + case Opt_plink: + au_opt_set(sbinfo->si_mntflags, PLINK); + break; + case Opt_noplink: + if (au_opt_test(sbinfo->si_mntflags, PLINK)) + au_plink_put(sb); + au_opt_clr(sbinfo->si_mntflags, PLINK); + break; + case Opt_list_plink: + if (au_opt_test(sbinfo->si_mntflags, PLINK)) + au_plink_list(sb); + break; + case Opt_clean_plink: + if (au_opt_test(sbinfo->si_mntflags, PLINK)) + au_plink_put(sb); + break; + + case Opt_diropq_a: + au_opt_set(sbinfo->si_mntflags, ALWAYS_DIROPQ); + break; + case Opt_diropq_w: + au_opt_clr(sbinfo->si_mntflags, ALWAYS_DIROPQ); + break; + + case Opt_dlgt: + au_opt_set(sbinfo->si_mntflags, DLGT); + break; + case Opt_nodlgt: + au_opt_clr(sbinfo->si_mntflags, DLGT); + break; + + case Opt_warn_perm: + au_opt_set(sbinfo->si_mntflags, WARN_PERM); + break; + case Opt_nowarn_perm: + au_opt_clr(sbinfo->si_mntflags, WARN_PERM); + break; + + case Opt_refrof: + au_opt_set(sbinfo->si_mntflags, REFROF); + break; + case Opt_norefrof: + /* au_opt_set(sbinfo->si_mntflags, COO_LEAF); */ + au_opt_clr(sbinfo->si_mntflags, REFROF); + break; + + case Opt_verbose: + au_opt_set(sbinfo->si_mntflags, VERBOSE); + break; + case Opt_noverbose: + au_opt_clr(sbinfo->si_mntflags, VERBOSE); + break; + + case Opt_sum: + au_opt_set(sbinfo->si_mntflags, SUM); + break; + case Opt_wsum: + au_opt_clr(sbinfo->si_mntflags, SUM); + au_opt_set(sbinfo->si_mntflags, SUM_W); + case Opt_nosum: + au_opt_clr(sbinfo->si_mntflags, SUM); + au_opt_clr(sbinfo->si_mntflags, SUM_W); + break; + + case Opt_wbr_create: + create = &opt->wbr_create; + if (sbinfo->si_wbr_create_ops->fin) { + err = sbinfo->si_wbr_create_ops->fin(sb); + if (!err) + err = 1; + } + sbinfo->si_wbr_create = create->wbr_create; + sbinfo->si_wbr_create_ops + = au_wbr_create_ops + create->wbr_create; + switch (create->wbr_create) { + case AuWbrCreate_MFSRRV: + case AuWbrCreate_MFSRR: + sbinfo->si_wbr_mfs.mfsrr_watermark + = create->mfsrr_watermark; + /*FALLTHROUGH*/ + case AuWbrCreate_MFS: + case AuWbrCreate_MFSV: + case AuWbrCreate_PMFS: + case AuWbrCreate_PMFSV: + sbinfo->si_wbr_mfs.mfs_expire = create->mfs_second * HZ; + break; + } + if (sbinfo->si_wbr_create_ops->init) + sbinfo->si_wbr_create_ops->init(sb); /* ignore */ + break; + case Opt_wbr_copyup: + sbinfo->si_wbr_copyup = opt->wbr_copyup; + sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + opt->wbr_copyup; + break; + + case Opt_coo: + sbinfo->si_mntflags &= ~AuOptMask_COO; + sbinfo->si_mntflags |= opt->coo; + break; + + case Opt_dirwh: + sbinfo->si_dirwh = opt->dirwh; + break; + + case Opt_rdcache: + sbinfo->si_rdcache = opt->rdcache * HZ; + break; + + case Opt_trunc_xino: + au_opt_set(sbinfo->si_mntflags, TRUNC_XINO); + break; + case Opt_notrunc_xino: + au_opt_clr(sbinfo->si_mntflags, TRUNC_XINO); + break; + + case Opt_dirperm1: + au_opt_set(sbinfo->si_mntflags, DIRPERM1); + break; + case Opt_nodirperm1: + au_opt_clr(sbinfo->si_mntflags, DIRPERM1); + break; + + case Opt_shwh: + au_opt_set(sbinfo->si_mntflags, SHWH); + break; + case Opt_noshwh: + au_opt_clr(sbinfo->si_mntflags, SHWH); + break; + + case Opt_trunc_xino_path: + case Opt_itrunc_xino: + err = au_xino_trunc(sb, opt->xino_itrunc.bindex); + if (!err) + err = 1; + break; + + case Opt_trunc_xib: + au_fset_opts(opts->flags, TRUNC_XIB); + break; + case Opt_notrunc_xib: + au_fclr_opts(opts->flags, TRUNC_XIB); + break; + + default: + err = 0; + break; + } + + AuTraceErr(err); + return err; +} + +/* + * returns tri-state. + * plus: processed without an error + * zero: unprocessed + * minus: error + */ +static int au_opt_br(struct super_block *sb, struct au_opt *opt, + struct au_opts *opts) +{ + int err, do_refresh; + + AuTraceEnter(); + + err = 0; + switch (opt->type) { + case Opt_append: + opt->add.bindex = au_sbend(sb) + 1; + if (opt->add.bindex < 0) + opt->add.bindex = 0; + goto add; + case Opt_prepend: + opt->add.bindex = 0; + add: + case Opt_add: + err = au_br_add(sb, &opt->add, + au_ftest_opts(opts->flags, REMOUNT)); + if (!err) { + err = 1; + au_fset_opts(opts->flags, REFRESH_DIR); + if (au_br_whable(opt->add.perm)) + au_fset_opts(opts->flags, REFRESH_NONDIR); + } + break; + + case Opt_del: + case Opt_idel: + err = au_br_del(sb, &opt->del, + au_ftest_opts(opts->flags, REMOUNT)); + if (!err) { + err = 1; + au_fset_opts(opts->flags, TRUNC_XIB); + au_fset_opts(opts->flags, REFRESH_DIR); + au_fset_opts(opts->flags, REFRESH_NONDIR); + } + break; + + case Opt_mod: + case Opt_imod: + err = au_br_mod(sb, &opt->mod, + au_ftest_opts(opts->flags, REMOUNT), + &do_refresh); + if (!err) { + err = 1; + if (do_refresh) { + au_fset_opts(opts->flags, REFRESH_DIR); + au_fset_opts(opts->flags, REFRESH_NONDIR); + } + } + break; + } + + AuTraceErr(err); + return err; +} + +static int au_opt_xino(struct super_block *sb, struct au_opt *opt, + struct au_opt_xino **opt_xino, + struct au_opt_xinodir **opt_xinodir, + struct au_opts *opts) +{ + int err; + const int remount = !!au_ftest_opts(opts->flags, REMOUNT); + + AuTraceEnter(); + + err = 0; + switch (opt->type) { + case Opt_xino: + err = au_xino_set(sb, &opt->xino, remount); + if (!err) + *opt_xino = &opt->xino; + break; +#if 0 /* def CONFIG_AUFS_EXPORT */ /* reserved for futur use */ + case Opt_xinodir: + err = au_xinodir_set(sb, &opt->xinodir, remount); + if (!err) + *opt_xinodir = &opt->xinodir; + break; +#endif + case Opt_noxino: + au_xino_clr(sb); + *opt_xino = (void *)-1; + break; + } + + AuTraceErr(err); + return err; +} + +int verify_opts(struct super_block *sb, unsigned long sb_flags, + unsigned int pending, int remount) +{ + int err; + aufs_bindex_t bindex, bend; + unsigned char do_plink, skip, do_free; + struct au_branch *br; + struct au_wbr *wbr; + struct dentry *root; + struct inode *dir, *h_dir; + unsigned int mnt_flags; + + AuTraceEnter(); + mnt_flags = au_mntflags(sb); + AuDebugOn(!(mnt_flags & AuOptMask_COO)); + AuDebugOn(!(mnt_flags & AuOptMask_UDBA)); + + if (!(sb_flags & MS_RDONLY)) { + if (unlikely(!au_br_writable(au_sbr_perm(sb, 0)))) + AuWarn("first branch should be rw\n"); + if (unlikely(au_opt_test(mnt_flags, SHWH))) + AuWarn("shwh should be used with ro\n"); + } else if (unlikely(!au_opt_test(mnt_flags, COO_NONE))) { + AuWarn("resetting coo for readonly mount\n"); + au_opt_set_coo(au_sbi(sb)->si_mntflags, COO_NONE); + } + + if (unlikely(au_opt_test((mnt_flags | pending), UDBA_INOTIFY) + && !au_opt_test_xino(mnt_flags))) + AuWarn("udba=inotify requires xino\n"); + + err = 0; + root = sb->s_root; + dir = sb->s_root->d_inode; + do_plink = !!au_opt_test(mnt_flags, PLINK); + bend = au_sbend(sb); + for (bindex = 0; !err && bindex <= bend; bindex++) { + skip = 0; + h_dir = au_h_iptr(dir, bindex); + br = au_sbr(sb, bindex); + do_free = 0; + wbr = br->br_wbr; + if (wbr) + wbr_wh_read_lock(wbr); + switch (br->br_perm) { + case AuBrPerm_RR: + case AuBrPerm_RO: + case AuBrPerm_RRWH: + case AuBrPerm_ROWH: + do_free = !!wbr; + skip = (!wbr + || (!wbr->wbr_whbase + && !wbr->wbr_plink + && !wbr->wbr_tmp)); + break; + + case AuBrPerm_RWNoLinkWH: + /* skip = (!br->br_whbase && !br->br_tmp); */ + skip = (!wbr || !wbr->wbr_whbase); + if (skip && wbr) { + if (do_plink) + skip = !!wbr->wbr_plink; + else + skip = !wbr->wbr_plink; + } + break; + + case AuBrPerm_RW: + /* skip = (br->br_whbase && br->br_tmp); */ + skip = (wbr && wbr->wbr_whbase); + if (skip) { + if (do_plink) + skip = !!wbr->wbr_plink; + else + skip = !wbr->wbr_plink; + } + break; + + default: + BUG(); + } + if (wbr) + wbr_wh_read_unlock(wbr); + + if (skip) + continue; + + vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT); + if (wbr) + wbr_wh_write_lock(wbr); + err = au_wh_init(au_h_dptr(root, bindex), br, br->br_mnt, sb, + bindex); + if (wbr) + wbr_wh_write_unlock(wbr); + vfsub_i_unlock(h_dir); + + if (!err && do_free) { + kfree(wbr); + br->br_wbr = NULL; + } + } + + AuTraceErr(err); + return err; +} + +int au_opts_mount(struct super_block *sb, struct au_opts *opts) +{ + int err; + struct inode *dir; + struct au_opt *opt; + struct au_opt_xino *opt_xino, xino; + struct au_opt_xinodir *opt_xinodir; + aufs_bindex_t bend; + struct au_sbinfo *sbinfo; + unsigned int tmp; + struct au_branch *br; + + AuTraceEnter(); + SiMustWriteLock(sb); + DiMustWriteLock(sb->s_root); + dir = sb->s_root->d_inode; + IiMustWriteLock(dir); + + err = 0; + opt_xino = NULL; + opt_xinodir = NULL; + opt = opts->opt; + while (err >= 0 && opt->type != Opt_tail) + err = au_opt_simple(sb, opt++, opts); + if (err > 0) + err = 0; + else if (unlikely(err < 0)) + goto out; + + /* disable xino, xinodir, hinotify, dlgt temporary */ + sbinfo = au_sbi(sb); + tmp = sbinfo->si_mntflags; + au_opt_clr(sbinfo->si_mntflags, XINO); + au_opt_clr(sbinfo->si_mntflags, XINODIR); + au_opt_clr(sbinfo->si_mntflags, DLGT); + au_opt_set_udba(sbinfo->si_mntflags, UDBA_REVAL); + + opt = opts->opt; + while (err >= 0 && opt->type != Opt_tail) + err = au_opt_br(sb, opt++, opts); + if (err > 0) + err = 0; + else if (unlikely(err < 0)) + goto out; + + bend = au_sbend(sb); + if (unlikely(bend < 0)) { + err = -EINVAL; + AuErr("no branches\n"); + goto out; + } + + if (au_opt_test(tmp, XINO)) + au_opt_set(sbinfo->si_mntflags, XINO); + else if (au_opt_test(tmp, XINODIR)) + au_opt_set(sbinfo->si_mntflags, XINODIR); + opt = opts->opt; + while (!err && opt->type != Opt_tail) + err = au_opt_xino(sb, opt++, &opt_xino, &opt_xinodir, opts); + if (unlikely(err)) + goto out; + + /* todo: test this error case? */ + err = verify_opts(sb, sb->s_flags, tmp, /*remount*/0); + if (unlikely(err)) + goto out; + + /* enable xino */ + if (au_opt_test(tmp, XINO) && !opt_xino) { + xino.file = au_xino_def(sb); + err = PTR_ERR(xino.file); + if (IS_ERR(xino.file)) + goto out; + + br = au_xino_def_br(sbinfo); + err = au_xino_set(sb, &xino, /*remount*/0); + fput(xino.file); + if (unlikely(err)) + goto out; + au_xino_def_br_set(br, sbinfo); + } + + /* restore hinotify */ + sbinfo->si_mntflags &= ~AuOptMask_UDBA; + sbinfo->si_mntflags |= (tmp & AuOptMask_UDBA); + if (au_opt_test(tmp, UDBA_INOTIFY)) + au_reset_hinotify(dir, au_hi_flags(dir, 1) & ~AuHi_XINO); + + /* restore dlgt */ + if (au_opt_test(tmp, DLGT)) + au_opt_set(sbinfo->si_mntflags, DLGT); + + out: + AuTraceErr(err); + return err; +} + +int au_opts_remount(struct super_block *sb, struct au_opts *opts) +{ + int err, rerr; + struct inode *dir; + struct au_opt_xino *opt_xino; + struct au_opt_xinodir *opt_xinodir; + struct au_opt *opt; + unsigned char dlgt; + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + SiMustWriteLock(sb); + DiMustWriteLock(sb->s_root); + dir = sb->s_root->d_inode; + IiMustWriteLock(dir); + sbinfo = au_sbi(sb); + + err = 0; + dlgt = !!au_opt_test(sbinfo->si_mntflags, DLGT); + opt_xino = NULL; + opt_xinodir = NULL; + opt = opts->opt; + while (err >= 0 && opt->type != Opt_tail) { + err = au_opt_simple(sb, opt, opts); + + /* disable it temporary */ + dlgt = !!au_opt_test(sbinfo->si_mntflags, DLGT); + au_opt_clr(sbinfo->si_mntflags, DLGT); + + if (!err) + err = au_opt_br(sb, opt, opts); + if (!err) + err = au_opt_xino(sb, opt, &opt_xino, &opt_xinodir, + opts); + + /* restore it */ + if (dlgt) + au_opt_set(sbinfo->si_mntflags, DLGT); + opt++; + } + if (err > 0) + err = 0; + AuTraceErr(err); + + /* go on even err */ + + /* todo: test this error case? */ + au_opt_clr(sbinfo->si_mntflags, DLGT); + rerr = verify_opts(sb, opts->sb_flags, sbinfo->si_mntflags, + /*remount*/1); + if (unlikely(dlgt)) + au_opt_set(sbinfo->si_mntflags, DLGT); + if (unlikely(rerr && !err)) + err = rerr; + + if (au_ftest_opts(opts->flags, TRUNC_XIB)) { + rerr = au_xib_trunc(sb); + if (unlikely(rerr && !err)) + err = rerr; + } + + /* they are handled by the caller */ + if (!au_ftest_opts(opts->flags, REFRESH_DIR) + && (opts->given_udba || au_opt_test_xino(sbinfo->si_mntflags))) + au_fset_opts(opts->flags, REFRESH_DIR); + + LKTRTrace("status 0x%x\n", opts->flags); + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/opts.h b/fs/aufs/opts.h new file mode 100644 index 0000000..5a6d6ac --- /dev/null +++ b/fs/aufs/opts.h @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * mount options/flags + * + * $Id: opts.h,v 1.37 2009/01/26 06:24:05 sfjro Exp $ + */ + +#ifndef __AUFS_OPTS_H__ +#define __AUFS_OPTS_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) +typedef const char *au_parser_pattern_t; +#else +typedef char *au_parser_pattern_t; +#endif + +/* ---------------------------------------------------------------------- */ +/* mount flags */ + +/* external inode number bitmap and translation table */ +#define AuOpt_XINO 1 +#define AuOpt_XINODIR (1 << 1) +#define AuOpt_TRUNC_XINO (1 << 2) +#define AuOpt_UDBA_NONE (1 << 3) /* users direct branch access */ +#define AuOpt_UDBA_REVAL (1 << 4) +#define AuOpt_UDBA_INOTIFY (1 << 5) +#define AuOpt_SHWH (1 << 6) +#define AuOpt_PLINK (1 << 7) +#define AuOpt_WARN_PERM (1 << 8) +#define AuOpt_DIRPERM1 (1 << 9) +#define AuOpt_DLGT (1 << 10) +#define AuOpt_COO_NONE (1 << 11) /* copyup on open */ +#define AuOpt_COO_LEAF (1 << 12) +#define AuOpt_COO_ALL (1 << 13) +#define AuOpt_ALWAYS_DIROPQ (1 << 14) +#define AuOpt_REFROF (1 << 15) +#define AuOpt_VERBOSE (1 << 16) +#define AuOpt_SUM (1 << 17) +#define AuOpt_SUM_W (1 << 18) /* unimplemented */ + +#if 1 /* ndef CONFIG_AUFS_EXPORT */ /* reserved for future use */ +#undef AuOpt_XINODIR +#define AuOpt_XINODIR 0 +#endif +#ifndef CONFIG_AUFS_HINOTIFY +#undef AuOpt_UDBA_INOTIFY +#define AuOpt_UDBA_INOTIFY 0 +#endif +#ifndef CONFIG_AUFS_SHWH +#undef AuOpt_SHWH +#define AuOpt_SHWH 0 +#endif +#ifndef CONFIG_AUFS_DLGT +#undef AuOpt_DIRPERM1 +#define AuOpt_DIRPERM1 0 +#undef AuOpt_DLGT +#define AuOpt_DLGT 0 +#endif + +/* policies to select one among multiple writable branches */ +enum { + AuWbrCreate_TDP, /* top down parent */ + AuWbrCreate_RR, /* round robin */ + AuWbrCreate_MFS, /* most free space */ + AuWbrCreate_MFSV, /* mfs with seconds */ + AuWbrCreate_MFSRR, /* mfs then rr */ + AuWbrCreate_MFSRRV, /* mfs then rr with seconds */ + AuWbrCreate_PMFS, /* parent and mfs */ + AuWbrCreate_PMFSV, /* parent and mfs with seconds */ + + AuWbrCreate_Def = AuWbrCreate_TDP +}; + +enum { + AuWbrCopyup_TDP, /* top down parent */ + AuWbrCopyup_BUP, /* bottom up parent */ + AuWbrCopyup_BU, /* bottom up */ + + AuWbrCopyup_Def = AuWbrCopyup_TDP +}; + +#define AuOptMask_COO (AuOpt_COO_NONE \ + | AuOpt_COO_LEAF \ + | AuOpt_COO_ALL) +#define AuOptMask_UDBA (AuOpt_UDBA_NONE \ + | AuOpt_UDBA_REVAL \ + | AuOpt_UDBA_INOTIFY) + +#ifdef CONFIG_AUFS_COMPAT +#define AuOpt_DefExtra1 AuOpt_ALWAYS_DIROPQ +#else +#define AuOpt_DefExtra1 0 +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16) +#define AuOpt_DefExtra2 AuOpt_PLINK +#else +#define AuOpt_DefExtra2 0 +#endif + +#define AuOpt_Def (AuOpt_XINO \ + | AuOpt_UDBA_REVAL \ + | AuOpt_WARN_PERM \ + | AuOpt_COO_NONE \ + | AuOpt_DefExtra1 \ + | AuOpt_DefExtra2) + +/* ---------------------------------------------------------------------- */ + +struct au_opt_add { + aufs_bindex_t bindex; + char *path; + int perm; + struct nameidata nd; +}; + +struct au_opt_del { + char *path; + struct dentry *h_root; +}; + +struct au_opt_mod { + char *path; + int perm; + struct dentry *h_root; +}; + +struct au_opt_xino { + char *path; + struct file *file; +}; + +struct au_opt_xinodir { +#if 0 /* reserved for future use */ + char *name; + struct path path; +#endif +}; + +struct au_opt_xino_itrunc { + aufs_bindex_t bindex; +}; + +struct au_opt_xino_trunc_v { + unsigned long long upper; + int step; +}; + +struct au_opt_wbr_create { + int wbr_create; + int mfs_second; + unsigned long long mfsrr_watermark; +}; + +struct au_opt { + int type; + union { + struct au_opt_xino xino; + struct au_opt_xinodir xinodir; + struct au_opt_xino_itrunc xino_itrunc; + struct au_opt_add add; + struct au_opt_del del; + struct au_opt_mod mod; + int dirwh; + int rdcache; + int deblk; + int nhash; + int udba; + int coo; + struct au_opt_wbr_create wbr_create; + int wbr_copyup; + }; +}; + +/* opts flags */ +#define AuOpts_REMOUNT 1 +#define AuOpts_REFRESH_DIR (1 << 1) +#define AuOpts_REFRESH_NONDIR (1 << 2) +#define AuOpts_TRUNC_XIB (1 << 3) +#define au_ftest_opts(flags, name) ((flags) & AuOpts_##name) +#define au_fset_opts(flags, name) { (flags) |= AuOpts_##name; } +#define au_fclr_opts(flags, name) { (flags) &= ~AuOpts_##name; } + +struct au_opts { + struct au_opt *opt; + int max_opt; + + unsigned int given_udba; + unsigned int flags; + unsigned long sb_flags; +}; + +/* ---------------------------------------------------------------------- */ + +au_parser_pattern_t au_optstr_br_perm(int brperm); +au_parser_pattern_t au_optstr_udba(int udba); +au_parser_pattern_t au_optstr_coo(int coo); +au_parser_pattern_t au_optstr_wbr_copyup(int wbr_copyup); +au_parser_pattern_t au_optstr_wbr_create(int wbr_create); + +void au_opts_free(struct au_opts *opts); +int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts); +int verify_opts(struct super_block *sb, unsigned long sb_flags, + unsigned int pending, int remount); +int au_opts_mount(struct super_block *sb, struct au_opts *opts); +int au_opts_remount(struct super_block *sb, struct au_opts *opts); + +/* ---------------------------------------------------------------------- */ + +#define au_opt_test(flags, name) (flags & AuOpt_##name) + +static inline int au_opt_test_xino(unsigned int flags) +{ + return flags & (AuOpt_XINO | AuOpt_XINODIR); +} + +#define au_opt_set(flags, name) do { \ + BUILD_BUG_ON(AuOpt_##name & (AuOptMask_COO | AuOptMask_UDBA)); \ + ((flags) |= AuOpt_##name); \ +} while (0) + +#define au_opt_set_coo(flags, name) do { \ + (flags) &= ~AuOptMask_COO; \ + ((flags) |= AuOpt_##name); \ +} while (0) + +#define au_opt_set_udba(flags, name) do { \ + (flags) &= ~AuOptMask_UDBA; \ + ((flags) |= AuOpt_##name); \ +} while (0) + +#define au_opt_clr(flags, name) { ((flags) &= ~AuOpt_##name); } + +static inline int au_test_dlgt(unsigned int flags) +{ + return au_opt_test(flags, DLGT) && !au_test_wkq(current); +} + +static inline int au_test_dirperm1(unsigned int flags) +{ + return au_opt_test(flags, DIRPERM1) && !au_test_wkq(current); +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_OPTS_H__ */ diff --git a/fs/aufs/plink.c b/fs/aufs/plink.c new file mode 100644 index 0000000..ef19ad0 --- /dev/null +++ b/fs/aufs/plink.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * pseudo-link + * + * $Id: plink.c,v 1.28 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +struct pseudo_link { + struct list_head list; + struct inode *inode; +}; + +#ifdef CONFIG_AUFS_DEBUG +void au_plink_list(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + struct list_head *plink_list; + struct pseudo_link *plink; + + AuTraceEnter(); + SiMustAnyLock(sb); + sbinfo = au_sbi(sb); + AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); + + plink_list = &sbinfo->si_plink.head; + spin_lock(&sbinfo->si_plink.spin); + list_for_each_entry(plink, plink_list, list) + AuDbg("%lu\n", plink->inode->i_ino); + spin_unlock(&sbinfo->si_plink.spin); +} +#endif + +int au_plink_test(struct super_block *sb, struct inode *inode) +{ + int found; + struct au_sbinfo *sbinfo; + struct list_head *plink_list; + struct pseudo_link *plink; + + LKTRTrace("i%lu\n", inode->i_ino); + SiMustAnyLock(sb); + sbinfo = au_sbi(sb); + AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); + + found = 0; + plink_list = &sbinfo->si_plink.head; + spin_lock(&sbinfo->si_plink.spin); + list_for_each_entry(plink, plink_list, list) + if (plink->inode == inode) { + found = 1; + break; + } + spin_unlock(&sbinfo->si_plink.spin); + return found; +} + +/* 20 is max digits length of ulong 64 */ +#define PLINK_NAME_LEN ((20 + 1) * 2) + +static int plink_name(char *name, int len, struct inode *inode, + aufs_bindex_t bindex) +{ + int rlen; + struct inode *h_inode; + + LKTRTrace("i%lu, b%d\n", inode->i_ino, bindex); + AuDebugOn(len != PLINK_NAME_LEN); + h_inode = au_h_iptr(inode, bindex); + AuDebugOn(!h_inode); + rlen = snprintf(name, len, "%lu.%lu", inode->i_ino, h_inode->i_ino); + AuDebugOn(rlen >= len); + return rlen; +} + +struct dentry *au_plink_lkup(struct super_block *sb, aufs_bindex_t bindex, + struct inode *inode) +{ + struct dentry *h_dentry, *h_parent; + struct au_branch *br; + struct au_wbr *wbr; + struct inode *h_dir; + char tgtname[PLINK_NAME_LEN]; + int len; + struct au_ndx ndx = { + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + + LKTRTrace("b%d, i%lu\n", bindex, inode->i_ino); + br = au_sbr(sb, bindex); + wbr = br->br_wbr; + AuDebugOn(!wbr); + h_parent = wbr->wbr_plink; + AuDebugOn(!h_parent); + h_dir = h_parent->d_inode; + AuDebugOn(!h_dir); + + len = plink_name(tgtname, sizeof(tgtname), inode, bindex); + + /* always superio. */ + ndx.nfsmnt = au_do_nfsmnt(br->br_mnt); + vfsub_i_lock_nested(h_dir, AuLsc_I_CHILD2); + h_dentry = au_sio_lkup_one(tgtname, h_parent, len, &ndx); + vfsub_i_unlock(h_dir); + return h_dentry; +} + +static int do_whplink(char *tgt, int len, struct dentry *h_parent, + struct dentry *h_dentry, struct vfsmount *nfsmnt, + struct super_block *sb) +{ + int err, dlgt; + struct dentry *h_tgt; + struct inode *h_dir; + struct vfsub_args vargs; + struct au_ndx ndx = { + .nfsmnt = nfsmnt, + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + + AuTraceEnter(); + + dlgt = !!au_test_dlgt(au_mntflags(sb)); + if (dlgt) + au_fset_ndx(ndx.flags, DLGT); + h_tgt = au_lkup_one(tgt, h_parent, len, &ndx); + err = PTR_ERR(h_tgt); + if (IS_ERR(h_tgt)) + goto out; + + err = 0; + vfsub_args_init(&vargs, NULL, dlgt, 0); + /* wh.plink dir is not monitored */ + h_dir = h_parent->d_inode; + if (h_tgt->d_inode && h_tgt->d_inode != h_dentry->d_inode) + err = vfsub_unlink(h_dir, h_tgt, &vargs); + if (!err && !h_tgt->d_inode) { + err = vfsub_link(h_dentry, h_dir, h_tgt, &vargs); + /* todo: unnecessary? */ + /* inc_nlink(inode); */ + } + dput(h_tgt); + + out: + AuTraceErr(err); + return err; +} + +struct do_whplink_args { + int *errp; + char *tgt; + int len; + struct dentry *h_parent; + struct dentry *h_dentry; + struct vfsmount *nfsmnt; + struct super_block *sb; +}; + +static void call_do_whplink(void *args) +{ + struct do_whplink_args *a = args; + *a->errp = do_whplink(a->tgt, a->len, a->h_parent, a->h_dentry, + a->nfsmnt, a->sb); +} + +static int whplink(struct dentry *h_dentry, struct inode *inode, + aufs_bindex_t bindex, struct super_block *sb) +{ + int err, len, wkq_err; + struct au_branch *br; + struct au_wbr *wbr; + struct dentry *h_parent; + struct inode *h_dir; + char tgtname[PLINK_NAME_LEN]; + + LKTRTrace("%.*s\n", AuDLNPair(h_dentry)); + br = au_sbr(inode->i_sb, bindex); + wbr = br->br_wbr; + AuDebugOn(!wbr); + h_parent = wbr->wbr_plink; + AuDebugOn(!h_parent); + h_dir = h_parent->d_inode; + AuDebugOn(!h_dir); + + len = plink_name(tgtname, sizeof(tgtname), inode, bindex); + + /* always superio. */ + vfsub_i_lock_nested(h_dir, AuLsc_I_CHILD2); + if (!au_test_wkq(current)) { + struct do_whplink_args args = { + .errp = &err, + .tgt = tgtname, + .len = len, + .h_parent = h_parent, + .h_dentry = h_dentry, + .nfsmnt = au_do_nfsmnt(br->br_mnt), + .sb = sb + }; + wkq_err = au_wkq_wait(call_do_whplink, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } else + err = do_whplink(tgtname, len, h_parent, h_dentry, + au_do_nfsmnt(br->br_mnt), sb); + vfsub_i_unlock(h_dir); + + AuTraceErr(err); + return err; +} + +static void do_put_plink(struct pseudo_link *plink, int do_del) +{ + AuTraceEnter(); + + iput(plink->inode); + if (do_del) + list_del(&plink->list); + kfree(plink); +} + +void au_plink_append(struct super_block *sb, struct inode *inode, + struct dentry *h_dentry, aufs_bindex_t bindex) +{ + struct au_sbinfo *sbinfo; + struct list_head *plink_list; + struct pseudo_link *plink; + int found, err, cnt; + + LKTRTrace("i%lu\n", inode->i_ino); + SiMustAnyLock(sb); + sbinfo = au_sbi(sb); + AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); + + cnt = 0; + found = 0; + plink_list = &sbinfo->si_plink.head; + spin_lock(&sbinfo->si_plink.spin); + list_for_each_entry(plink, plink_list, list) { + cnt++; + if (plink->inode == inode) { + found = 1; + break; + } + } + + err = 0; + plink = NULL; + if (!found) { + plink = kmalloc(sizeof(*plink), GFP_ATOMIC); + if (plink) { + plink->inode = au_igrab(inode); + list_add(&plink->list, plink_list); + cnt++; + } else + err = -ENOMEM; + } + spin_unlock(&sbinfo->si_plink.spin); + +#if 0 /* todo: test here */ + if (found) + return; /* success */ +#endif + + if (!err) + err = whplink(h_dentry, inode, bindex, sb); + + if (unlikely(cnt > AUFS_PLINK_WARN)) + AuWarn1("unexpectedly many pseudo links, %d\n", cnt); + if (unlikely(err)) { + AuWarn("err %d, damaged pseudo link. ignored.\n", err); + if (!found && plink) + do_put_plink(plink, /*do_del*/1); + } +} + +void au_plink_put(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + struct list_head *plink_list; + struct pseudo_link *plink, *tmp; + + AuTraceEnter(); + SiMustWriteLock(sb); + sbinfo = au_sbi(sb); + AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); + + plink_list = &sbinfo->si_plink.head; + /* spin_lock(&sbinfo->si_plink.spin); */ + list_for_each_entry_safe(plink, tmp, plink_list, list) + do_put_plink(plink, 0); + INIT_LIST_HEAD(plink_list); + /* spin_unlock(&sbinfo->si_plink.head); */ +} + +void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id) +{ + struct au_sbinfo *sbinfo; + struct list_head *plink_list; + struct pseudo_link *plink, *tmp; + struct inode *inode; + aufs_bindex_t bstart, bend, bindex; + int do_put; + + AuTraceEnter(); + SiMustWriteLock(sb); + sbinfo = au_sbi(sb); + AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK)); + + plink_list = &sbinfo->si_plink.head; + /* spin_lock(&sbinfo->si_plink.spin); */ + list_for_each_entry_safe(plink, tmp, plink_list, list) { + do_put = 0; + inode = au_igrab(plink->inode); + ii_write_lock_child(inode); + bstart = au_ibstart(inode); + bend = au_ibend(inode); + if (bstart >= 0) { + for (bindex = bstart; bindex <= bend; bindex++) { + if (!au_h_iptr(inode, bindex) + || au_ii_br_id(inode, bindex) != br_id) + continue; + au_set_h_iptr(inode, bindex, NULL, 0); + do_put = 1; + break; + } + } else + do_put_plink(plink, 1); + + if (do_put) { + for (bindex = bstart; bindex <= bend; bindex++) + if (au_h_iptr(inode, bindex)) { + do_put = 0; + break; + } + if (do_put) + do_put_plink(plink, 1); + } + ii_write_unlock(inode); + iput(inode); + } + /* spin_unlock(&sbinfo->si_plink.spin); */ +} diff --git a/fs/aufs/robr.c b/fs/aufs/robr.c new file mode 100644 index 0000000..4e05d03 --- /dev/null +++ b/fs/aufs/robr.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * 'robr', aufs as readonly branch of another aufs + * + * $Id: robr.c,v 1.9 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +int au_test_robr_wh(struct qstr *name, struct dentry *h_parent, + struct qstr *wh_name, int try_sio, struct au_ndx *ndx) +{ + if (strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) + return au_wh_test(h_parent, wh_name, try_sio, ndx); + return -EPERM; +} + +int au_test_robr_shwh(struct super_block *sb, const struct qstr *name) +{ + return 0; +} + +/* ---------------------------------------------------------------------- */ + +struct au_robr_lvma { + struct list_head list; + struct vm_area_struct *vma; +}; + +struct file *au_robr_safe_file(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct super_block *sb = file->f_dentry->d_sb; + struct au_robr_lvma *lvma, *entry; + struct au_sbinfo *sbinfo; + struct list_head *head; + unsigned char found, warn; + + AuTraceEnter(); + + if (!file->private_data || !au_test_aufs(sb)) + return NULL; + + warn = 0; + found = 0; + sbinfo = au_sbi(sb); + head = &sbinfo->si_lvma.head; + spin_lock(&sbinfo->si_lvma.spin); + list_for_each_entry(entry, head, list) { + found = (entry->vma == vma); + if (unlikely(found)) + break; + } + if (!found) { + lvma = kmalloc(sizeof(*lvma), GFP_ATOMIC); + if (lvma) { + lvma->vma = vma; + list_add(&lvma->list, head); + } else { + warn = 1; + file = NULL; + } + } else + file = NULL; + spin_unlock(&sbinfo->si_lvma.spin); + + if (unlikely(warn)) + AuWarn1("no memory for lvma\n"); + return file; +} + +void au_robr_reset_file(struct vm_area_struct *vma, struct file *file) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct au_robr_lvma *entry, *found; + struct au_sbinfo *sbinfo; + struct list_head *head; + + AuTraceEnter(); + AuDebugOn(!au_test_aufs(sb)); + + vma->vm_file = file; + /* smp_mb(); */ /* flush vm_file */ + + found = NULL; + sbinfo = au_sbi(sb); + head = &sbinfo->si_lvma.head; + spin_lock(&sbinfo->si_lvma.spin); + list_for_each_entry(entry, head, list) + if (entry->vma == vma) { + found = entry; + break; + } + AuDebugOn(!found); + list_del(&found->list); + spin_unlock(&sbinfo->si_lvma.spin); + kfree(found); +} diff --git a/fs/aufs/sbinfo.c b/fs/aufs/sbinfo.c new file mode 100644 index 0000000..26c67bc --- /dev/null +++ b/fs/aufs/sbinfo.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * superblock private data + * + * $Id: sbinfo.c,v 1.54 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +void au_si_free(struct super_block *sb, int err) +{ + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + sbinfo = au_sbi(sb); + AuDebugOn(!sbinfo); + AuDebugOn(!list_empty(&sbinfo->si_plink.head)); + + if (!err) { + au_sbilist_lock(); + au_sbilist_del(sbinfo); + si_write_lock(sb); + sysaufs_sbi_del(sb); + au_sbilist_unlock(); + } else + si_write_lock(sb); + + au_xino_clr(sb); + au_br_free(sbinfo); + kfree(sbinfo->si_branch); + au_export_put(sbinfo); + mutex_destroy(&sbinfo->si_xib_mtx); + si_write_unlock(sb); + au_rwsem_destroy(&sbinfo->si_rwsem); + sysaufs_sbi_put(sb); + kfree(sbinfo); +} + +int au_si_alloc(struct super_block *sb) +{ + int err; + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + + err = -ENOMEM; + sbinfo = kmalloc(sizeof(*sbinfo), GFP_NOFS); + //if (LktrCond) {kfree(sbinfo); sbinfo = NULL;} + if (unlikely(!sbinfo)) + goto out; + sbinfo->si_branch = kzalloc(sizeof(*sbinfo->si_branch), GFP_NOFS); + //if (LktrCond) {kfree(sbinfo->si_branch); sbinfo->si_branch = NULL;} + if (unlikely(!sbinfo->si_branch)) + goto out_sbinfo; + err = sysaufs_si_init(sbinfo); + if (unlikely(err)) + goto out_br; + + au_rw_init_wlock(&sbinfo->si_rwsem); + //au_dbg_locked_si_reg(sb, 1); + sbinfo->si_generation = 0; + //sbinfo->si_generation = INT_MAX - 2; + sbinfo->au_si_status = 0; + sbinfo->si_bend = -1; + sbinfo->si_last_br_id = 0; + + sbinfo->si_wbr_copyup = AuWbrCopyup_Def; + sbinfo->si_wbr_create = AuWbrCreate_Def; + sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + AuWbrCopyup_Def; + sbinfo->si_wbr_create_ops = au_wbr_create_ops + AuWbrCreate_Def; + + sbinfo->si_mntflags = AuOpt_Def; + + sbinfo->si_xread = NULL; + sbinfo->si_xwrite = NULL; + sbinfo->si_xib = NULL; + mutex_init(&sbinfo->si_xib_mtx); + sbinfo->si_xib_buf = NULL; + au_xino_def_br_set(NULL, sbinfo); + /* leave si_xib_last_pindex and si_xib_next_bit */ + + au_nwt_init(&sbinfo->si_nowait); + + sbinfo->si_rdcache = AUFS_RDCACHE_DEF * HZ; + sbinfo->si_dirwh = AUFS_DIRWH_DEF; + + au_spl_init(&sbinfo->si_plink); + + /* leave other members for sysaufs, si_list and si_mnt. */ + + au_robr_lvma_init(sbinfo); + sb->s_fs_info = sbinfo; + //sysaufs_sbi_get(sb); + + au_debug_sbinfo_init(sbinfo); + return 0; /* success */ + + out_br: + kfree(sbinfo->si_branch); + out_sbinfo: + kfree(sbinfo); + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct au_branch *au_sbr(struct super_block *sb, aufs_bindex_t bindex) +{ + struct au_branch *br; + + SiMustAnyLock(sb); + AuDebugOn(bindex < 0 || au_sbend(sb) < bindex); + br = au_sbi(sb)->si_branch[0 + bindex]; + AuDebugOn(!br); + return br; +} + +au_gen_t au_sigen_inc(struct super_block *sb) +{ + au_gen_t gen; + + SiMustWriteLock(sb); + gen = ++au_sbi(sb)->si_generation; + au_update_digen(sb->s_root); + au_update_iigen(sb->s_root->d_inode); + sb->s_root->d_inode->i_version++; + return gen; +} + +int au_find_bindex(struct super_block *sb, struct au_branch *br) +{ + aufs_bindex_t bindex, bend; + + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) + if (au_sbr(sb, bindex) == br) + return bindex; + return -1; +} + +/* ---------------------------------------------------------------------- */ + +/* dentry and super_block lock. call at entry point */ +void aufs_read_lock(struct dentry *dentry, int flags) +{ + si_read_lock(dentry->d_sb, flags); + if (au_ftest_lock(flags, DW)) + di_write_lock_child(dentry); + else + di_read_lock_child(dentry, flags); +} + +void aufs_read_unlock(struct dentry *dentry, int flags) +{ + if (au_ftest_lock(flags, DW)) + di_write_unlock(dentry); + else + di_read_unlock(dentry, flags); + si_read_unlock(dentry->d_sb); +} + +void aufs_write_lock(struct dentry *dentry) +{ + si_write_lock(dentry->d_sb); + di_write_lock_child(dentry); +} + +void aufs_write_unlock(struct dentry *dentry) +{ + di_write_unlock(dentry); + si_write_unlock(dentry->d_sb); +} + +void aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int flags) +{ + AuDebugOn(d1 == d2 || d1->d_sb != d2->d_sb); + si_read_lock(d1->d_sb, flags); + di_write_lock2_child(d1, d2, au_ftest_lock(flags, DIR)); +} + +void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2) +{ + AuDebugOn(d1 == d2 || d1->d_sb != d2->d_sb); + di_write_unlock2(d1, d2); + si_read_unlock(d1->d_sb); +} + +/* ---------------------------------------------------------------------- */ + +aufs_bindex_t au_new_br_id(struct super_block *sb) +{ + aufs_bindex_t br_id; + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + SiMustWriteLock(sb); + + sbinfo = au_sbi(sb); + while (1) { + br_id = ++sbinfo->si_last_br_id; + if (br_id && au_br_index(sb, br_id) < 0) + return br_id; + } +} diff --git a/fs/aufs/super.c b/fs/aufs/super.c new file mode 100644 index 0000000..1943a6b --- /dev/null +++ b/fs/aufs/super.c @@ -0,0 +1,1032 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * mount and super_block operations + * + * $Id: super.c,v 1.103 2009/01/26 06:24:10 sfjro Exp $ + */ + +#include +#include +#include +#include +#include + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +#include +typedef struct mnt_namespace au_mnt_ns_t; +#define au_nsproxy(tsk) (tsk)->nsproxy +#define au_mnt_ns(tsk) (tsk)->nsproxy->mnt_ns +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#include +typedef struct namespace au_mnt_ns_t; +#define au_nsproxy(tsk) (tsk)->nsproxy +#define au_mnt_ns(tsk) (tsk)->nsproxy->namespace +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +#include +typedef struct namespace au_mnt_ns_t; +#define au_nsproxy(tsk) (tsk)->namespace +#define au_mnt_ns(tsk) (tsk)->namespace +#endif + +#include "aufs.h" + +/* + * super_operations + */ +static struct inode *aufs_alloc_inode(struct super_block *sb) +{ + struct aufs_icntnr *c; + + AuTraceEnter(); + + c = au_cache_alloc_icntnr(); + if (c) { + inode_init_once(&c->vfs_inode); + c->vfs_inode.i_version = 1; /* sigen(sb); */ + c->iinfo.ii_hinode = NULL; + return &c->vfs_inode; + } + return NULL; +} + +static void aufs_destroy_inode(struct inode *inode) +{ + int err; + + LKTRTrace("i%lu\n", inode->i_ino); + + if (!inode->i_nlink || IS_DEADDIR(inode)) { + /* in nowait task or remount, sbi is write-locked */ + struct super_block *sb = inode->i_sb; + const int locked = si_noflush_read_trylock(sb); + + err = au_xigen_inc(inode); + if (unlikely(err)) + AuWarn1("failed resetting i_generation, %d\n", err); + if (locked) + si_read_unlock(sb); + } + + au_iinfo_fin(inode); + au_cache_free_icntnr(container_of(inode, struct aufs_icntnr, + vfs_inode)); +} + +static void aufs_read_inode(struct inode *inode) +{ + int err; +#if 0 + static struct backing_dev_info bdi = { + .ra_pages = 0, /* No readahead */ + .capabilities = BDI_CAP_NO_ACCT_DIRTY | BDI_CAP_NO_WRITEBACK + }; +#endif + + LKTRTrace("i%lu\n", inode->i_ino); + + err = au_xigen_new(inode); + if (!err) + err = au_iinfo_init(inode); + //if (LktrCond) err = -1; + if (!err) { + inode->i_version++; + //inode->i_mapping->backing_dev_info = &bdi; + return; /* success */ + } + + LKTRTrace("intializing inode info failed(%d)\n", err); + make_bad_inode(inode); +} + +/* lock free root dinfo */ +static int au_show_brs(struct seq_file *seq, struct super_block *sb) +{ + int err; + aufs_bindex_t bindex, bend; + struct au_hdentry *hd; + + AuTraceEnter(); + + err = 0; + bend = au_sbend(sb); + hd = au_di(sb->s_root)->di_hdentry; + for (bindex = 0; !err && bindex <= bend; bindex++) { + err = seq_path(seq, au_sbr_mnt(sb, bindex), + hd[bindex].hd_dentry, au_esc_chars); + if (err > 0) { + const char *p = au_optstr_br_perm(au_sbr_perm(sb, + bindex)); + err = seq_printf(seq, "=%s", p); + } + if (!err && bindex != bend) + err = seq_putc(seq, ':'); + } + + AuTraceErr(err); + return err; +} + +static void au_show_wbr_create(struct seq_file *m, int v, + struct au_sbinfo *sbinfo) +{ + au_parser_pattern_t pat; + + AuDebugOn(v == AuWbrCreate_Def); + + seq_printf(m, ",create="); + pat = au_optstr_wbr_create(v); + switch (v) { + case AuWbrCreate_TDP: + case AuWbrCreate_RR: + case AuWbrCreate_MFS: + case AuWbrCreate_PMFS: + seq_printf(m, pat); + break; + case AuWbrCreate_MFSV: + seq_printf(m, /*pat*/"mfs:%lu", + sbinfo->si_wbr_mfs.mfs_expire / HZ); + break; + case AuWbrCreate_PMFSV: + seq_printf(m, /*pat*/"pmfs:%lu", + sbinfo->si_wbr_mfs.mfs_expire / HZ); + break; + case AuWbrCreate_MFSRR: + seq_printf(m, /*pat*/"mfsrr:%llu", + sbinfo->si_wbr_mfs.mfsrr_watermark); + break; + case AuWbrCreate_MFSRRV: + seq_printf(m, /*pat*/"mfsrr:%llu:%lu", + sbinfo->si_wbr_mfs.mfsrr_watermark, + sbinfo->si_wbr_mfs.mfs_expire / HZ); + break; + } +} + +/* seq_file will re-call me in case of too long string */ +static int aufs_show_options(struct seq_file *m, struct vfsmount *mnt) +{ + int err, n; + unsigned int mnt_flags, v; + struct super_block *sb; + struct au_sbinfo *sbinfo; + struct file *xino; + + AuTraceEnter(); + + sb = mnt->mnt_sb; + /* lock free root dinfo */ + si_noflush_read_lock(sb); + sbinfo = au_sbi(sb); + + /* compatibility for some aufs scripts */ + seq_printf(m, ",si=%p", sbinfo); + mnt_flags = au_mntflags(sb); + if (au_opt_test(mnt_flags, XINO)) { + seq_puts(m, ",xino="); + xino = sbinfo->si_xib; + err = seq_path(m, xino->f_vfsmnt, xino->f_dentry, au_esc_chars); + if (unlikely(err <= 0)) + goto out; + err = 0; +#define Deleted "\\040(deleted)" + m->count -= sizeof(Deleted) - 1; + AuDebugOn(memcmp(m->buf + m->count, Deleted, + sizeof(Deleted) - 1)); +#undef Deleted +#if 0 //def CONFIG_AUFS_EXPORT /* reserved for future use */ + } else if (au_opt_test(mnt_flags, XINODIR)) { + seq_puts(m, ",xinodir="); + seq_path(m, &sbinfo->si_xinodir, au_esc_chars); +#endif + } else + seq_puts(m, ",noxino"); + +#define AuBool(name, str) do { \ + v = au_opt_test(mnt_flags, name); \ + if (v != au_opt_test(AuOpt_Def, name)) \ + seq_printf(m, ",%s" #str, v ? "" : "no"); \ +} while (0) + +#define AuStr(name, str) do { \ + v = mnt_flags & AuOptMask_##name; \ + if (v != (AuOpt_Def & AuOptMask_##name)) \ + seq_printf(m, "," #str "=%s", au_optstr_##str(v)); \ +} while (0) + +#ifdef CONFIG_AUFS_COMPAT +#define AuStr_BrOpt "dirs=" +#else +#define AuStr_BrOpt "br:" +#endif + + AuBool(TRUNC_XINO, trunc_xino); + AuBool(DIRPERM1, dirperm1); + AuBool(SHWH, shwh); + AuBool(PLINK, plink); + AuStr(UDBA, udba); + + v = sbinfo->si_wbr_create; + if (v != AuWbrCreate_Def) + au_show_wbr_create(m, v, sbinfo); + + v = sbinfo->si_wbr_copyup; + if (v != AuWbrCopyup_Def) + seq_printf(m, ",cpup=%s", au_optstr_wbr_copyup(v)); + + v = au_opt_test(mnt_flags, ALWAYS_DIROPQ); + if (v != au_opt_test(AuOpt_Def, ALWAYS_DIROPQ)) + seq_printf(m, ",diropq=%c", v ? 'a' : 'w'); + AuBool(REFROF, refrof); + AuBool(DLGT, dlgt); + AuBool(WARN_PERM, warn_perm); + AuBool(VERBOSE, verbose); + AuBool(SUM, sum); + /* AuBool(SUM_W, wsum); */ + + n = sbinfo->si_dirwh; + if (n != AUFS_DIRWH_DEF) + seq_printf(m, ",dirwh=%d", n); + n = sbinfo->si_rdcache / HZ; + if (n != AUFS_RDCACHE_DEF) + seq_printf(m, ",rdcache=%d", n); + + AuStr(COO, coo); + + if (!sysaufs_brs) { + seq_puts(m, "," AuStr_BrOpt); + au_show_brs(m, sb); + } + + out: + si_read_unlock(sb); + return 0; + +#undef AuBool +#undef AuStr +#undef AuStr_BrOpt +} + +#ifndef ULLONG_MAX /* before linux-2.6.18 */ +#define ULLONG_MAX (~0ULL) +#endif + +static u64 au_add_till_max(u64 a, u64 b) +{ + u64 old; + + old = a; + a += b; + if (old < a) + return a; + return ULLONG_MAX; +} + +static int au_statfs_sum(struct super_block *sb, struct kstatfs *buf, int dlgt) +{ + int err; + aufs_bindex_t bend, bindex, i; + unsigned char shared; + u64 blocks, bfree, bavail, files, ffree; + struct super_block *h_sb; + + AuTraceEnter(); + + blocks = 0; + bfree = 0; + bavail = 0; + files = 0; + ffree = 0; + + err = 0; + bend = au_sbend(sb); + for (bindex = bend; bindex >= 0; bindex--) { + h_sb = au_sbr_sb(sb, bindex); + shared = 0; + for (i = bindex + 1; !shared && i <= bend; i++) + shared = au_sbr_sb(sb, i) == h_sb; + if (shared) + continue; + + err = vfsub_statfs(h_sb->s_root, buf, dlgt); + if (unlikely(err)) + goto out; + + blocks = au_add_till_max(blocks, buf->f_blocks); + bfree = au_add_till_max(bfree, buf->f_bfree); + bavail = au_add_till_max(bavail, buf->f_bavail); + files = au_add_till_max(files, buf->f_files); + ffree = au_add_till_max(ffree, buf->f_ffree); + } + + buf->f_blocks = blocks; + buf->f_bfree = bfree; + buf->f_bavail = bavail; + buf->f_files = files; + buf->f_ffree = ffree; + + out: + AuTraceErr(err); + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +#define StatfsArg(sb) au_sbr_sb(sb, 0)->s_root +#define StatfsSb(d) ((d)->d_sb) +static int aufs_statfs(struct dentry *arg, struct kstatfs *buf) +#else +#define StatfsArg(sb) au_sbr_sb(sb, 0) +#define StatfsSb(s) (s) +static int aufs_statfs(struct super_block *arg, struct kstatfs *buf) +#endif +{ + int err; + unsigned int mnt_flags; + unsigned char dlgt; + struct super_block *sb = StatfsSb(arg); + + AuTraceEnter(); + + /* lock free root dinfo */ + si_noflush_read_lock(sb); + mnt_flags = au_mntflags(sb); + dlgt = !!au_test_dlgt(mnt_flags); + if (!au_opt_test(mnt_flags, SUM)) + err = vfsub_statfs(StatfsArg(sb), buf, dlgt); + else + err = au_statfs_sum(sb, buf, dlgt); + //if (LktrCond) err = -1; + si_read_unlock(sb); + if (!err) { + buf->f_type = AUFS_SUPER_MAGIC; + buf->f_namelen -= AUFS_WH_PFX_LEN; + //todo: support uuid? + memset(&buf->f_fsid, 0, sizeof(buf->f_fsid)); + } + /* buf->f_bsize = buf->f_blocks = buf->f_bfree = buf->f_bavail = -1; */ + + AuTraceErr(err); + return err; +} + +static void au_update_mnt(struct vfsmount *mnt, int flags) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) + struct vfsmount *pos; + struct super_block *sb = mnt->mnt_sb; + struct dentry *root = sb->s_root; + struct au_sbinfo *sbinfo = au_sbi(sb); + au_mnt_ns_t *ns; + + AuTraceEnter(); + AuDebugOn(!kernel_locked()); + + if (sbinfo->si_mnt != mnt + || atomic_read(&sb->s_active) == 1 + || !au_nsproxy(current)) + return; + + /* no get/put */ + ns = au_mnt_ns(current); + AuDebugOn(!ns); + sbinfo->si_mnt = NULL; + list_for_each_entry(pos, &ns->list, mnt_list) + if (pos != mnt && pos->mnt_sb->s_root == root) { + sbinfo->si_mnt = pos; + break; + } + AuDebugOn(!(flags & MNT_DETACH) && !sbinfo->si_mnt); +#endif +} + +static void au_fsync_br(struct super_block *sb) +{ +#ifdef CONFIG_AUFS_FSYNC_SUPER_PATCH + aufs_bindex_t bend, bindex; + int brperm; + struct au_branch *br; + struct super_block *h_sb; + + AuTraceEnter(); + + si_write_lock(sb); + bend = au_sbend(sb); + for (bindex = 0; bindex < bend; bindex++) { + br = au_sbr(sb, bindex); + brperm = br->br_perm; + if (brperm == AuBrPerm_RR || brperm == AuBrPerm_RRWH) + continue; + h_sb = br->br_mnt->mnt_sb; + if (bdev_read_only(h_sb->s_bdev)) + continue; + + lockdep_off(); + down_write(&h_sb->s_umount); + shrink_dcache_sb(h_sb); + fsync_super(h_sb); + up_write(&h_sb->s_umount); + lockdep_on(); + } + si_write_unlock(sb); +#endif +} + +#define UmountBeginHasMnt (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)) + +#if UmountBeginHasMnt +#define UmountBeginSb(mnt) ((mnt)->mnt_sb) +#define UmountBeginMnt(mnt) (mnt) +#define UmountBeginFlags _flags +static void aufs_umount_begin(struct vfsmount *arg, int _flags) +#else +#define UmountBeginSb(sb) sb +#define UmountBeginMnt(sb) NULL +#define UmountBeginFlags 0 +static void aufs_umount_begin(struct super_block *arg) +#endif +{ + struct super_block *sb = UmountBeginSb(arg); + struct vfsmount *mnt = UmountBeginMnt(arg); + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + + sbinfo = au_sbi(sb); + if (!sbinfo) + return; + + au_fsync_br(sb); + + si_write_lock(sb); + if (au_opt_test(au_mntflags(sb), PLINK)) + au_plink_put(sb); +#if 0 // remove + if (au_opt_test(au_mntflags(sb), UDBA_INOTIFY)) + shrink_dcache_sb(sb); +#endif + au_update_mnt(mnt, UmountBeginFlags); +#if 0 + if (sbinfo->si_wbr_create_ops->fin) + sbinfo->si_wbr_create_ops->fin(sb); +#endif + si_write_unlock(sb); +} + +/* final actions when unmounting a file system */ +static void aufs_put_super(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + + sbinfo = au_sbi(sb); + if (!sbinfo) + return; + +#if !UmountBeginHasMnt + /* umount_begin() may not be called. */ + aufs_umount_begin(sb); +#endif + au_si_free(sb, /*err*/0); +} + +/* ---------------------------------------------------------------------- */ + +/* + * refresh dentry and inode at remount time. + */ +static int do_refresh(struct dentry *dentry, mode_t type, + unsigned int dir_flags) +{ + int err; + struct dentry *parent; + struct inode *inode; + + LKTRTrace("%.*s, 0%o\n", AuDLNPair(dentry), type); + inode = dentry->d_inode; + AuDebugOn(!inode); + + di_write_lock_child(dentry); + parent = dget_parent(dentry); + di_read_lock_parent(parent, AuLock_IR); + /* returns a number of positive dentries */ + err = au_refresh_hdentry(dentry, type); + if (err >= 0) { + err = au_refresh_hinode(inode, dentry); + if (!err && type == S_IFDIR) + au_reset_hinotify(inode, dir_flags); + } + if (unlikely(err)) + AuErr("unrecoverable error %d, %.*s\n", err, AuDLNPair(dentry)); + di_read_unlock(parent, AuLock_IR); + dput(parent); + di_write_unlock(dentry); + + AuTraceErr(err); + return err; +} + +static int test_dir(struct dentry *dentry, void *arg) +{ + return S_ISDIR(dentry->d_inode->i_mode); +} + +/* todo: merge with refresh_nondir()? */ +static int refresh_dir(struct dentry *root, au_gen_t sgen) +{ + int err, i, j, ndentry, e; + struct au_dcsub_pages dpages; + struct au_dpage *dpage; + struct dentry **dentries; + struct inode *inode; + const unsigned int flags = au_hi_flags(root->d_inode, /*isdir*/1); + + LKTRTrace("sgen %d\n", sgen); + SiMustWriteLock(root->d_sb); + /* dont trust BKL */ + AuDebugOn(au_digen(root) != sgen || !kernel_locked()); + + err = 0; + list_for_each_entry(inode, &root->d_sb->s_inodes, i_sb_list) + if (S_ISDIR(inode->i_mode) && au_iigen(inode) != sgen) { + ii_write_lock_child(inode); + e = au_refresh_hinode_self(inode); + ii_write_unlock(inode); + if (unlikely(e)) { + LKTRTrace("e %d, i%lu\n", e, inode->i_ino); + if (!err) + err = e; + /* go on even if err */ + } + } + + e = au_dpages_init(&dpages, GFP_NOFS); + if (unlikely(e)) { + if (!err) + err = e; + goto out; + } + e = au_dcsub_pages(&dpages, root, test_dir, NULL); + if (unlikely(e)) { + if (!err) + err = e; + goto out_dpages; + } + + for (i = 0; !e && i < dpages.ndpage; i++) { + dpage = dpages.dpages + i; + dentries = dpage->dentries; + ndentry = dpage->ndentry; + for (j = 0; !e && j < ndentry; j++) { + struct dentry *d; + d = dentries[j]; +#ifdef CONFIG_AUFS_DEBUG + { + struct dentry *parent; + parent = dget_parent(d); + AuDebugOn(!S_ISDIR(d->d_inode->i_mode) + || IS_ROOT(d) + || au_digen(parent) != sgen); + dput(parent); + } +#endif + if (au_digen(d) != sgen) { + e = do_refresh(d, S_IFDIR, flags); + if (unlikely(e && !err)) + err = e; + /* break on err */ + } + } + } + + out_dpages: + au_dpages_free(&dpages); + out: + AuTraceErr(err); + return err; +} + +static int test_nondir(struct dentry *dentry, void *arg) +{ + return !S_ISDIR(dentry->d_inode->i_mode); +} + +static int refresh_nondir(struct dentry *root, au_gen_t sgen, int do_dentry) +{ + int err, i, j, ndentry, e; + struct au_dcsub_pages dpages; + struct au_dpage *dpage; + struct dentry **dentries; + struct inode *inode; + + LKTRTrace("sgen %d\n", sgen); + SiMustWriteLock(root->d_sb); + /* dont trust BKL */ + AuDebugOn(au_digen(root) != sgen || !kernel_locked()); + + err = 0; + list_for_each_entry(inode, &root->d_sb->s_inodes, i_sb_list) + if (!S_ISDIR(inode->i_mode) && au_iigen(inode) != sgen) { + ii_write_lock_child(inode); + e = au_refresh_hinode_self(inode); + ii_write_unlock(inode); + if (unlikely(e)) { + LKTRTrace("e %d, i%lu\n", e, inode->i_ino); + if (!err) + err = e; + /* go on even if err */ + } + } + + if (!do_dentry) + goto out; + + e = au_dpages_init(&dpages, GFP_NOFS); + if (unlikely(e)) { + if (!err) + err = e; + goto out; + } + e = au_dcsub_pages(&dpages, root, test_nondir, NULL); + if (unlikely(e)) { + if (!err) + err = e; + goto out_dpages; + } + + for (i = 0; i < dpages.ndpage; i++) { + dpage = dpages.dpages + i; + dentries = dpage->dentries; + ndentry = dpage->ndentry; + for (j = 0; j < ndentry; j++) { + struct dentry *d; + d = dentries[j]; +#ifdef CONFIG_AUFS_DEBUG + { + struct dentry *parent; + parent = dget_parent(d); + AuDebugOn(S_ISDIR(d->d_inode->i_mode) + || au_digen(parent) != sgen); + dput(parent); + } +#endif + inode = d->d_inode; + if (inode && au_digen(d) != sgen) { + e = do_refresh(d, inode->i_mode & S_IFMT, 0); + if (unlikely(e && !err)) + err = e; + /* go on even err */ + } + } + } + + out_dpages: + au_dpages_free(&dpages); + out: + AuTraceErr(err); + return err; +} + +/* stop extra interpretation of errno in mount(8), and strange error messages */ +static int cvt_err(int err) +{ + AuTraceErr(err); + + switch (err) { + case -ENOENT: + case -ENOTDIR: + case -EEXIST: + case -EIO: + err = -EINVAL; + } + return err; +} + +/* protected by s_umount */ +static int aufs_remount_fs(struct super_block *sb, int *flags, char *data) +{ + int err, rerr; + au_gen_t sigen; + struct dentry *root; + struct inode *inode; + struct au_opts opts; + struct au_sbinfo *sbinfo; + unsigned char dlgt; + + LKTRTrace("flags 0x%x, data %s, len %lu\n", + *flags, data ? data : "NULL", + (unsigned long)(data ? strlen(data) : 0)); + + au_fsync_br(sb); + + err = 0; + root = sb->s_root; + if (!data || !*data) { + aufs_write_lock(root); + err = verify_opts(sb, *flags, /*pending*/0, /*remount*/1); + aufs_write_unlock(root); + goto out; /* success */ + } + + err = -ENOMEM; + memset(&opts, 0, sizeof(opts)); + opts.opt = (void *)__get_free_page(GFP_NOFS); + if (unlikely(!opts.opt)) + goto out; + opts.max_opt = PAGE_SIZE / sizeof(*opts.opt); + opts.flags = AuOpts_REMOUNT; + opts.sb_flags = *flags; + + /* parse it before aufs lock */ + err = au_opts_parse(sb, data, &opts); + if (unlikely(err)) + goto out_opts; + + sbinfo = au_sbi(sb); + inode = root->d_inode; + vfsub_i_lock(inode); + aufs_write_lock(root); + + /* au_do_opts() may return an error */ + err = au_opts_remount(sb, &opts); + au_opts_free(&opts); + + if (au_ftest_opts(opts.flags, REFRESH_DIR) + || au_ftest_opts(opts.flags, REFRESH_NONDIR)) { + dlgt = !!au_opt_test(sbinfo->si_mntflags, DLGT); + au_opt_clr(sbinfo->si_mntflags, DLGT); + au_sigen_inc(sb); + au_reset_hinotify(inode, au_hi_flags(inode, /*isdir*/1)); + sigen = au_sigen(sb); + au_fclr_si(sbinfo, FAILED_REFRESH_DIRS); + + DiMustNoWaiters(root); + IiMustNoWaiters(root->d_inode); + di_write_unlock(root); + + rerr = refresh_dir(root, sigen); + if (unlikely(rerr)) { + au_fset_si(sbinfo, FAILED_REFRESH_DIRS); + AuWarn("Refreshing directories failed, ignores (%d)\n", + rerr); + } + + if (au_ftest_opts(opts.flags, REFRESH_NONDIR)) { + rerr = refresh_nondir(root, sigen, !rerr); + if (unlikely(rerr)) + AuWarn("Refreshing non-directories failed," + " ignores (%d)\n", rerr); + } + + /* aufs_write_lock() calls ..._child() */ + di_write_lock_child(root); + + au_cpup_attr_all(inode, /*force*/1); + if (dlgt) + au_opt_set(sbinfo->si_mntflags, DLGT); + } + + aufs_write_unlock(root); + vfsub_i_unlock(inode); + + out_opts: + free_page((unsigned long)opts.opt); + out: + err = cvt_err(err); + AuTraceErr(err); + return err; +} + +static struct super_operations aufs_sop = { + .alloc_inode = aufs_alloc_inode, + .destroy_inode = aufs_destroy_inode, + .read_inode = aufs_read_inode, + //.dirty_inode = aufs_dirty_inode, + //.write_inode = aufs_write_inode, + //void (*put_inode) (struct inode *); + .drop_inode = generic_delete_inode, + //.delete_inode = aufs_delete_inode, + //.clear_inode = aufs_clear_inode, + + .show_options = aufs_show_options, + .statfs = aufs_statfs, + + .put_super = aufs_put_super, + //void (*write_super) (struct super_block *); + //int (*sync_fs)(struct super_block *sb, int wait); + //void (*write_super_lockfs) (struct super_block *); + //void (*unlockfs) (struct super_block *); + .remount_fs = aufs_remount_fs, + /* depends upon umount flags. also use put_super() (< 2.6.18) */ + .umount_begin = aufs_umount_begin +}; + +/* ---------------------------------------------------------------------- */ + +/* + * at first mount time. + */ + +static int alloc_root(struct super_block *sb) +{ + int err; + struct inode *inode; + struct dentry *root; + + AuTraceEnter(); + + err = -ENOMEM; + inode = iget(sb, AUFS_ROOT_INO); + //if (LktrCond) {iput(inode); inode = NULL;} + if (unlikely(!inode)) + goto out; + err = PTR_ERR(inode); + if (IS_ERR(inode)) + goto out; + err = -ENOMEM; + if (unlikely(is_bad_inode(inode))) + goto out_iput; + + inode->i_op = &aufs_dir_iop; + inode->i_fop = &aufs_dir_fop; + inode->i_mode = S_IFDIR; + root = d_alloc_root(inode); + //if (LktrCond) {igrab(inode); dput(root); root = NULL;} + if (unlikely(!root)) + goto out_iput; + err = PTR_ERR(root); + if (IS_ERR(root)) + goto out_iput; + + err = au_alloc_dinfo(root); + if (!err) { + sb->s_root = root; + return 0; /* success */ + } + dput(root); + goto out; /* do not iput */ + + out_iput: + iput(inode); + out: + AuTraceErr(err); + return err; + +} + +static int aufs_fill_super(struct super_block *sb, void *raw_data, int silent) +{ + int err; + struct dentry *root; + struct inode *inode; + struct au_opts opts; + char *arg = raw_data; + + if (unlikely(!arg || !*arg)) { + err = -EINVAL; + AuErr("no arg\n"); + goto out; + } + LKTRTrace("%s, silent %d\n", arg, silent); + + err = -ENOMEM; + memset(&opts, 0, sizeof(opts)); + opts.opt = (void *)__get_free_page(GFP_NOFS); + if (unlikely(!opts.opt)) + goto out; + opts.max_opt = PAGE_SIZE / sizeof(*opts.opt); + opts.sb_flags = sb->s_flags; + + err = au_si_alloc(sb); + if (unlikely(err)) + goto out_opts; + SiMustWriteLock(sb); + + /* all timestamps always follow the ones on the branch */ + sb->s_flags |= MS_NOATIME | MS_NODIRATIME; + sb->s_op = &aufs_sop; + sb->s_magic = AUFS_SUPER_MAGIC; + au_export_init(sb); + + err = alloc_root(sb); + if (unlikely(err)) { + AuDebugOn(sb->s_root); + si_write_unlock(sb); + goto out_info; + } + root = sb->s_root; + DiMustWriteLock(root); + inode = root->d_inode; + inode->i_nlink = 2; + + /* + * actually we can parse options regardless aufs lock here. + * but at remount time, parsing must be done before aufs lock. + * so we follow the same rule. + */ + ii_write_lock_parent(inode); + au_dbg_locked_si_reg(sb, 1); + aufs_write_unlock(root); + err = au_opts_parse(sb, arg, &opts); + if (unlikely(err)) + goto out_root; + + /* lock vfs_inode first, then aufs. */ + vfsub_i_lock(inode); + inode->i_op = &aufs_dir_iop; + inode->i_fop = &aufs_dir_fop; + aufs_write_lock(root); + + sb->s_maxbytes = 0; + err = au_opts_mount(sb, &opts); + au_opts_free(&opts); + if (unlikely(err)) + goto out_unlock; + AuDebugOn(!sb->s_maxbytes); + + aufs_write_unlock(root); + vfsub_i_unlock(inode); + goto out_opts; /* success */ + + out_unlock: + aufs_write_unlock(root); + vfsub_i_unlock(inode); + out_root: + dput(root); + sb->s_root = NULL; + out_info: + au_si_free(sb, /*err*/1); + sb->s_fs_info = NULL; + out_opts: + free_page((unsigned long)opts.opt); + out: + AuTraceErr(err); + err = cvt_err(err); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +static int aufs_get_sb(struct file_system_type *fs_type, int flags, + const char *dev_name, void *raw_data, + struct vfsmount *mnt) +{ + int err; + + /* all timestamps always follow the ones on the branch */ + /* mnt->mnt_flags |= MNT_NOATIME | MNT_NODIRATIME; */ + err = get_sb_nodev(fs_type, flags, raw_data, aufs_fill_super, mnt); + if (!err) { + struct super_block *sb = mnt->mnt_sb; + struct au_sbinfo *sbinfo = au_sbi(sb); + sbinfo->si_mnt = mnt; + + au_sbilist_lock(); + au_sbilist_add(sbinfo); + si_write_lock(sb); + sysaufs_sbi_add(sb); + si_write_unlock(sb); + au_sbilist_unlock(); + } + return err; +} +#else +static struct super_block *aufs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *raw_data) +{ + return get_sb_nodev(fs_type, flags, raw_data, aufs_fill_super); +} +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) */ + +struct file_system_type aufs_fs_type = { + .name = AUFS_FSTYPE, + .fs_flags = +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) + FS_RENAME_DOES_D_MOVE | /* a race between rename and others */ +#endif + FS_REVAL_DOT, /* for NFS branch and udba */ + .get_sb = aufs_get_sb, + .kill_sb = generic_shutdown_super, + /* no need to __module_get() and module_put(). */ + .owner = THIS_MODULE, +}; diff --git a/fs/aufs/super.h b/fs/aufs/super.h new file mode 100644 index 0000000..02acd04 --- /dev/null +++ b/fs/aufs/super.h @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * super_block operations + * + * $Id: super.h,v 1.91 2009/01/26 06:24:15 sfjro Exp $ + */ + +#ifndef __AUFS_SUPER_H__ +#define __AUFS_SUPER_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +#include +#else +#include +#endif +#include +//#include "hinode.h" +#include "misc.h" +//#include "opts.h" +#include "wkq.h" + +typedef ssize_t (*au_readf_t)(struct file *, char __user *, size_t, loff_t *); +typedef ssize_t (*au_writef_t)(struct file *, const char __user *, size_t, + loff_t *); + +struct au_wbr_copyup_operations { + int (*copyup)(struct dentry *dentry); +}; + +struct au_wbr_create_operations { + int (*create)(struct dentry *dentry, int isdir); + int (*init)(struct super_block *sb); + int (*fin)(struct super_block *sb); +}; + +struct au_wbr_mfs { + struct mutex mfs_lock; /* protect this structure */ + unsigned long mfs_jiffy; + unsigned long mfs_expire; + aufs_bindex_t mfs_bindex; + + unsigned long long mfsrr_bytes; + unsigned long long mfsrr_watermark; +}; + +/* sbinfo status flags */ +/* + * set true when refresh_dirs() failed at remount time. + * then try refreshing dirs at access time again. + * if it is false, refreshing dirs at access time is unnecesary + */ +#define AuSi_FAILED_REFRESH_DIRS 1 +#define au_ftest_si(sbinfo, name) ((sbinfo)->au_si_status & AuSi_##name) +#define au_fset_si(sbinfo, name) \ + { (sbinfo)->au_si_status |= AuSi_##name; } +#define au_fclr_si(sbinfo, name) \ + { (sbinfo)->au_si_status &= ~AuSi_##name; } + +struct sysaufs_sbinfo; +struct au_branch; +struct au_sbinfo { + /* nowait tasks in the system-wide workqueue */ + struct au_nowait_tasks si_nowait; + + struct au_rwsem si_rwsem; + + /* branch management */ + au_gen_t si_generation; + + /* see above flags */ + unsigned char au_si_status; + + aufs_bindex_t si_bend; + aufs_bindex_t si_last_br_id; + struct au_branch **si_branch; + + /* policy to select a writable branch */ + unsigned char si_wbr_copyup; + unsigned char si_wbr_create; + struct au_wbr_copyup_operations *si_wbr_copyup_ops; + struct au_wbr_create_operations *si_wbr_create_ops; + + /* round robin */ + atomic_t si_wbr_rr_next; + + /* most free space */ + struct au_wbr_mfs si_wbr_mfs; + + /* mount flags */ + /* include/asm-ia64/siginfo.h defines a macro named si_flags */ + unsigned int si_mntflags; + + /* external inode number (bitmap and translation table) */ + au_readf_t si_xread; + au_writef_t si_xwrite; + struct file *si_xib; + struct mutex si_xib_mtx; /* protect xib members */ + unsigned long *si_xib_buf; + unsigned long si_xib_last_pindex; + int si_xib_next_bit; + /* reserved for future use */ + /* unsigned long long si_xib_limit; */ /* Max xib file size */ + +#ifdef CONFIG_AUFS_HINOTIFY + struct au_branch *si_xino_def_br; +#endif + +#ifdef CONFIG_AUFS_EXPORT + /* i_generation */ + struct file *si_xigen; + atomic_t si_xigen_next; +#endif + + /* readdir cache time, max, in HZ */ + unsigned long si_rdcache; + + /* + * If the number of whiteouts are larger than si_dirwh, leave all of + * them after au_whtmp_ren to reduce the cost of rmdir(2). + * future fsck.aufs or kernel thread will remove them later. + * Otherwise, remove all whiteouts and the dir in rmdir(2). + */ + unsigned int si_dirwh; + + /* + * rename(2) a directory with all children. + */ + /* reserved for future use */ + /* int si_rendir; */ + + /* pseudo_link list */ /* todo: dirty? */ + struct au_splhead si_plink; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) + struct vfsmount *si_mnt; /* no get/put */ +#endif + + /* sysfs */ +#ifdef CONFIG_AUFS_SYSAUFS + /* super_blocks list is not exported */ + struct list_head si_list; + struct sysaufs_sbinfo *si_sa; +#endif + +#ifdef CONFIG_AUFS_ROBR + /* locked vma list for mmap() */ // very dirty + struct au_splhead si_lvma; +#endif + +#if 0 //def CONFIG_AUFS_EXPORT /* reserved for future use */ + struct path si_xinodir; +#endif + +#ifdef CONFIG_AUFS_DEBUG_LOCK + struct au_splhead si_dbg_lock[AuDbgLock_Last]; +#endif +}; + +/* ---------------------------------------------------------------------- */ + +/* policy to select one among writable branches */ +#define AuWbrCopyup(sbinfo, args...) \ + ((sbinfo)->si_wbr_copyup_ops->copyup(args)) +#define AuWbrCreate(sbinfo, args...) \ + ((sbinfo)->si_wbr_create_ops->create(args)) + +/* flags for si_read_lock()/aufs_read_lock()/di_read_lock() */ +#define AuLock_DW 1 /* write-lock dentry */ +#define AuLock_IR (1 << 1) /* read-lock inode */ +#define AuLock_IW (1 << 2) /* write-lock inode */ +#define AuLock_FLUSH (1 << 3) /* wait for 'nowait' tasks */ +#define AuLock_DIR (1 << 4) /* target is a dir */ +#define au_ftest_lock(flags, name) ((flags) & AuLock_##name) +#define au_fset_lock(flags, name) { (flags) |= AuLock_##name; } +#define au_fclr_lock(flags, name) { (flags) &= ~AuLock_##name; } + +/* ---------------------------------------------------------------------- */ + +/* super.c */ +extern struct file_system_type aufs_fs_type; + +/* sbinfo.c */ +void au_si_free(struct super_block *sb, int err); +int au_si_alloc(struct super_block *sb); +struct au_branch *au_sbr(struct super_block *sb, aufs_bindex_t bindex); +au_gen_t au_sigen_inc(struct super_block *sb); +int au_find_bindex(struct super_block *sb, struct au_branch *br); + +void aufs_read_lock(struct dentry *dentry, int flags); +void aufs_read_unlock(struct dentry *dentry, int flags); +void aufs_write_lock(struct dentry *dentry); +void aufs_write_unlock(struct dentry *dentry); +void aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int isdir); +void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2); + +aufs_bindex_t au_new_br_id(struct super_block *sb); + +/* wbr_policy.c */ +extern struct au_wbr_copyup_operations au_wbr_copyup_ops[]; +extern struct au_wbr_create_operations au_wbr_create_ops[]; +int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst); + +/* ---------------------------------------------------------------------- */ + +#if 0 //LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) +static inline int au_mnt_want_write(struct vfsmount *h_mnt) +{ + return mnt_want_write(h_mnt); +} + +static inline void au_mnt_drop_write(struct vfsmount *h_mnt) +{ + mnt_drop_write(h_mnt); +} +#else +static inline int au_mnt_want_write(struct vfsmount *h_mnt) +{ + return 0; +} + +static inline void au_mnt_drop_write(struct vfsmount *h_mnt) +{ + /* empty */ +} +#endif + +/* ---------------------------------------------------------------------- */ + +static inline struct au_sbinfo *au_sbi(struct super_block *sb) +{ + return sb->s_fs_info; +} + +static inline const char *au_sbtype(struct super_block *sb) +{ + return sb->s_type->name; +} + +static inline int au_test_aufs(struct super_block *sb) +{ + return sb->s_magic == AUFS_SUPER_MAGIC; +} + +static inline int au_test_nfs(struct super_block *sb) +{ +#ifdef CONFIG_AUFS_BR_NFS + return sb->s_magic == NFS_SUPER_MAGIC; +#else + return 0; +#endif +} + +static inline int au_test_nfs4(struct super_block *sb) +{ +#ifdef CONFIG_AUFS_BR_NFS_V4 + return au_test_nfs(sb) && !strcmp(sb->s_type->name, "nfs4"); +#else + return 0; +#endif +} + +static inline int au_test_fuse(struct super_block *sb) +{ +#ifdef CONFIG_AUFS_WORKAROUND_FUSE + return sb->s_magic == FUSE_SUPER_MAGIC; +#else + return 0; +#endif +} + +static inline int au_test_xfs(struct super_block *sb) +{ +#ifdef CONFIG_AUFS_BR_XFS + return sb->s_magic == XFS_SB_MAGIC; +#else + return 0; +#endif +} + +static inline int au_test_tmpfs(struct super_block *sb) +{ +#ifdef CONFIG_TMPFS + return sb->s_magic == TMPFS_MAGIC; +#else + return 0; +#endif +} + +static inline int au_test_ecryptfs(struct super_block *sb) +{ +#if defined(CONFIG_ECRYPT_FS) || defined(CONFIG_ECRYPT_FS_MODULE) + /* why don't they use s_magic? */ + return !strcmp(sb->s_type->name, "ecryptfs"); +#else + return 0; +#endif +} + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_HINOTIFY +static inline void au_xino_def_br_set(struct au_branch *br, + struct au_sbinfo *sbinfo) +{ + sbinfo->si_xino_def_br = br; +} + +static inline struct au_branch *au_xino_def_br(struct au_sbinfo *sbinfo) +{ + return sbinfo->si_xino_def_br; +} +#else +static inline void au_xino_def_br_set(struct au_branch *br, + struct au_sbinfo *sbinfo) +{ + /* empty */ +} + +static inline struct au_branch *au_xino_def_br(struct au_sbinfo *sbinfo) +{ + return NULL; +} +#endif + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_EXPORT +void au_export_init(struct super_block *sb); + +static inline int au_test_nfsd(struct task_struct *tsk) +{ + return !tsk->mm && !strcmp(tsk->comm, "nfsd"); +} + +static inline void au_export_put(struct au_sbinfo *sbinfo) +{ +#if 0 /* reserved for future use */ + path_put(&sbinfo->si_xinodir); +#endif +} + +int au_xigen_inc(struct inode *inode); +int au_xigen_new(struct inode *inode); +int au_xigen_set(struct super_block *sb, struct file *base); +void au_xigen_clr(struct super_block *sb); +#else +static inline void au_export_init(struct super_block *sb) +{ + /* nothing */ +} + +static inline int au_test_nfsd(struct task_struct *tsk) +{ + return 0; +} + +static inline void au_export_put(struct au_sbinfo *sbinfo) +{ + /* nothing */ +} + +static inline int au_xigen_inc(struct inode *inode) +{ + return 0; +} + +static inline int au_xigen_new(struct inode *inode) +{ + return 0; +} + +static inline int au_xigen_set(struct super_block *sb, struct file *base) +{ + return 0; +} + +static inline void au_xigen_clr(struct super_block *sb) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_EXPORT */ + +#ifdef CONFIG_AUFS_ROBR +static inline int au_test_nested(struct super_block *h_sb) +{ + return 0; +} + +static inline void au_robr_lvma_init(struct au_sbinfo *sbinfo) +{ + au_spl_init(&sbinfo->si_lvma); +} +#else +static inline int au_test_nested(struct super_block *h_sb) +{ + int err = 0; + if (unlikely(au_test_aufs(h_sb))) { + err = -EINVAL; + AuTraceErr(err); + } + return err; +} + +static inline void au_robr_lvma_init(struct au_sbinfo *sbinfo) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_ROBR */ + +/* ---------------------------------------------------------------------- */ + +/* lock superblock. mainly for entry point functions */ +/* + * si_do_noflush_read_lock, si_do_noflush_write_lock, + * si_do_read_unlock, si_do_write_unlock, si_do_downgrade_lock + */ +AuSimpleLockRwsemFuncs(si_do_noflush, struct super_block *sb, + au_sbi(sb)->si_rwsem); +AuSimpleUnlockRwsemFuncs(si_do, struct super_block *sb, au_sbi(sb)->si_rwsem); + +static inline void si_noflush_read_lock(struct super_block *sb) +{ + au_dbg_locking_si_reg(sb, 0); + si_do_noflush_read_lock(sb); + au_dbg_locking_si_unreg(sb, 0); + au_dbg_locked_si_reg(sb, 0); +} + +static inline void si_noflush_write_lock(struct super_block *sb) +{ + au_dbg_locking_si_reg(sb, 1); + si_do_noflush_write_lock(sb); + au_dbg_locking_si_unreg(sb, 1); + au_dbg_locked_si_reg(sb, 1); +} + +static inline int si_noflush_read_trylock(struct super_block *sb) +{ + int locked; + + au_dbg_locking_si_reg(sb, 0); + locked = si_do_noflush_read_trylock(sb); + au_dbg_locking_si_unreg(sb, 0); + if (locked) + au_dbg_locked_si_reg(sb, 0); + return locked; +} + +static inline int si_noflush_write_trylock(struct super_block *sb) +{ + int locked; + + au_dbg_locking_si_reg(sb, 1); + locked = si_do_noflush_write_trylock(sb); + au_dbg_locking_si_unreg(sb, 1); + if (locked) + au_dbg_locked_si_reg(sb, 1); + return locked; +} + +static inline void si_read_unlock(struct super_block *sb) +{ + si_do_read_unlock(sb); + au_dbg_locked_si_unreg(sb, 0); +} + +static inline void si_write_unlock(struct super_block *sb) +{ + si_do_write_unlock(sb); + au_dbg_locked_si_unreg(sb, 1); +} + +static inline void si_downgrade_lock(struct super_block *sb) +{ + si_do_downgrade_lock(sb); +} + +static inline void si_read_lock(struct super_block *sb, int flags) +{ + if (/* !au_test_nfsd(current) && */au_ftest_lock(flags, FLUSH)) + au_nwt_flush(&au_sbi(sb)->si_nowait); + si_noflush_read_lock(sb); +} + +static inline void si_write_lock(struct super_block *sb) +{ + //WARN_ON(au_test_nfsd(current)); + au_nwt_flush(&au_sbi(sb)->si_nowait); + si_noflush_write_lock(sb); +} + +static inline int si_read_trylock(struct super_block *sb, int flags) +{ + if (/* !au_test_nfsd(current) && */au_ftest_lock(flags, FLUSH)) + au_nwt_flush(&au_sbi(sb)->si_nowait); + return si_noflush_read_trylock(sb); +} + +static inline int si_write_trylock(struct super_block *sb, int flags) +{ + if (/* !au_test_nfsd(current) && */au_ftest_lock(flags, FLUSH)) + au_nwt_flush(&au_sbi(sb)->si_nowait); + return si_noflush_write_trylock(sb); +} + +/* to debug easier, do not make them inlined functions */ +#define SiMustReadLock(sb) AuRwMustReadLock(&au_sbi(sb)->si_rwsem) +#define SiMustWriteLock(sb) AuRwMustWriteLock(&au_sbi(sb)->si_rwsem) +#define SiMustAnyLock(sb) AuRwMustAnyLock(&au_sbi(sb)->si_rwsem) + +/* ---------------------------------------------------------------------- */ + +static inline aufs_bindex_t au_sbend(struct super_block *sb) +{ + SiMustAnyLock(sb); + return au_sbi(sb)->si_bend; +} + +static inline unsigned int au_mntflags(struct super_block *sb) +{ + SiMustAnyLock(sb); + return au_sbi(sb)->si_mntflags; +} + +static inline au_gen_t au_sigen(struct super_block *sb) +{ + SiMustAnyLock(sb); + return au_sbi(sb)->si_generation; +} + +/* ---------------------------------------------------------------------- */ + +/* limited support before 2.6.18 */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +static inline void au_mntget(struct super_block *sb) +{ + mntget(au_sbi(sb)->si_mnt); +} + +static inline void au_mntput(struct super_block *sb) +{ + mntput(au_sbi(sb)->si_mnt); +} +#else +static inline void au_mntget(struct super_block *sb) +{ + /* empty */ +} + +static inline void au_mntput(struct super_block *sb) +{ + /* empty */ +} +#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) */ + +#endif /* __KERNEL__ */ +#endif /* __AUFS_SUPER_H__ */ diff --git a/fs/aufs/sysaufs.c b/fs/aufs/sysaufs.c new file mode 100644 index 0000000..422c7e5 --- /dev/null +++ b/fs/aufs/sysaufs.c @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sysfs interface + * + * $Id: sysaufs.c,v 1.46 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include +#include +#include +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +/* super_blocks list is not exported */ +DEFINE_MUTEX(au_sbilist_mtx); +LIST_HEAD(au_sbilist); + +static decl_subsys(aufs, NULL, NULL); +enum { + Sbi, Stat, Config, +#ifdef CONFIG_AUFS_DEBUG + Debug, +#endif + _Last +}; + +static ssize_t config_show(au_subsys_t *subsys, char *buf) +{ + //struct kset *kset = &au_subsys_to_kset(*subsys); +#define conf_bool(name) "CONFIG_AUFS_" #name "=y\n" + static const char opt[] = +#ifdef CONFIG_AUFS + "CONFIG_AUFS=y\n" +#else + "CONFIG_AUFS=m\n" +#endif +#ifdef CONFIG_AUFS_BRANCH_MAX_127 + conf_bool(BRANCH_MAX_127) +#elif defined(CONFIG_AUFS_BRANCH_MAX_511) + conf_bool(BRANCH_MAX_511) +#elif defined(CONFIG_AUFS_BRANCH_MAX_1023) + conf_bool(BRANCH_MAX_1023) +#elif defined(CONFIG_AUFS_BRANCH_MAX_32767) + conf_bool(BRANCH_MAX_32767) +#endif + conf_bool(SYSAUFS) +#ifdef CONFIG_AUFS_HINOTIFY + conf_bool(HINOTIFY) +#endif +#ifdef CONFIG_AUFS_EXPORT + conf_bool(EXPORT) +#endif +#ifdef CONFIG_AUFS_INO_T_64 + conf_bool(INO_T_64) +#endif +#ifdef CONFIG_AUFS_ROBR + conf_bool(ROBR) +#endif +#ifdef CONFIG_AUFS_DLGT + conf_bool(DLGT) +#endif +#ifdef CONFIG_AUFS_HIN_OR_DLGT + conf_bool(HIN_OR_DLGT) +#endif +#ifdef CONFIG_AUFS_SHWH + conf_bool(SHWH) +#endif +#ifdef CONFIG_AUFS_RR_SQUASHFS + conf_bool(RR_SQUASHFS) +#endif +#ifdef CONFIG_AUFS_SEC_PERM_PATCH + conf_bool(SEC_PERM_PATCH) +#endif +#ifdef CONFIG_AUFS_SPLICE_PATCH + conf_bool(SPLICE_PATCH) +#endif +#ifdef CONFIG_AUFS_LHASH_PATCH + conf_bool(LHASH_PATCH) +#endif +#ifdef CONFIG_AUFS_PUT_FILP_PATCH + conf_bool(PUT_FILP_PATCH) +#endif +#ifdef CONFIG_AUFS_BR_NFS + conf_bool(BR_NFS) +#endif +#ifdef CONFIG_AUFS_BR_NFS_V4 + conf_bool(BR_NFS_V4) +#endif +#ifdef CONFIG_AUFS_BR_XFS + conf_bool(BR_XFS) +#endif +#ifdef CONFIG_AUFS_FSYNC_SUPER_PATCH + conf_bool(FSYNC_SUPER_PATCH) +#endif +#ifdef CONFIG_AUFS_DENY_WRITE_ACCESS_PATCH + conf_bool(DENY_WRITE_ACCESS_PATCH) +#endif +#ifdef CONFIG_AUFS_KSIZE_PATCH + conf_bool(KSIZE_PATCH) +#endif +#ifdef CONFIG_AUFS_WORKAROUND_FUSE + conf_bool(WORKAROUND_FUSE) +#endif +#ifdef CONFIG_AUFS_GETATTR + conf_bool(GETATTR) +#endif +#ifdef CONFIG_AUFS_DEBUG + conf_bool(DEBUG) +#endif +#ifdef CONFIG_AUFS_MAGIC_SYSRQ + conf_bool(MAGIC_SYSRQ) +#endif +#ifdef CONFIG_AUFS_DEBUG_LOCK + conf_bool(DEBUG_LOCK) +#endif +#ifdef CONFIG_AUFS_COMPAT + conf_bool(COMPAT) +#endif +#ifdef CONFIG_AUFS_UNIONFS23_PATCH + conf_bool(UNIONFS23_PATCH) +#endif + ; +#undef conf_bool + + char *p = buf; + const char *end = buf + PAGE_SIZE; + + p += snprintf(p, end - p, "%s", opt); +#ifdef DbgUdbaRace + if (p < end) + p += snprintf(p, end - p, "DbgUdbaRace=%d\n", DbgUdbaRace); +#endif + if (p < end) + return p - buf; + else + return -EFBIG; +} + +static ssize_t stat_show(au_subsys_t *subsys, char *buf) +{ + //struct kset *kset = &au_subsys_to_kset(*subsys); + char *p = buf; + const char *end = buf + PAGE_SIZE; + int i; + + p += snprintf(p, end - p, "wkq max_busy:"); + for (i = 0; p < end && i < aufs_nwkq; i++) + p += snprintf(p, end - p, " %u", atomic_read(&au_wkq[i].max_busy)); + if (p < end) + p += snprintf(p, end - p, ", %u(generic)\n", + atomic_read(&au_wkq[aufs_nwkq].max_busy)); + + if (p < end) + return p - buf; + else + return -EFBIG; +} + +static ssize_t sbi_show(au_subsys_t *subsys, char *buf) +{ + //struct kset *kset = &au_subsys_to_kset(*subsys); + char *p = buf; + const char *end = buf + PAGE_SIZE; + struct au_sbinfo *sbinfo; + + /* this order is important */ + mutex_lock(&au_sbilist_mtx); + list_for_each_entry(sbinfo, &au_sbilist, si_list) + if (p < end) + p += snprintf(p, end - p, "%p\n", sbinfo); + mutex_unlock(&au_sbilist_mtx); + + if (p < end) + return p - buf; + else + return -EFBIG; +} + +#ifdef CONFIG_AUFS_DEBUG +static ssize_t debug_show(au_subsys_t *subsys, char *buf) +{ + //struct kset *kset = &au_subsys_to_kset(*subsys); + return sprintf(buf, "%d\n", au_debug_test()); +} + +static ssize_t debug_store(au_subsys_t *subsys, const char *buf, size_t sz) +{ + //struct kset *kset = &au_subsys_to_kset(*subsys); + + LKTRTrace("%.*s\n", (unsigned int)sz, buf); + + if (unlikely(!sz || (*buf != '0' && *buf != '1'))) + return -EOPNOTSUPP; + + if (*buf == '0') + au_debug_off(); + else if (*buf == '1') + au_debug_on(); + return sz; +} +#endif + +static struct subsys_attribute sysaufs_entry[] = { + [Config] = __ATTR_RO(config), + [Stat] = __ATTR_RO(stat), + [Sbi] = __ATTR_RO(sbi), +#ifdef CONFIG_AUFS_DEBUG + [Debug] = __ATTR(debug, S_IRUGO | S_IWUSR, debug_show, + debug_store) +#endif +}; +static struct attribute *sysaufs_attr[_Last + 1]; /* last NULL */ +static struct attribute_group sysaufs_attr_group = { + .attrs = sysaufs_attr, +}; + +/* ---------------------------------------------------------------------- */ + +typedef int (*sysaufs_sbi_sub_show_t)(struct seq_file *seq, + struct super_block *sb); + +static int sysaufs_sbi_xi(struct seq_file *seq, struct file *xf, int dlgt, + int print_path) +{ + int err; + struct kstat st; + + err = vfsub_getattr(xf->f_vfsmnt, xf->f_dentry, &st, dlgt); + if (!err) { + seq_printf(seq, "%llux%lu %lld", + st.blocks, st.blksize, (long long)st.size); + if (print_path) { + seq_putc(seq, ' '); + seq_path(seq, xf->f_vfsmnt, xf->f_dentry, au_esc_chars); + } + seq_putc(seq, '\n'); + } else + seq_printf(seq, "err %d\n", err); + + AuTraceErr(err); + return err; +} + +static int sysaufs_sbi_xino(struct seq_file *seq, struct super_block *sb) +{ + int err; + unsigned int mnt_flags; + unsigned char dlgt, xinodir; + aufs_bindex_t bend, bindex; + struct kstat st; + struct au_sbinfo *sbinfo; + struct file *xf; + + AuTraceEnter(); + + sbinfo = au_sbi(sb); + mnt_flags = au_mntflags(sb); + xinodir = !!au_opt_test(mnt_flags, XINODIR); + if (!au_opt_test(mnt_flags, XINO)) { +#ifdef CONFIG_AUFS_DEBUG + AuDebugOn(sbinfo->si_xib); + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) + AuDebugOn(au_sbr(sb, bindex)->br_xino.xi_file); +#endif + err = 0; + goto out; /* success */ + } + + dlgt = !!au_test_dlgt(mnt_flags); + err = sysaufs_sbi_xi(seq, sbinfo->si_xib, dlgt, xinodir); + + bend = au_sbend(sb); + for (bindex = 0; !err && bindex <= bend; bindex++) { + xf = au_sbr(sb, bindex)->br_xino.xi_file; + if (!xf) + continue; + seq_printf(seq, "%d: ", bindex); + err = vfsub_getattr(xf->f_vfsmnt, xf->f_dentry, &st, dlgt); + if (!err) + seq_printf(seq, "%d, %Lux%lu %Ld\n", + file_count(xf), st.blocks, st.blksize, + (long long)st.size); + else + seq_printf(seq, "err %d\n", err); + } + + out: + AuTraceErr(err); + return err; +} + +#ifdef CONFIG_AUFS_EXPORT +static int sysaufs_sbi_xigen(struct seq_file *seq, struct super_block *sb) +{ + int err; + unsigned int mnt_flags; + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + + err = 0; + sbinfo = au_sbi(sb); + mnt_flags = au_mntflags(sb); + if (au_opt_test_xino(mnt_flags)) + err = sysaufs_sbi_xi(seq, sbinfo->si_xigen, + !!au_opt_test(mnt_flags, DLGT), + !!au_opt_test(mnt_flags, XINODIR)); + + AuTraceErr(err); + return err; +} +#endif + +static int sysaufs_sbi_mntpnt1(struct seq_file *seq, struct super_block *sb) +{ + AuTraceEnter(); + + seq_path(seq, au_sbi(sb)->si_mnt, sb->s_root, au_esc_chars); + seq_putc(seq, '\n'); + + return 0; +} + +#define SysaufsBr_PREFIX "br" +static int sysaufs_sbi_br(struct seq_file *seq, struct super_block *sb, + aufs_bindex_t bindex) +{ + int err; + struct dentry *root; + struct au_branch *br; + + LKTRTrace("b%d\n", bindex); + + err = -ENOENT; + if (unlikely(au_sbend(sb) < bindex)) + goto out; + + err = 0; + root = sb->s_root; + di_read_lock_parent(root, !AuLock_IR); + br = au_sbr(sb, bindex); + seq_path(seq, br->br_mnt, au_h_dptr(root, bindex), au_esc_chars); + di_read_unlock(root, !AuLock_IR); + seq_printf(seq, "=%s\n", au_optstr_br_perm(br->br_perm)); + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static struct sysaufs_sbi_entry { + char *name; + sysaufs_sbi_sub_show_t show; +} sysaufs_sbi_entry[SysaufsSb_Last] = { + [SysaufsSb_XINO] = { + .name = "xino", + .show = sysaufs_sbi_xino + }, +#ifdef CONFIG_AUFS_EXPORT + [SysaufsSb_XIGEN] = { + .name = "xigen", + .show = sysaufs_sbi_xigen + }, +#endif + [SysaufsSb_MNTPNT1] = { + .name = "mntpnt1", + .show = sysaufs_sbi_mntpnt1 + } +}; + +static struct super_block *find_sb_lock(struct kobject *kobj) +{ + struct super_block *sb; + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + MtxMustLock(&au_sbilist_mtx); + + sb = NULL; + list_for_each_entry(sbinfo, &au_sbilist, si_list) { + if (&au_subsys_to_kset(sbinfo->si_sa->subsys).kobj != kobj) + continue; + sb = sbinfo->si_mnt->mnt_sb; + si_read_lock(sb, !AuLock_FLUSH); + break; + } + return sb; +} + +static struct seq_file *au_seq(char *p, ssize_t len) +{ + struct seq_file *seq; + + seq = kzalloc(sizeof(*seq), GFP_NOFS); + if (seq) { + //mutex_init(&seq.lock); + seq->buf = p; + seq->count = 0; + seq->size = len; + return seq; /* success */ + } + + seq = ERR_PTR(-ENOMEM); + AuTraceErrPtr(seq); + return seq; +} + +//todo: file size may exceed PAGE_SIZE +static ssize_t sysaufs_sbi_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + ssize_t err; + struct super_block *sb; + struct seq_file *seq; + char *name; + int i; + + LKTRTrace("%s/%s\n", kobject_name(kobj), attr->name); + + err = -ENOENT; + mutex_lock(&au_sbilist_mtx); + sb = find_sb_lock(kobj); + if (unlikely(!sb)) + goto out; + + seq = au_seq(buf, PAGE_SIZE); + err = PTR_ERR(seq); + if (IS_ERR(seq)) + goto out; + + name = (void *)attr->name; + for (i = 0; i < SysaufsSb_Last; i++) { + if (!strcmp(name, sysaufs_sbi_entry[i].name)) { + err = sysaufs_sbi_entry[i].show(seq, sb); + goto out_seq; + } + } + + if (!strncmp(name, SysaufsBr_PREFIX, sizeof(SysaufsBr_PREFIX) - 1)) { + name += sizeof(SysaufsBr_PREFIX) - 1; + err = sysaufs_sbi_br(seq, sb, simple_strtol(name, NULL, 10)); + goto out_seq; + } + BUG(); + + out_seq: + if (!err) { + err = seq->count; + /* sysfs limit */ + if (unlikely(err == PAGE_SIZE)) + err = -EFBIG; + } + kfree(seq); + out: + si_read_unlock(sb); + mutex_unlock(&au_sbilist_mtx); + AuTraceErr(err); + return err; +} + +static struct sysfs_ops sysaufs_sbi_ops = { + .show = sysaufs_sbi_show +}; + +static struct kobj_type sysaufs_sbi_ktype = { + //.release = dir_release, + .sysfs_ops = &sysaufs_sbi_ops +}; + +/* ---------------------------------------------------------------------- */ + +static void sysaufs_br_free(struct kref *ref) +{ + AuTraceEnter(); + //AuDbg("here\n"); + kfree(container_of(ref, struct sysaufs_br, ref)); +} + +struct sysaufs_br *sysaufs_br_alloc(void) +{ + struct sysaufs_br *sabr; + + AuTraceEnter(); + //AuDbg("here\n"); + + sabr = kcalloc(sizeof(*sabr), 1, GFP_NOFS); + if (sabr) + kref_init(&sabr->ref); + + AuTraceErrPtr(sabr); + return sabr; +} + +void sysaufs_br_get(struct au_branch *br) +{ + kref_get(&br->br_sabr->ref); +} + +void sysaufs_br_put(struct au_branch *br) +{ + kref_put(&br->br_sabr->ref, sysaufs_br_free); +} + +//todo: they may take a long time +void sysaufs_brs_del(struct super_block *sb) +{ + aufs_bindex_t bindex, bend; + struct sysaufs_sbinfo *sa; + struct sysaufs_br *sabr; + struct au_branch *br; + + AuTraceEnter(); + + sa = au_sbi(sb)->si_sa; + if (!sysaufs_brs || !sa->added) + return; + + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) { + br = au_sbr(sb, bindex); + sabr = br->br_sabr; + if (unlikely(!sabr->added)) + continue; + + sabr->added = 0; + sysfs_remove_file(&au_subsys_to_kset(sa->subsys).kobj, + &sabr->attr); + sysaufs_br_put(br); + /* for older sysfs */ + module_put(THIS_MODULE); + } +} + +void sysaufs_brs_add(struct super_block *sb) +{ + int err; + aufs_bindex_t bindex, bend; + struct sysaufs_br *sabr; + struct sysaufs_sbinfo *sa; + struct au_branch *br; + + AuTraceEnter(); + + sa = au_sbi(sb)->si_sa; + if (!sysaufs_brs || !sa->added) + return; + + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) { + br = au_sbr(sb, bindex); + sysaufs_br_get(br); + sabr = br->br_sabr; + AuDebugOn(sabr->added); + snprintf(sabr->name, sizeof(sabr->name), + SysaufsBr_PREFIX "%d", bindex); + sabr->attr.name = sabr->name; + sabr->attr.mode = 0444; + sabr->attr.owner = THIS_MODULE; + /* for older sysfs */ + BUG_ON(!try_module_get(THIS_MODULE)); + err = sysfs_create_file(&au_subsys_to_kset(sa->subsys).kobj, + &sabr->attr); + if (!err) + sabr->added = 1; + else + AuWarn("failed %s under sysfs(%d)\n", sabr->name, err); + } +} + +/* ---------------------------------------------------------------------- */ + +static void sysaufs_sbi_free(struct kref *ref) +{ + AuTraceEnter(); + //AuDbg("here\n"); + kfree(container_of(ref, struct sysaufs_sbinfo, ref)); +} + +int sysaufs_si_init(struct au_sbinfo *sbinfo) +{ + sbinfo->si_sa = kcalloc(sizeof(*sbinfo->si_sa), 1, GFP_NOFS); + if (sbinfo->si_sa) { + kref_init(&sbinfo->si_sa->ref); + return 0; + } + AuTraceErr(-ENOMEM); + return -ENOMEM; +} + +void sysaufs_sbi_get(struct super_block *sb) +{ + kref_get(&au_sbi(sb)->si_sa->ref); +} + +void sysaufs_sbi_put(struct super_block *sb) +{ + kref_put(&au_sbi(sb)->si_sa->ref, sysaufs_sbi_free); +} + +void sysaufs_sbi_add(struct super_block *sb) +{ + int err, i; + const char *name; + struct au_sbinfo *sbinfo = au_sbi(sb); + struct sysaufs_sbinfo *sa = sbinfo->si_sa; + + AuTraceEnter(); + MtxMustLock(&au_sbilist_mtx); + + err = kobject_set_name(&au_subsys_to_kset(sa->subsys).kobj, + "sbi_%p", sbinfo); + if (unlikely(err)) + goto out; + name = kobject_name(&au_subsys_to_kset(sa->subsys).kobj); + subsys_get(&aufs_subsys); + kobj_set_kset_s(&au_subsys_to_kset(sa->subsys), aufs_subsys); + //au_subsys_to_kset(sa->subsys).kobj.ktype = &sysaufs_ktype; + err = subsystem_register(&sa->subsys); + if (unlikely(err)) + goto out_put; +#if 1 + if (au_subsys_to_kset(sa->subsys).kobj.ktype) + sysaufs_sbi_ktype = *(au_subsys_to_kset(sa->subsys).kobj.ktype); + sysaufs_sbi_ktype.sysfs_ops = &sysaufs_sbi_ops; + au_subsys_to_kset(sa->subsys).kobj.ktype = &sysaufs_sbi_ktype; +#endif + + for (i = 0; i < SysaufsSb_Last; i++) { + sa->attr[i].name = sysaufs_sbi_entry[i].name; + sa->attr[i].mode = S_IRUGO; + sa->attr[i].owner = THIS_MODULE; + /* for older sysfs */ + BUG_ON(!try_module_get(THIS_MODULE)); + err = sysfs_create_file(&au_subsys_to_kset(sa->subsys).kobj, + sa->attr + i); + if (unlikely(err)) + goto out_reg; + } + + /* compatibility for some aufs scripts */ + strcpy(sa->compat_name, "si_"); + strcat(sa->compat_name, name + 4); + err = sysfs_create_link(&au_subsys_to_kset(aufs_subsys).kobj, + &au_subsys_to_kset(sa->subsys).kobj, + sa->compat_name); + if (unlikely(err)) { + AuWarn("failed adding %s for compatibility(%d), ignored.\n", + sa->compat_name, err); + err = 0; + } + + sa->added = 1; + sysaufs_brs_add(sb); + goto out; /* success */ + + out_reg: + for (; i >= 0; i--) { + sysfs_remove_file(&au_subsys_to_kset(sa->subsys).kobj, + sa->attr + i); + /* for older sysfs */ + module_put(THIS_MODULE); + } + subsystem_unregister(&sa->subsys); + out_put: + subsys_put(&aufs_subsys); + out: + if (!err) + sysaufs_sbi_get(sb); + else + AuWarn("failed adding sysfs (%d)\n", err); +} + +void sysaufs_sbi_del(struct super_block *sb) +{ + struct au_sbinfo *sbinfo = au_sbi(sb); + struct sysaufs_sbinfo *sa = sbinfo->si_sa; + int i; + + AuTraceEnter(); + MtxMustLock(&au_sbilist_mtx); + + sysaufs_brs_del(sb); + for (i = 0; i < SysaufsSb_Last; i++) { + sysfs_remove_file(&au_subsys_to_kset(sa->subsys).kobj, + sa->attr + i); + /* for older sysfs */ + module_put(THIS_MODULE); + } + sa->added = 0; + sysfs_remove_link(&au_subsys_to_kset(aufs_subsys).kobj, + sa->compat_name); + subsystem_unregister(&sa->subsys); + subsys_put(&aufs_subsys); + sysaufs_sbi_put(sb); +} + +/* ---------------------------------------------------------------------- */ + +int __init sysaufs_init(void) +{ + int err, i; + + for (i = 0; i < _Last; i++) + sysaufs_attr[i] = &sysaufs_entry[i].attr; + + subsys_get(&fs_subsys); + kobj_set_kset_s(&au_subsys_to_kset(aufs_subsys), fs_subsys); + err = subsystem_register(&aufs_subsys); + if (unlikely(err)) + goto out_put; + err = sysfs_create_group(&au_subsys_to_kset(aufs_subsys).kobj, + &sysaufs_attr_group); + if (!err) + return err; /* success */ + + subsystem_unregister(&aufs_subsys); + out_put: + subsys_put(&fs_subsys); + AuTraceErr(err); + return err; +} + +void sysaufs_fin(void) +{ + AuDebugOn(!list_empty(&au_sbilist)); + sysfs_remove_group(&au_subsys_to_kset(aufs_subsys).kobj, + &sysaufs_attr_group); + subsystem_unregister(&aufs_subsys); + subsys_put(&fs_subsys); +} diff --git a/fs/aufs/sysaufs.h b/fs/aufs/sysaufs.h new file mode 100644 index 0000000..ef96716 --- /dev/null +++ b/fs/aufs/sysaufs.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sysfs interface + * + * $Id: sysaufs.h,v 1.21 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __SYSAUFS_H__ +#define __SYSAUFS_H__ + +#ifdef __KERNEL__ + +#include +#include +#include + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) +typedef struct kset au_subsys_t; +#define au_subsys_to_kset(subsys) (subsys) +#else +typedef struct subsystem au_subsys_t; +#define au_subsys_to_kset(subsys) ((subsys).kset) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) +static inline struct kset *subsys_get(struct kset *s) +{ + return kset_get(s); +} + +static inline void subsys_put(struct kset *s) +{ + kset_put(s); +} +#endif + +/* ---------------------------------------------------------------------- */ + +/* entries under sysfs per super block */ +enum { + SysaufsSb_XINO, +#ifdef CONFIG_AUFS_EXPORT + SysaufsSb_XIGEN, +#endif + SysaufsSb_MNTPNT1, +#if 0 + SysaufsSb_PLINK, + SysaufsSb_files, +#endif + SysaufsSb_Last +}; + +struct sysaufs_sbinfo { +#ifdef CONFIG_AUFS_SYSAUFS + //todo: try kset directly + au_subsys_t subsys; + struct attribute attr[SysaufsSb_Last]; + struct kref ref; + int added; + char compat_name[32]; +#endif +}; + +/* 'brN' entry under sysfs per super block */ +struct sysaufs_br { +#ifdef CONFIG_AUFS_SYSAUFS + char name[8]; + struct attribute attr; + struct kref ref; + int added; +#endif +}; + +/* ---------------------------------------------------------------------- */ + +struct au_branch; +struct au_sbinfo; +#ifdef CONFIG_AUFS_SYSAUFS +extern struct mutex au_sbilist_mtx; +extern struct list_head au_sbilist; + +static inline void au_sbilist_lock(void) +{ + mutex_lock(&au_sbilist_mtx); +} + +static inline void au_sbilist_unlock(void) +{ + mutex_unlock(&au_sbilist_mtx); +} + +static inline void au_sbilist_del(struct au_sbinfo *sbinfo) +{ + list_del(&sbinfo->si_list); +} + +static inline void au_sbilist_add(struct au_sbinfo *sbinfo) +{ + /* the order in this list is important */ + list_add_tail(&sbinfo->si_list, &au_sbilist); +} + +struct sysaufs_br *sysaufs_br_alloc(void); +void sysaufs_br_get(struct au_branch *br); +void sysaufs_br_put(struct au_branch *br); +void sysaufs_brs_add(struct super_block *sb); +void sysaufs_brs_del(struct super_block *sb); +int sysaufs_si_init(struct au_sbinfo *sbinfo); +void sysaufs_sbi_get(struct super_block *sb); +void sysaufs_sbi_put(struct super_block *sb); +void sysaufs_sbi_add(struct super_block *sb); +void sysaufs_sbi_del(struct super_block *sb); +int __init sysaufs_init(void); +void sysaufs_fin(void); + +#else + +#define au_sbilist_lock() do {} while (0) +#define au_sbilist_unlock() do {} while (0) + +static inline void au_sbilist_del(struct au_sbinfo *sbinfo) +{ + /* empty */ +} + +static inline void au_sbilist_add(struct au_sbinfo *sbinfo) +{ + /* empty */ +} + +static inline struct sysaufs_br *sysaufs_br_alloc(void) +{ + return (void *)-1; //todo: poison +} + +static inline void sysaufs_br_get(struct au_branch *br) +{ + /* nothing */ +} + +static inline void sysaufs_br_put(struct au_branch *br) +{ + /* nothing */ +} + +static inline void sysaufs_brs_add(struct super_block *sb) +{ + /* nothing */ +} + +static inline void sysaufs_brs_del(struct super_block *sb) +{ + /* nothing */ +} + +static inline int sysaufs_si_init(struct au_sbinfo *sbinfo) +{ + return 0; +} + +static inline void sysaufs_sbi_get(struct super_block *sb) +{ + /* nothing */ +} + +static inline void sysaufs_sbi_put(struct super_block *sb) +{ + /* nothing */ +} + +static inline void sysaufs_sbi_add(struct super_block *sb) +{ + /* nothing */ +} + +static inline void sysaufs_sbi_del(struct super_block *sb) +{ + /* nothing */ +} + +static inline int sysaufs_init(void) +{ + sysaufs_brs = 0; + return 0; +} + +#define sysaufs_fin() do {} while (0) + +#endif /* CONFIG_AUFS_SYSAUFS */ + +#endif /* __KERNEL__ */ +#endif /* __SYSAUFS_H__ */ diff --git a/fs/aufs/sysrq.c b/fs/aufs/sysrq.c new file mode 100644 index 0000000..e7d3f53 --- /dev/null +++ b/fs/aufs/sysrq.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * magic sysrq hanlder + * + * $Id: sysrq.c,v 1.11 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include +//#include +#include + +#include "aufs.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#error mis-configuraion or Makefile +#elif !defined(pr_warning) +#define pr_warning(fmt, arg...) printk(KERN_WARNING fmt, ##arg) +#endif + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_DEBUG_LOCK +struct au_dbg_lock { + struct list_head list; + union { + struct super_block *sb; + struct dentry *dentry; + struct inode *inode; + void *any; + }; + int flags; + unsigned int lsc; + pid_t pid; +}; + +static void au_dbg_reg(struct au_splhead *spl, void *any, int flags, + unsigned int lsc) +{ + struct au_dbg_lock *p = kmalloc(sizeof(*p), GFP_NOFS); + + if (p) { + p->any = any; + p->flags = flags; + p->lsc = lsc; + p->pid = current->pid; + spin_lock(&spl->spin); + list_add(&p->list, &spl->head); + spin_unlock(&spl->spin); + } + WARN_ON(!p); +} + +static void au_dbg_unreg(struct au_splhead *spl, void *any, int flags) +{ + struct au_dbg_lock *p, *tmp, *found; + struct list_head *head = &spl->head; + const pid_t pid = current->pid; + + found = NULL; + spin_lock(&spl->spin); + list_for_each_entry_safe(p, tmp, head, list) + if (p->any == any && p->pid == pid) { + list_del(&p->list); + found = p; + break; + } + spin_unlock(&spl->spin); + kfree(found); + WARN_ON(!found); +} + +/* ---------------------------------------------------------------------- */ + +void au_dbg_locking_si_reg(struct super_block *sb, int flags) +{ + au_dbg_reg(au_sbi(sb)->si_dbg_lock + AuDbgLock_SI_LOCKING, sb, flags, + -1); +} + +void au_dbg_locking_si_unreg(struct super_block *sb, int flags) +{ + au_dbg_unreg(au_sbi(sb)->si_dbg_lock + AuDbgLock_SI_LOCKING, sb, flags); +} + +void au_dbg_locked_si_reg(struct super_block *sb, int flags) +{ + au_dbg_reg(au_sbi(sb)->si_dbg_lock + AuDbgLock_SI_LOCKED, sb, flags, + -1); +} + +void au_dbg_locked_si_unreg(struct super_block *sb, int flags) +{ + au_dbg_unreg(au_sbi(sb)->si_dbg_lock + AuDbgLock_SI_LOCKED, sb, flags); +} + +void au_dbg_locking_di_reg(struct dentry *d, int flags, unsigned int lsc) +{ + au_dbg_reg(au_sbi(d->d_sb)->si_dbg_lock + AuDbgLock_DI_LOCKING, d, + flags, lsc); +} + +void au_dbg_locking_di_unreg(struct dentry *d, int flags) +{ + au_dbg_unreg(au_sbi(d->d_sb)->si_dbg_lock + AuDbgLock_DI_LOCKING, d, + flags); +} + +void au_dbg_locked_di_reg(struct dentry *d, int flags, unsigned int lsc) +{ + au_dbg_reg(au_sbi(d->d_sb)->si_dbg_lock + AuDbgLock_DI_LOCKED, d, flags, + lsc); +} + +void au_dbg_locked_di_unreg(struct dentry *d, int flags) +{ + au_dbg_unreg(au_sbi(d->d_sb)->si_dbg_lock + AuDbgLock_DI_LOCKED, d, + flags); +} + +void au_dbg_locking_ii_reg(struct inode *i, int flags, unsigned int lsc) +{ + au_dbg_reg(au_sbi(i->i_sb)->si_dbg_lock + AuDbgLock_II_LOCKING, i, + flags, lsc); +} + +void au_dbg_locking_ii_unreg(struct inode *i, int flags) +{ + au_dbg_unreg(au_sbi(i->i_sb)->si_dbg_lock + AuDbgLock_II_LOCKING, i, + flags); +} + +void au_dbg_locked_ii_reg(struct inode *i, int flags, unsigned int lsc) +{ + au_dbg_reg(au_sbi(i->i_sb)->si_dbg_lock + AuDbgLock_II_LOCKED, i, flags, + lsc); +} + +void au_dbg_locked_ii_unreg(struct inode *i, int flags) +{ + au_dbg_unreg(au_sbi(i->i_sb)->si_dbg_lock + AuDbgLock_II_LOCKED, i, + flags); +} +#endif /* CONFIG_AUFS_DEBUG_LOCK */ + +/* ---------------------------------------------------------------------- */ + +static void sysrq_sb(struct super_block *sb) +{ + char *plevel; + struct inode *i; + struct au_sbinfo *sbinfo; + struct file *file; + + plevel = au_plevel; + au_plevel = KERN_WARNING; + au_debug_on(); + + sbinfo = au_sbi(sb); + pr_warning("si=%p\n", sbinfo); + pr_warning(AUFS_NAME ": superblock\n"); + au_dpri_sb(sb); + pr_warning(AUFS_NAME ": root dentry\n"); + au_dpri_dentry(sb->s_root); + pr_warning(AUFS_NAME ": root inode\n"); + au_dpri_inode(sb->s_root->d_inode); + pr_warning(AUFS_NAME ": isolated inode\n"); + list_for_each_entry(i, &sb->s_inodes, i_sb_list) + if (list_empty(&i->i_dentry)) + au_dpri_inode(i); + pr_warning(AUFS_NAME ": files\n"); + list_for_each_entry(file, &sb->s_files, f_u.fu_list) + if (au_test_aufs_file(file)) + au_dpri_file(file); + +#ifdef CONFIG_AUFS_DEBUG_LOCK + { + struct au_dbg_lock *p; + struct list_head *head; + + pr_warning(AUFS_NAME ": locking si\n"); + head = &sbinfo->si_dbg_lock[AuDbgLock_SI_LOCKING].head; + list_for_each_entry(p, head, list) + pr_warning("pid: %d, 0x%x\n", p->pid, p->flags); + pr_warning(AUFS_NAME ": locked si\n"); + head = &sbinfo->si_dbg_lock[AuDbgLock_SI_LOCKED].head; + list_for_each_entry(p, head, list) + pr_warning("pid: %d, 0x%x\n", p->pid, p->flags); + pr_warning(AUFS_NAME ": locking di\n"); + head = &sbinfo->si_dbg_lock[AuDbgLock_DI_LOCKING].head; + list_for_each_entry(p, head, list) { + pr_warning("pid: %d, 0x%x, %d\n", + p->pid, p->flags, p->lsc); + au_dpri_dentry(p->dentry); + } + pr_warning(AUFS_NAME ": locked di\n"); + head = &sbinfo->si_dbg_lock[AuDbgLock_DI_LOCKED].head; + list_for_each_entry(p, head, list) { + pr_warning("pid: %d, 0x%x, %d\n", + p->pid, p->flags, p->lsc); + au_dpri_dentry(p->dentry); + } + pr_warning(AUFS_NAME ": locking ii\n"); + head = &sbinfo->si_dbg_lock[AuDbgLock_II_LOCKING].head; + list_for_each_entry(p, head, list) { + pr_warning("pid: %d, %d\n", p->pid, p->lsc); + au_dpri_inode(p->inode); + } + pr_warning(AUFS_NAME ": locked ii\n"); + head = &sbinfo->si_dbg_lock[AuDbgLock_II_LOCKED].head; + list_for_each_entry(p, head, list) { + pr_warning("pid: %d, %d\n", p->pid, p->lsc); + au_dpri_inode(p->inode); + } + } +#endif + + au_plevel = plevel; + au_debug_off(); +} + +/* ---------------------------------------------------------------------- */ + +/* module parameter */ +static char *aufs_sysrq_key = "a"; +module_param_named(sysrq, aufs_sysrq_key, charp, S_IRUGO); +MODULE_PARM_DESC(sysrq, "MagicSysRq key for " AUFS_NAME); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +static void au_sysrq(int key, struct tty_struct *tty) +#else +static void au_sysrq(int key, struct pt_regs *regs, struct tty_struct *tty) +#endif +{ + struct au_sbinfo *sbinfo; + +#if 0 + if (au_debug_test()) + au_debug_off(); + else + au_debug_on(); +#endif + + //mutex_lock(&au_sbilist_mtx); + list_for_each_entry(sbinfo, &au_sbilist, si_list) + sysrq_sb(sbinfo->si_mnt->mnt_sb); + //mutex_unlock(&au_sbilist_mtx); +} + +static struct sysrq_key_op au_sysrq_op = { + .handler = au_sysrq, + .help_msg = "Aufs", + .action_msg = "Aufs", + .enable_mask = SYSRQ_ENABLE_DUMP //?? +}; + +/* ---------------------------------------------------------------------- */ + +int __init au_sysrq_init(void) +{ + int err; + char key; + + err = -1; + key = *aufs_sysrq_key; + if ('a' <= key && key <= 'z') + err = register_sysrq_key(key, &au_sysrq_op); + if (unlikely(err)) + AuErr("err %d, sysrq=%c\n", err, key); + return err; +} + +void au_sysrq_fin(void) +{ + int err; + err = unregister_sysrq_key(*aufs_sysrq_key, &au_sysrq_op); + if (unlikely(err)) + AuErr("err %d (ignored)\n", err); +} diff --git a/fs/aufs/vdir.c b/fs/aufs/vdir.c new file mode 100644 index 0000000..d3a470e --- /dev/null +++ b/fs/aufs/vdir.c @@ -0,0 +1,941 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * virtual or vertical directory + * + * $Id: vdir.c,v 1.48 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +static int calc_size(int namelen) +{ + int sz; + + sz = sizeof(struct au_vdir_de) + namelen; + if (sizeof(ino_t) == sizeof(long)) { + const int mask = sizeof(ino_t) - 1; + if (sz & mask) { + sz += sizeof(ino_t); + sz &= ~mask; + } + } + + AuDebugOn(sz % sizeof(ino_t)); + return sz; +} + +static int set_deblk_end(union au_vdir_deblk_p *p, + union au_vdir_deblk_p *deblk_end) +{ + if (calc_size(0) <= deblk_end->p - p->p) { + p->de->de_str.len = 0; + /* smp_mb(); */ + return 0; + } + return -1; /* error */ +} + +/* returns true or false */ +static int is_deblk_end(union au_vdir_deblk_p *p, + union au_vdir_deblk_p *deblk_end) +{ + if (calc_size(0) <= deblk_end->p - p->p) + return !p->de->de_str.len; + return 1; +} + +static au_vdir_deblk_t *last_deblk(struct au_vdir *vdir) +{ + return vdir->vd_deblk[vdir->vd_nblk - 1]; +} + +void au_nhash_init(struct au_nhash *nhash) +{ + int i; + for (i = 0; i < AuSize_NHASH; i++) + INIT_HLIST_HEAD(nhash->heads + i); +} + +struct au_nhash *au_nhash_new(gfp_t gfp) +{ + struct au_nhash *nhash; + + nhash = kmalloc(sizeof(*nhash), gfp); + if (nhash) { + au_nhash_init(nhash); + return nhash; + } + return ERR_PTR(-ENOMEM); +} + +void au_nhash_del(struct au_nhash *nhash) +{ + au_nhash_fin(nhash); + kfree(nhash); +} + +void au_nhash_move(struct au_nhash *dst, struct au_nhash *src) +{ + int i; + + AuTraceEnter(); + + *dst = *src; + for (i = 0; i < AuSize_NHASH; i++) { + struct hlist_head *h; + h = dst->heads + i; + if (h->first) + h->first->pprev = &h->first; + INIT_HLIST_HEAD(src->heads + i); + } + /* smp_mb(); */ +} + +/* ---------------------------------------------------------------------- */ + +void au_nhash_fin(struct au_nhash *whlist) +{ + int i; + struct hlist_head *head; + struct au_vdir_wh *tpos; + struct hlist_node *pos, *n; + + AuTraceEnter(); + + for (i = 0; i < AuSize_NHASH; i++) { + head = whlist->heads + i; + hlist_for_each_entry_safe(tpos, pos, n, head, wh_hash) { + /* hlist_del(pos); */ + kfree(tpos); + } + } +} + +int au_nhash_test_longer_wh(struct au_nhash *whlist, aufs_bindex_t btgt, + int limit) +{ + int n, i; + struct hlist_head *head; + struct au_vdir_wh *tpos; + struct hlist_node *pos; + + LKTRTrace("limit %d\n", limit); + + n = 0; + for (i = 0; i < AuSize_NHASH; i++) { + head = whlist->heads + i; + hlist_for_each_entry(tpos, pos, head, wh_hash) + if (tpos->wh_bindex == btgt && ++n > limit) + return 1; + } + return 0; +} + +static unsigned int au_name_hash(const unsigned char *name, unsigned int len) +{ + return full_name_hash(name, len) % AuSize_NHASH; +} + +/* returns found(true) or not */ +int au_nhash_test_known_wh(struct au_nhash *whlist, char *name, int namelen) +{ + struct hlist_head *head; + struct au_vdir_wh *tpos; + struct hlist_node *pos; + struct au_vdir_destr *str; + + LKTRTrace("%.*s\n", namelen, name); + + head = whlist->heads + au_name_hash(name, namelen); + hlist_for_each_entry(tpos, pos, head, wh_hash) { + str = &tpos->wh_str; + LKTRTrace("%.*s\n", str->len, str->name); + if (str->len == namelen && !memcmp(str->name, name, namelen)) + return 1; + } + return 0; +} + +int au_nhash_append_wh(struct au_nhash *whlist, char *name, int namelen, + ino_t ino, unsigned int d_type, aufs_bindex_t bindex, + unsigned char shwh) +{ + int err; + struct au_vdir_destr *str; + struct au_vdir_wh *wh; + + LKTRTrace("%.*s\n", namelen, name); + + err = -ENOMEM; + wh = kmalloc(sizeof(*wh) + namelen, GFP_NOFS); + if (unlikely(!wh)) + goto out; + err = 0; + wh->wh_bindex = bindex; + if (shwh) + au_shwh_init_wh(wh, ino, d_type); + str = &wh->wh_str; + str->len = namelen; + memcpy(str->name, name, namelen); + hlist_add_head(&wh->wh_hash, + whlist->heads + au_name_hash(name, namelen)); + /* smp_mb(); */ + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +void au_vdir_free(struct au_vdir *vdir) +{ + au_vdir_deblk_t **deblk; + + AuTraceEnter(); + + deblk = vdir->vd_deblk; + while (vdir->vd_nblk--) { + kfree(*deblk); + deblk++; + } + kfree(vdir->vd_deblk); + au_cache_free_vdir(vdir); +} + +static int append_deblk(struct au_vdir *vdir) +{ + int err, sz, i; + au_vdir_deblk_t **o; + union au_vdir_deblk_p p, deblk_end; + + AuTraceEnter(); + + err = -ENOMEM; + sz = sizeof(*o) * vdir->vd_nblk; + o = au_kzrealloc(vdir->vd_deblk, sz, sz + sizeof(*o), GFP_NOFS); + if (unlikely(!o)) + goto out; + vdir->vd_deblk = o; + p.deblk = kmalloc(sizeof(*p.deblk), GFP_NOFS); + if (p.deblk) { + i = vdir->vd_nblk++; + vdir->vd_deblk[i] = p.deblk; + vdir->vd_last.i = i; + vdir->vd_last.p.p = p.p; + deblk_end.deblk = p.deblk + 1; + err = set_deblk_end(&p, &deblk_end); + AuDebugOn(err); + } + + out: + AuTraceErr(err); + return err; +} + +static struct au_vdir *alloc_vdir(void) +{ + struct au_vdir *vdir; + int err; + + AuTraceEnter(); + + err = -ENOMEM; + vdir = au_cache_alloc_vdir(); + if (unlikely(!vdir)) + goto out; + vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_NOFS); + if (unlikely(!vdir->vd_deblk)) + goto out_free; + + vdir->vd_nblk = 0; + vdir->vd_version = 0; + vdir->vd_jiffy = 0; + err = append_deblk(vdir); + if (!err) + return vdir; /* success */ + + kfree(vdir->vd_deblk); + + out_free: + au_cache_free_vdir(vdir); + out: + vdir = ERR_PTR(err); + AuTraceErrPtr(vdir); + return vdir; +} + +static int reinit_vdir(struct au_vdir *vdir) +{ + int err; + union au_vdir_deblk_p p, deblk_end; + + AuTraceEnter(); + + while (vdir->vd_nblk > 1) { + kfree(vdir->vd_deblk[vdir->vd_nblk - 1]); + vdir->vd_deblk[vdir->vd_nblk - 1] = NULL; + vdir->vd_nblk--; + } + p.deblk = vdir->vd_deblk[0]; + deblk_end.deblk = p.deblk + 1; + err = set_deblk_end(&p, &deblk_end); + AuDebugOn(err); + vdir->vd_version = 0; + vdir->vd_jiffy = 0; + vdir->vd_last.i = 0; + vdir->vd_last.p.deblk = vdir->vd_deblk[0]; + /* smp_mb(); */ + return err; +} + +/* ---------------------------------------------------------------------- */ + +static void free_dehlist(struct au_nhash *dehlist) +{ + int i; + struct hlist_head *head; + struct au_vdir_dehstr *tpos; + struct hlist_node *pos, *n; + + AuTraceEnter(); + + for (i = 0; i < AuSize_NHASH; i++) { + head = dehlist->heads + i; + hlist_for_each_entry_safe(tpos, pos, n, head, hash) { + /* hlist_del(pos); */ + au_cache_free_dehstr(tpos); + } + } +} + +/* returns found(true) or not */ +static int test_known(struct au_nhash *delist, char *name, int namelen) +{ + struct hlist_head *head; + struct au_vdir_dehstr *tpos; + struct hlist_node *pos; + struct au_vdir_destr *str; + + LKTRTrace("%.*s\n", namelen, name); + + head = delist->heads + au_name_hash(name, namelen); + hlist_for_each_entry(tpos, pos, head, hash) { + str = tpos->str; + LKTRTrace("%.*s\n", str->len, str->name); + if (str->len == namelen && !memcmp(str->name, name, namelen)) + return 1; + } + return 0; + +} + +static int append_de(struct au_vdir *vdir, char *name, int namelen, ino_t ino, + unsigned int d_type, struct au_nhash *delist) +{ + int err, sz; + union au_vdir_deblk_p p, *room, deblk_end; + struct au_vdir_dehstr *dehstr; + + LKTRTrace("%.*s %d, i%lu, dt%u\n", + namelen, name, namelen, (unsigned long)ino, d_type); + + p.deblk = last_deblk(vdir); + deblk_end.deblk = p.deblk + 1; + room = &vdir->vd_last.p; + AuDebugOn(room->p < p.p || deblk_end.p <= room->p + || !is_deblk_end(room, &deblk_end)); + + sz = calc_size(namelen); + if (unlikely(sz > deblk_end.p - room->p)) { + err = append_deblk(vdir); + if (unlikely(err)) + goto out; + p.deblk = last_deblk(vdir); + deblk_end.deblk = p.deblk + 1; + /* smp_mb(); */ + AuDebugOn(room->p != p.p); + } + + err = -ENOMEM; + dehstr = au_cache_alloc_dehstr(); + if (unlikely(!dehstr)) + goto out; + dehstr->str = &room->de->de_str; + hlist_add_head(&dehstr->hash, + delist->heads + au_name_hash(name, namelen)); + + room->de->de_ino = ino; + room->de->de_type = d_type; + room->de->de_str.len = namelen; + memcpy(room->de->de_str.name, name, namelen); + + err = 0; + room->p += sz; + if (unlikely(set_deblk_end(room, &deblk_end))) + err = append_deblk(vdir); + /* smp_mb(); */ + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + unsigned int d_type, ino_t *ino) +{ + int err; + struct au_xino_entry xinoe; + struct mutex *mtx; + const int isdir = (d_type == DT_DIR); + + /* prevent hardlinks from race condition */ + mtx = NULL; + if (!isdir) { + mtx = &au_sbr(sb, bindex)->br_xino.xi_nondir_mtx; + mutex_lock(mtx); + } + err = au_xino_read(sb, bindex, h_ino, &xinoe); + if (unlikely(err)) + goto out; + + if (!xinoe.ino) { + err = -EIO; + xinoe.ino = au_xino_new_ino(sb); + if (unlikely(!xinoe.ino)) + goto out; + +#if 0 /* reserved for future use */ + struct inode *h_inode; + xinoe.h_gen = AuXino_INVALID_HGEN; + h_inode = ilookup(au_sbr_sb(sb, bindex), h_ino); + if (h_inode) { + if (!is_bad_inode(h_inode)) { + xinoe.h_gen = h_inode->i_generation; + WARN_ON(xinoe.h_gen == AuXino_INVALID_HGEN); + } + iput(h_inode); + } +#endif + err = au_xino_write(sb, bindex, h_ino, &xinoe); + if (unlikely(err)) + goto out; + } + + *ino = xinoe.ino; + + out: + if (!isdir) + mutex_unlock(mtx); + AuTraceErr(err); + return err; +} + +static int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + unsigned int d_type, ino_t *ino) +{ +#ifdef CONFIG_AUFS_SHWH + return au_ino(sb, bindex, h_ino, d_type, ino); +#else + return 0; +#endif +} + +#define AuFillVdir_CALLED 1 +#define AuFillVdir_SHWH (1 << 1) +#define au_ftest_fillvdir(flags, name) ((flags) & AuFillVdir_##name) +#define au_fset_fillvdir(flags, name) { (flags) |= AuFillVdir_##name; } +#define au_fclr_fillvdir(flags, name) { (flags) &= ~AuFillVdir_##name; } +#ifndef CONFIG_AUFS_SHWH +#undef AuFillVdir_SHWH +#define AuFillVdir_SHWH 0 +#endif + +struct fillvdir_arg { + struct file *file; + struct au_vdir *vdir; + struct au_nhash *delist; + struct au_nhash *whlist; + aufs_bindex_t bindex; + unsigned int flags; + int err; +}; + +static int fillvdir(void *__arg, const char *__name, int namelen, loff_t offset, + au_filldir_ino_t h_ino, unsigned int d_type) +{ + struct fillvdir_arg *arg = __arg; + char *name = (void *)__name; + aufs_bindex_t bindex, bend; + struct super_block *sb; + ino_t ino; + + LKTRTrace("%.*s, namelen %d, i%llu, dt%u\n", + namelen, name, namelen, (unsigned long long)h_ino, d_type); + + sb = arg->file->f_dentry->d_sb; + bend = arg->bindex; + arg->err = 0; + au_fset_fillvdir(arg->flags, CALLED); + /* smp_mb(); */ + if (namelen <= AUFS_WH_PFX_LEN + || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) { + for (bindex = 0; bindex < bend; bindex++) + if (test_known(arg->delist + bindex, name, namelen) + || au_nhash_test_known_wh(arg->whlist + bindex, + name, namelen)) + goto out; /* already exists or whiteouted */ + + ino = 1; /* why does gcc warns? */ + arg->err = au_ino(sb, bend, h_ino, d_type, &ino); + if (!arg->err) + arg->err = append_de(arg->vdir, name, namelen, ino, + d_type, arg->delist + bend); + } else { + name += AUFS_WH_PFX_LEN; + namelen -= AUFS_WH_PFX_LEN; + for (bindex = 0; bindex < bend; bindex++) + if (au_nhash_test_known_wh(arg->whlist + bend, name, + namelen)) + goto out; /* already whiteouted */ + + ino = 1; /* dummy */ + if (au_ftest_fillvdir(arg->flags, SHWH)) + arg->err = au_wh_ino(sb, bend, h_ino, d_type, &ino); + if (!arg->err) + arg->err = au_nhash_append_wh + (arg->whlist + bend, name, namelen, ino, d_type, + bend, au_ftest_fillvdir(arg->flags, SHWH)); + } + + out: + if (!arg->err) + arg->vdir->vd_jiffy = jiffies; + /* smp_mb(); */ + AuTraceErr(arg->err); + return arg->err; +} + +static int au_handle_shwh(struct super_block *sb, struct au_vdir *vdir, + aufs_bindex_t bstart, aufs_bindex_t bend, + struct au_nhash *_whlist, struct au_nhash *_delist) +{ +#ifdef CONFIG_AUFS_SHWH + int err, i; + struct hlist_head *head; + struct au_vdir_wh *tpos; + struct hlist_node *pos, *n; + char *p, *o; + struct au_nhash *whlist, *delist; + struct au_vdir_destr *destr; + aufs_bindex_t bindex; + + AuTraceEnter(); + AuDebugOn(!au_opt_test(au_mntflags(sb), SHWH)); + + err = -ENOMEM; + o = p = __getname(); + if (unlikely(!p)) + goto out; + + err = 0; + memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN); + p += AUFS_WH_PFX_LEN; + for (bindex = bstart; !err && bindex <= bend; bindex++) { + whlist = _whlist + bindex; + delist = _delist + bindex; + + for (i = 0; i < AuSize_NHASH; i++) { + head = whlist->heads + i; + hlist_for_each_entry_safe(tpos, pos, n, head, wh_hash) { + destr = &tpos->wh_str; + memcpy(p, destr->name, destr->len); + err = append_de(vdir, o, + destr->len + AUFS_WH_PFX_LEN, + tpos->wh_ino, tpos->wh_type, + delist); + if (unlikely(err)) + break; + } + } + } + + __putname(o); + + out: + AuTraceErr(err); + return err; +#else + return 0; +#endif +} + +static int au_do_read_vdir(struct fillvdir_arg *arg) +{ + int err; + unsigned int mnt_flags; + loff_t offset; + aufs_bindex_t bend, bindex, bstart; + unsigned char dlgt, shwh; + struct super_block *sb; + struct file *hf; + + AuTraceEnter(); + + err = -ENOMEM; + bend = au_fbend(arg->file); + arg->delist = kmalloc(sizeof(*arg->delist) * (bend + 1), GFP_NOFS); + if (unlikely(!arg->delist)) + goto out; + arg->whlist = kmalloc(sizeof(*arg->whlist) * (bend + 1), GFP_NOFS); + if (unlikely(!arg->whlist)) + goto out_delist; + err = 0; + for (bindex = 0; bindex <= bend; bindex++) { + au_nhash_init(arg->delist + bindex); + au_nhash_init(arg->whlist + bindex); + } + + sb = arg->file->f_dentry->d_sb; + mnt_flags = au_mntflags(sb); + dlgt = !!au_test_dlgt(mnt_flags); + arg->flags = 0; + shwh = 0; + if (au_opt_test(mnt_flags, SHWH)) { + shwh = 1; + au_fset_fillvdir(arg->flags, SHWH); + } + bstart = au_fbstart(arg->file); + for (bindex = bstart; !err && bindex <= bend; bindex++) { + hf = au_h_fptr(arg->file, bindex); + if (!hf) + continue; + + offset = vfsub_llseek(hf, 0, SEEK_SET); + err = offset; + if (unlikely(offset)) + break; + arg->bindex = bindex; + do { + arg->err = 0; + au_fclr_fillvdir(arg->flags, CALLED); + /* smp_mb(); */ + err = vfsub_readdir(hf, fillvdir, arg, dlgt); + if (err >= 0) + err = arg->err; + } while (!err && au_ftest_fillvdir(arg->flags, CALLED)); + } + + if (!err && shwh) + err = au_handle_shwh(sb, arg->vdir, bstart, bend, arg->whlist, + arg->delist); + + for (bindex = bstart; bindex <= bend; bindex++) { + free_dehlist(arg->delist + bindex); + au_nhash_fin(arg->whlist + bindex); + } + kfree(arg->whlist); + + out_delist: + kfree(arg->delist); + out: + AuTraceErr(err); + return err; +} + +static int read_vdir(struct file *file, int may_read) +{ + int err; + unsigned long expire; + struct fillvdir_arg arg; + unsigned char do_read; + struct dentry *dentry; + struct inode *inode; + struct au_vdir *vdir, *allocated; + struct super_block *sb; + + dentry = file->f_dentry; + LKTRTrace("%.*s, may %d\n", AuDLNPair(dentry), may_read); + FiMustWriteLock(file); + inode = dentry->d_inode; + IMustLock(inode); + IiMustWriteLock(inode); + AuDebugOn(!S_ISDIR(inode->i_mode)); + + err = 0; + allocated = NULL; + do_read = 0; + sb = inode->i_sb; + expire = au_sbi(sb)->si_rdcache; + vdir = au_ivdir(inode); + if (!vdir) { + AuDebugOn(au_fvdir_cache(file)); + do_read = 1; + vdir = alloc_vdir(); + err = PTR_ERR(vdir); + if (IS_ERR(vdir)) + goto out; + err = 0; + allocated = vdir; + } else if (may_read + && (inode->i_version != vdir->vd_version + || time_after(jiffies, vdir->vd_jiffy + expire))) { + LKTRTrace("iver %llu, vdver %lu, exp %lu\n", + (unsigned long long)inode->i_version, + vdir->vd_version, vdir->vd_jiffy + expire); + do_read = 1; + err = reinit_vdir(vdir); + if (unlikely(err)) + goto out; + } + + if (!do_read) + return 0; /* success */ + + arg.file = file; + arg.vdir = vdir; + err = au_do_read_vdir(&arg); + if (!err) { + /* todo: necessary? */ + /* file->f_pos = 0; */ + vdir->vd_version = inode->i_version; + vdir->vd_last.i = 0; + vdir->vd_last.p.deblk = vdir->vd_deblk[0]; + if (allocated) + au_set_ivdir(inode, allocated); + } else if (allocated) + au_vdir_free(allocated); + + out: + AuTraceErr(err); + return err; +} + +static int copy_vdir(struct au_vdir *tgt, struct au_vdir *src) +{ + int err, i, rerr, n; + + AuTraceEnter(); + AuDebugOn(tgt->vd_nblk != 1); + + err = -ENOMEM; + if (tgt->vd_nblk < src->vd_nblk) { + au_vdir_deblk_t **p; + p = au_kzrealloc(tgt->vd_deblk, sizeof(*p) * tgt->vd_nblk, + sizeof(*p) * src->vd_nblk, GFP_NOFS); + if (unlikely(!p)) + goto out; + tgt->vd_deblk = p; + } + + tgt->vd_nblk = src->vd_nblk; + n = src->vd_nblk; + memcpy(tgt->vd_deblk[0], src->vd_deblk[0], AuSize_DEBLK); + /* tgt->vd_last.i = 0; */ + /* tgt->vd_last.p.deblk = tgt->vd_deblk[0]; */ + tgt->vd_version = src->vd_version; + tgt->vd_jiffy = src->vd_jiffy; + + for (i = 1; i < n; i++) { + tgt->vd_deblk[i] = kmalloc(AuSize_DEBLK, GFP_NOFS); + if (tgt->vd_deblk[i]) + memcpy(tgt->vd_deblk[i], src->vd_deblk[i], + AuSize_DEBLK); + else + goto out; + } + /* smp_mb(); */ + return 0; /* success */ + + out: + rerr = reinit_vdir(tgt); + BUG_ON(rerr); + AuTraceErr(err); + return err; +} + +int au_vdir_init(struct file *file) +{ + int err; + struct dentry *dentry; + struct inode *inode; + struct au_vdir *vdir_cache, *allocated; + + dentry = file->f_dentry; + LKTRTrace("%.*s, pos %lld\n", AuDLNPair(dentry), file->f_pos); + FiMustWriteLock(file); + inode = dentry->d_inode; + IiMustWriteLock(inode); + AuDebugOn(!S_ISDIR(inode->i_mode)); + + err = read_vdir(file, !file->f_pos); + if (unlikely(err)) + goto out; + + allocated = NULL; + vdir_cache = au_fvdir_cache(file); + if (!vdir_cache) { + vdir_cache = alloc_vdir(); + err = PTR_ERR(vdir_cache); + if (IS_ERR(vdir_cache)) + goto out; + allocated = vdir_cache; + } else if (!file->f_pos && vdir_cache->vd_version != file->f_version) { + err = reinit_vdir(vdir_cache); + if (unlikely(err)) + goto out; + } else + return 0; /* success */ + + err = copy_vdir(vdir_cache, au_ivdir(inode)); + if (!err) { + file->f_version = inode->i_version; + if (allocated) + au_set_fvdir_cache(file, allocated); + } else if (allocated) + au_vdir_free(allocated); + + out: + AuTraceErr(err); + return err; +} + +static loff_t calc_offset(struct au_vdir *vdir) +{ + loff_t offset; + union au_vdir_deblk_p p; + + p.deblk = vdir->vd_deblk[vdir->vd_last.i]; + offset = vdir->vd_last.p.p - p.p; + offset += sizeof(*p.deblk) * vdir->vd_last.i; + return offset; +} + +/* returns true or false */ +static int seek_vdir(struct file *file) +{ + int valid, i, n; + struct dentry *dentry; + struct au_vdir *vdir_cache; + loff_t offset; + union au_vdir_deblk_p p, deblk_end; + + dentry = file->f_dentry; + LKTRTrace("%.*s, pos %lld\n", AuDLNPair(dentry), file->f_pos); + vdir_cache = au_fvdir_cache(file); + AuDebugOn(!vdir_cache); + + valid = 1; + offset = calc_offset(vdir_cache); + LKTRTrace("offset %lld\n", offset); + if (file->f_pos == offset) + goto out; + + vdir_cache->vd_last.i = 0; + vdir_cache->vd_last.p.deblk = vdir_cache->vd_deblk[0]; + if (!file->f_pos) + goto out; + + valid = 0; + i = file->f_pos / AuSize_DEBLK; + LKTRTrace("i %d\n", i); + if (i >= vdir_cache->vd_nblk) + goto out; + + n = vdir_cache->vd_nblk; + for (; i < n; i++) { + p.deblk = vdir_cache->vd_deblk[i]; + deblk_end.deblk = p.deblk + 1; + offset = i; + offset *= AuSize_DEBLK; + while (!is_deblk_end(&p, &deblk_end) && offset < file->f_pos) { + int l; + l = calc_size(p.de->de_str.len); + offset += l; + p.p += l; + } + if (!is_deblk_end(&p, &deblk_end)) { + valid = 1; + vdir_cache->vd_last.i = i; + vdir_cache->vd_last.p = p; + break; + } + } + + out: + /* smp_mb(); */ + AuTraceErr(!valid); + return valid; +} + +int au_vdir_fill_de(struct file *file, void *dirent, filldir_t filldir) +{ + int err, l; + struct dentry *dentry; + struct au_vdir *vdir_cache; + struct au_vdir_de *de; + union au_vdir_deblk_p deblk_end; + + dentry = file->f_dentry; + LKTRTrace("%.*s, pos %lld\n", AuDLNPair(dentry), file->f_pos); + vdir_cache = au_fvdir_cache(file); + AuDebugOn(!vdir_cache); + + if (!seek_vdir(file)) + return 0; + + while (1) { + deblk_end.deblk + = vdir_cache->vd_deblk[vdir_cache->vd_last.i] + 1; + while (!is_deblk_end(&vdir_cache->vd_last.p, &deblk_end)) { + de = vdir_cache->vd_last.p.de; + LKTRTrace("%.*s, off%lld, i%lu, dt%d\n", + de->de_str.len, de->de_str.name, + file->f_pos, (unsigned long)de->de_ino, + de->de_type); + err = filldir(dirent, de->de_str.name, de->de_str.len, + file->f_pos, de->de_ino, de->de_type); + if (unlikely(err)) { + AuTraceErr(err); + /* todo: ignore the error caused by udba? */ + /* return err; */ + return 0; + } + + l = calc_size(de->de_str.len); + vdir_cache->vd_last.p.p += l; + file->f_pos += l; + } + if (vdir_cache->vd_last.i < vdir_cache->vd_nblk - 1) { + vdir_cache->vd_last.i++; + vdir_cache->vd_last.p.deblk + = vdir_cache->vd_deblk[vdir_cache->vd_last.i]; + file->f_pos = sizeof(*vdir_cache->vd_last.p.deblk) + * vdir_cache->vd_last.i; + continue; + } + break; + } + + /* smp_mb(); */ + return 0; +} diff --git a/fs/aufs/vfsub.c b/fs/aufs/vfsub.c new file mode 100644 index 0000000..3744387 --- /dev/null +++ b/fs/aufs/vfsub.c @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sub-routines for VFS + * + * $Id: vfsub.c,v 1.43 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +void vfsub_args_init(struct vfsub_args *vargs, struct au_hin_ignore *ign, + int dlgt, int force_unlink) +{ + do_vfsub_args_reinit(vargs, ign); + vargs->flags = 0; + if (dlgt) + vfsub_fset(vargs->flags, DLGT); + if (force_unlink) + vfsub_fset(vargs->flags, FORCE_UNLINK); +} + +/* ---------------------------------------------------------------------- */ + +struct file *vfsub_filp_open(const char *path, int oflags, int mode) +{ + struct file *err; + + LKTRTrace("%s\n", path); + + lockdep_off(); + err = filp_open(path, oflags, mode); + lockdep_on(); + if (!IS_ERR(err)) + au_update_fuse_h_inode(err->f_vfsmnt, err->f_dentry); /*ignore*/ + return err; +} + +int vfsub_path_lookup(const char *name, unsigned int flags, + struct nameidata *nd) +{ + int err; + + LKTRTrace("%s\n", name); + + /* lockdep_off(); */ + err = path_lookup(name, flags, nd); + /* lockdep_on(); */ + if (!err) + au_update_fuse_h_inode(nd->mnt, nd->dentry); + /*ignore*/ + return err; +} + +struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, + int len) +{ + struct dentry *d; + + LKTRTrace("%.*s/%.*s\n", AuDLNPair(parent), len, name); + IMustLock(parent->d_inode); + + d = lookup_one_len(name, parent, len); + if (!IS_ERR(d)) + au_update_fuse_h_inode(NULL, d); /*ignore*/ + return d; +} + +#ifdef CONFIG_AUFS_LHASH_PATCH +struct dentry *vfsub__lookup_hash(struct qstr *name, struct dentry *parent, + struct nameidata *nd) +{ + struct dentry *d; + + LKTRTrace("%.*s/%.*s, nd %d\n", + AuDLNPair(parent), AuLNPair(name), !!nd); + if (nd) + LKTRTrace("nd{0x%x}\n", nd->flags); + IMustLock(parent->d_inode); + + d = __lookup_hash(name, parent, nd); + if (!IS_ERR(d)) + au_update_fuse_h_inode(NULL, d); /*ignore*/ + return d; +} +#endif + +/* ---------------------------------------------------------------------- */ + +int do_vfsub_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + int err; + struct vfsmount *mnt; + + LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode); + IMustLock(dir); + + err = vfs_create(dir, dentry, mode, nd); + if (!err) { + mnt = NULL; + if (nd) + mnt = nd->mnt; + /* dir inode is locked */ + au_update_fuse_h_inode(mnt, dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(mnt, dentry); /*ignore*/ + } + return err; +} + +int do_vfsub_symlink(struct inode *dir, struct dentry *dentry, + const char *symname, int mode) +{ + int err; + + LKTRTrace("i%lu, %.*s, %s, 0x%x\n", + dir->i_ino, AuDLNPair(dentry), symname, mode); + IMustLock(dir); + + err = vfs_symlink(dir, dentry, symname, mode); + if (!err) { + /* dir inode is locked */ + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(NULL, dentry); /*ignore*/ + } + return err; +} + +int do_vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, + dev_t dev) +{ + int err; + + LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode); + IMustLock(dir); + + err = vfs_mknod(dir, dentry, mode, dev); + if (!err) { + /* dir inode is locked */ + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(NULL, dentry); /*ignore*/ + } + return err; +} + +int do_vfsub_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry) +{ + int err; + + LKTRTrace("%.*s, i%lu, %.*s\n", + AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + + lockdep_off(); + err = vfs_link(src_dentry, dir, dentry); + lockdep_on(); + if (!err) { + LKTRTrace("src_i %p, dst_i %p\n", + src_dentry->d_inode, dentry->d_inode); + /* fuse has different memory inode for the same inumber */ + au_update_fuse_h_inode(NULL, src_dentry); /*ignore*/ + /* dir inode is locked */ + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(NULL, dentry); /*ignore*/ + } + return err; +} + +int do_vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry) +{ + int err; + + LKTRTrace("i%lu, %.*s, i%lu, %.*s\n", + src_dir->i_ino, AuDLNPair(src_dentry), + dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + IMustLock(src_dir); + AuDebugOn(src_dir != dir && !vfsub_is_rename_mutex_locked(dir->i_sb)); + + lockdep_off(); + err = vfs_rename(src_dir, src_dentry, dir, dentry); + lockdep_on(); + if (!err) { + /* dir inode is locked */ + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(NULL, src_dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(NULL, src_dentry); /*ignore*/ + } + return err; +} + +int do_vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + int err; + + LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode); + IMustLock(dir); + + err = vfs_mkdir(dir, dentry, mode); + if (!err) { + /* dir inode is locked */ + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + au_update_fuse_h_inode(NULL, dentry); /*ignore*/ + } + return err; +} + +int do_vfsub_rmdir(struct inode *dir, struct dentry *dentry) +{ + int err; + + LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + + lockdep_off(); + err = vfs_rmdir(dir, dentry); + lockdep_on(); + /* dir inode is locked */ + if (!err) + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + return err; +} + +int do_vfsub_unlink(struct inode *dir, struct dentry *dentry) +{ + int err; + + LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); + IMustLock(dir); + + /* vfs_unlink() locks inode */ + lockdep_off(); + err = vfs_unlink(dir, dentry); + lockdep_on(); + /* dir inode is locked */ + if (!err) + au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/ + return err; +} + +/* ---------------------------------------------------------------------- */ + +ssize_t do_vfsub_read_u(struct file *file, char __user *ubuf, size_t count, + loff_t *ppos) +{ + ssize_t err; + + LKTRTrace("%.*s, cnt %zu, pos %lld\n", + AuDLNPair(file->f_dentry), count, *ppos); + + if (0 /*!au_test_nfs(file->f_vfsmnt->mnt_sb)*/) + err = vfs_read(file, ubuf, count, ppos); + else { + lockdep_off(); + err = vfs_read(file, ubuf, count, ppos); + lockdep_on(); + } + if (err >= 0) + au_update_fuse_h_inode(file->f_vfsmnt, file->f_dentry); + /*ignore*/ + return err; +} + +/* todo: kernel_read()? */ +ssize_t do_vfsub_read_k(struct file *file, void *kbuf, size_t count, + loff_t *ppos) +{ + ssize_t err; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + err = do_vfsub_read_u(file, (char __user *)kbuf, count, ppos); + set_fs(oldfs); + return err; +} + +ssize_t do_vfsub_write_u(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + ssize_t err; + + LKTRTrace("%.*s, cnt %zu, pos %lld\n", + AuDLNPair(file->f_dentry), count, *ppos); + + lockdep_off(); + err = vfs_write(file, ubuf, count, ppos); + lockdep_on(); + if (err >= 0) + au_update_fuse_h_inode(file->f_vfsmnt, file->f_dentry); + /*ignore*/ + return err; +} + +ssize_t do_vfsub_write_k(struct file *file, void *kbuf, size_t count, + loff_t *ppos) +{ + ssize_t err; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + err = do_vfsub_write_u(file, (const char __user *)kbuf, count, ppos); + set_fs(oldfs); + return err; +} + +int do_vfsub_readdir(struct file *file, filldir_t filldir, void *arg) +{ + int err; + + LKTRTrace("%.*s\n", AuDLNPair(file->f_dentry)); + + lockdep_off(); + err = vfs_readdir(file, filldir, arg); + lockdep_on(); + if (err >= 0) + au_update_fuse_h_inode(file->f_vfsmnt, file->f_dentry); + /*ignore*/ + return err; +} + +#ifdef CONFIG_AUFS_SPLICE_PATCH +long do_vfsub_splice_to(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags) +{ + long err; + + LKTRTrace("%.*s, pos %lld, len %zu, 0x%x\n", + AuDLNPair(in->f_dentry), *ppos, len, flags); + + lockdep_off(); + err = vfs_splice_to(in, ppos, pipe, len, flags); + lockdep_on(); + if (err >= 0) + au_update_fuse_h_inode(in->f_vfsmnt, in->f_dentry); /*ignore*/ + return err; +} + +long do_vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, + loff_t *ppos, size_t len, unsigned int flags) +{ + long err; + + LKTRTrace("%.*s, pos %lld, len %zu, 0x%x\n", + AuDLNPair(out->f_dentry), *ppos, len, flags); + + lockdep_off(); + err = vfs_splice_from(pipe, out, ppos, len, flags); + lockdep_on(); + if (err >= 0) + au_update_fuse_h_inode(out->f_vfsmnt, out->f_dentry); /*ignore*/ + return err; +} +#endif + +/* ---------------------------------------------------------------------- */ + +struct au_vfsub_mkdir_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + int mode; + struct vfsub_args *vargs; +}; + +static void au_call_vfsub_mkdir(void *args) +{ + struct au_vfsub_mkdir_args *a = args; + *a->errp = vfsub_mkdir(a->dir, a->dentry, a->mode, a->vargs); +} + +int vfsub_sio_mkdir(struct au_hinode *hdir, struct dentry *dentry, int mode, + int dlgt) +{ + int err, do_sio, wkq_err; + struct inode *dir = hdir->hi_inode; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); + + vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0); + vfsub_ign_hinode(&vargs, IN_CREATE, hdir); + do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE, dlgt); + if (!do_sio) + err = vfsub_mkdir(dir, dentry, mode, &vargs); + else { + struct au_vfsub_mkdir_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .mode = mode, + .vargs = &vargs + }; + vfsub_fclr(vargs.flags, DLGT); + wkq_err = au_wkq_wait(au_call_vfsub_mkdir, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } + + AuTraceErr(err); + return err; +} + +struct au_vfsub_rmdir_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + struct vfsub_args *vargs; +}; + +static void au_call_vfsub_rmdir(void *args) +{ + struct au_vfsub_rmdir_args *a = args; + *a->errp = vfsub_rmdir(a->dir, a->dentry, a->vargs); +} + +int vfsub_sio_rmdir(struct au_hinode *hdir, struct dentry *dentry, int dlgt) +{ + int err, do_sio, wkq_err; + struct inode *dir = hdir->hi_inode; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry)); + + vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0); + vfsub_ign_hinode(&vargs, IN_DELETE, hdir); + do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE, dlgt); + if (!do_sio) + err = vfsub_rmdir(dir, dentry, &vargs); + else { + struct au_vfsub_rmdir_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .vargs = &vargs + }; + vfsub_fclr(vargs.flags, DLGT); + wkq_err = au_wkq_wait(au_call_vfsub_rmdir, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct notify_change_args { + int *errp; + struct dentry *h_dentry; + struct iattr *ia; + struct vfsub_args *vargs; +}; + +static void call_notify_change(void *args) +{ + struct notify_change_args *a = args; + struct inode *h_inode; + + LKTRTrace("%.*s, ia_valid 0x%x\n", + AuDLNPair(a->h_dentry), a->ia->ia_valid); + h_inode = a->h_dentry->d_inode; + IMustLock(h_inode); + + *a->errp = -EPERM; + if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) { + vfsub_ignore(a->vargs); + lockdep_off(); + *a->errp = notify_change(a->h_dentry, a->ia); + lockdep_on(); + if (!*a->errp) + au_update_fuse_h_inode(NULL, a->h_dentry); /*ignore*/ + else + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); + } + AuTraceErr(*a->errp); +} + +#ifdef CONFIG_AUFS_DLGT +static void vfsub_notify_change_dlgt(struct notify_change_args *args, + unsigned int flags) +{ + if (!vfsub_ftest(flags, DLGT)) + call_notify_change(args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_notify_change, args, /*dlgt*/1); + if (unlikely(wkq_err)) + *args->errp = wkq_err; + } +} +#else +static void vfsub_notify_change_dlgt(struct notify_change_args *args, + unsigned int flags) +{ + call_notify_change(args); +} +#endif + +int vfsub_notify_change(struct dentry *dentry, struct iattr *ia, + struct vfsub_args *vargs) +{ + int err; + struct notify_change_args args = { + .errp = &err, + .h_dentry = dentry, + .ia = ia, + .vargs = vargs + }; + + vfsub_notify_change_dlgt(&args, vargs->flags); + + AuTraceErr(err); + return err; +} + +int vfsub_sio_notify_change(struct au_hinode *hdir, struct dentry *dentry, + struct iattr *ia) +{ + int err, wkq_err; + struct au_hin_ignore ign; + struct vfsub_args vargs; + __u32 events; + struct notify_change_args args = { + .errp = &err, + .h_dentry = dentry, + .ia = ia, + .vargs = &vargs + }; + + LKTRTrace("%.*s, 0x%x\n", AuDLNPair(dentry), ia->ia_valid); + + vfsub_args_init(&vargs, &ign, /*dlgt*/0, /*force_unlink*/0); + events = vfsub_events_notify_change(ia); + if (events) + vfsub_ign_hinode(&vargs, events, hdir); + wkq_err = au_wkq_wait(call_notify_change, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct unlink_args { + int *errp; + struct inode *dir; + struct dentry *dentry; + struct vfsub_args *vargs; +}; + +static void call_unlink(void *args) +{ + struct unlink_args *a = args; + struct inode *h_inode; + const int stop_sillyrename = (au_test_nfs(a->dentry->d_sb) + && atomic_read(&a->dentry->d_count) == 1); + + LKTRTrace("%.*s, stop_silly %d, cnt %d\n", + AuDLNPair(a->dentry), stop_sillyrename, + atomic_read(&a->dentry->d_count)); + + if (!stop_sillyrename) + dget(a->dentry); + h_inode = a->dentry->d_inode; + if (h_inode) + atomic_inc_return(&h_inode->i_count); + vfsub_ignore(a->vargs); + *a->errp = do_vfsub_unlink(a->dir, a->dentry); + if (unlikely(*a->errp || (a->dentry->d_flags & DCACHE_NFSFS_RENAMED))) + vfsub_unignore(a->vargs); + au_dbg_hin_list(a->vargs); + if (!stop_sillyrename) + dput(a->dentry); + if (h_inode) + iput(h_inode); + + AuTraceErr(*a->errp); +} + +/* + * @dir: must be locked. + * @dentry: target dentry. + */ +int vfsub_unlink(struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs) +{ + int err; + struct unlink_args args = { + .errp = &err, + .dir = dir, + .dentry = dentry, + .vargs = vargs + }; + + if (!vfsub_ftest(vargs->flags, DLGT) + && !vfsub_ftest(vargs->flags, FORCE_UNLINK)) + call_unlink(&args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_unlink, &args, + vfsub_ftest(vargs->flags, DLGT)); + if (unlikely(wkq_err)) + err = wkq_err; + } + + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct statfs_args { + int *errp; + void *arg; + struct kstatfs *buf; +}; + +static void call_statfs(void *args) +{ + struct statfs_args *a = args; + *a->errp = vfs_statfs(a->arg, a->buf); +} + +#ifdef CONFIG_AUFS_DLGT +static void vfsub_statfs_dlgt(struct statfs_args *args, int dlgt) +{ + if (!dlgt) + call_statfs(args); + else { + int wkq_err; + wkq_err = au_wkq_wait(call_statfs, args, /*dlgt*/1); + if (unlikely(wkq_err)) + *args->errp = wkq_err; + } +} +#else +static void vfsub_statfs_dlgt(struct statfs_args *args, int dlgt) +{ + call_statfs(args); +} +#endif + +int vfsub_statfs(void *arg, struct kstatfs *buf, int dlgt) +{ + int err; + struct statfs_args args = { + .errp = &err, + .arg = arg, + .buf = buf + }; + + vfsub_statfs_dlgt(&args, dlgt); + + return err; +} diff --git a/fs/aufs/vfsub.h b/fs/aufs/vfsub.h new file mode 100644 index 0000000..0bd9719 --- /dev/null +++ b/fs/aufs/vfsub.h @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * sub-routines for VFS + * + * $Id: vfsub.h,v 1.44 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_VFSUB_H__ +#define __AUFS_VFSUB_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) +#include +#endif +#include + +/* ---------------------------------------------------------------------- */ + +/* vfsub flags */ +#define Vfsub_DLGT 1 /* operation with delegation */ +#define Vfsub_FORCE_UNLINK (1 << 1) /* force unlinking */ +#define vfsub_ftest(flags, name) ((flags) & Vfsub_##name) +#define vfsub_fset(flags, name) { (flags) |= Vfsub_##name; } +#define vfsub_fclr(flags, name) { (flags) &= ~Vfsub_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef Vfsub_DLGT +#define Vfsub_DLGT 0 +#endif + +struct au_hin_ignore; +struct vfsub_args { +#ifdef CONFIG_AUFS_HINOTIFY + /* inotify events to be ignored */ + int nignore; + struct au_hin_ignore *ignore; +#endif + + unsigned int flags; +}; + +struct au_hinode; +#ifdef CONFIG_AUFS_HINOTIFY +static inline +void do_vfsub_args_reinit(struct vfsub_args *vargs, struct au_hin_ignore *ign) +{ + vargs->nignore = 0; + vargs->ignore = ign; +} + +static inline void vfsub_args_reinit(struct vfsub_args *vargs) +{ + vargs->nignore = 0; +} + +__u32 vfsub_events_notify_change(struct iattr *ia); +void vfsub_ign_hinode(struct vfsub_args *vargs, __u32 events, + struct au_hinode *hinode); +void vfsub_ignore(struct vfsub_args *vargs); +void vfsub_unignore(struct vfsub_args *vargs); +#else +static inline +void do_vfsub_args_reinit(struct vfsub_args *vargs, struct au_hin_ignore *ign) +{ + /* empty */ +} + +static inline void vfsub_args_reinit(struct vfsub_args *vargs) +{ + /* empty */ +} + +static inline __u32 vfsub_events_notify_change(struct iattr *ia) +{ + return 0; +} + +static inline void vfsub_ign_hinode(struct vfsub_args *vargs, __u32 events, + struct au_hinode *hinode) +{ + /* empty */ +} + +static inline void vfsub_ignore(struct vfsub_args *vargs) +{ + /* empty */ +} + +static inline void vfsub_unignore(struct vfsub_args *vargs) +{ + /* empty */ +} +#endif /* CONFIG_AUFS_HINOTIFY */ + +void vfsub_args_init(struct vfsub_args *vargs, struct au_hin_ignore *ign, + int dlgt, int force_unlink); + +/* ---------------------------------------------------------------------- */ + +/* inotify_inode_watched() is not exported */ +static inline int au_test_inotify(struct inode *inode) +{ +#ifdef CONFIG_INOTIFY + return !list_empty(&inode->inotify_watches); +#endif + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* lock subclass for hidden inode */ +/* default MAX_LOCKDEP_SUBCLASSES(8) is not enough */ +/* reduce? gave up. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#define I_MUTEX_QUOTA 0 +#endif + +enum { + AuLsc_I_Begin = I_MUTEX_QUOTA, /* 4 */ + AuLsc_I_PARENT, /* hidden inode, parent first */ + AuLsc_I_PARENT2, /* copyup dirs */ + AuLsc_I_PARENT3, /* rename with hinotify */ + AuLsc_I_PARENT4, /* ditto */ + AuLsc_I_CHILD, + AuLsc_I_CHILD2, + AuLsc_I_End +}; + +/* simple abstraction */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16) +static inline void vfsub_i_lock(struct inode *i) +{ + down(&i->i_sem); +} + +static inline void vfsub_i_lock_nested(struct inode *i, unsigned lsc) +{ + vfsub_i_lock(i); +} + +static inline void vfsub_i_unlock(struct inode *i) +{ + up(&i->i_sem); +} + +static inline int vfsub_i_trylock(struct inode *i) +{ + return !down_trylock(&i->i_sem); +} + +#define IMustLock(i) AuDebugOn(!down_trylock(&(i)->i_sem)) +#else +static inline void vfsub_i_lock(struct inode *i) +{ + mutex_lock(&i->i_mutex); +} + +static inline void vfsub_i_lock_nested(struct inode *i, unsigned lsc) +{ + mutex_lock_nested(&i->i_mutex, lsc); +} + +static inline void vfsub_i_unlock(struct inode *i) +{ + mutex_unlock(&i->i_mutex); +} + +static inline int vfsub_i_trylock(struct inode *i) +{ + return mutex_trylock(&i->i_mutex); +} + +#define IMustLock(i) MtxMustLock(&(i)->i_mutex) +#endif /* LINUX_VERSION_CODE */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 17) +static inline void vfsub_lock_rename_mutex(struct super_block *sb) +{ + lockdep_off(); + down(&sb->s_vfs_rename_sem); + lockdep_on(); +} + +static inline void vfsub_unlock_rename_mutex(struct super_block *sb) +{ + lockdep_off(); + up(&sb->s_vfs_rename_sem); + lockdep_on(); +} + +static inline int vfsub_is_rename_mutex_locked(struct super_block *sb) +{ + int res; + + lockdep_off(); + res = down_trylock(&sb->s_vfs_rename_sem); + if (!res) + up(&sb->s_vfs_rename_sem); + lockdep_on(); + + return res; +} +#else +static inline void vfsub_lock_rename_mutex(struct super_block *sb) +{ + lockdep_off(); + mutex_lock(&sb->s_vfs_rename_mutex); + lockdep_on(); +} + +static inline void vfsub_unlock_rename_mutex(struct super_block *sb) +{ + lockdep_off(); + mutex_unlock(&sb->s_vfs_rename_mutex); + lockdep_on(); +} + +static inline int vfsub_is_rename_mutex_locked(struct super_block *sb) +{ + int res; + + lockdep_off(); + res = mutex_is_locked(&sb->s_vfs_rename_mutex); + lockdep_on(); + + return res; +} +#endif /* LINUX_VERSION_CODE */ + +static inline +struct dentry *vfsub_lock_rename(struct dentry *d1, struct dentry *d2) +{ + struct dentry *d; + + lockdep_off(); + d = lock_rename(d1, d2); + lockdep_on(); + return d; +} + +static inline void vfsub_unlock_rename(struct dentry *d1, struct dentry *d2) +{ + lockdep_off(); + unlock_rename(d1, d2); + lockdep_on(); +} + +static inline int au_verify_parent(struct dentry *dentry, struct inode *dir) +{ + IMustLock(dir); + return /* !dir->i_nlink || */ dentry->d_parent->d_inode != dir; +} + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_WORKAROUND_FUSE +/* br_fuse.c */ +int au_update_fuse_h_inode(struct vfsmount *h_mnt, struct dentry *h_dentry); +#else +static inline +int au_update_fuse_h_inode(struct vfsmount *h_mnt, struct dentry *h_dentry) +{ + return 0; +} +#endif + +#ifdef CONFIG_AUFS_BR_XFS +/* br_xfs.c */ +dev_t au_h_rdev(struct inode *h_inode, struct vfsmount *h_mnt, + struct dentry *h_dentry); +#else +static inline +dev_t au_h_rdev(struct inode *h_inode, struct vfsmount *h_mnt, + struct dentry *h_dentry) +{ + return h_inode->i_rdev; +} +#endif + +/* simple abstractions, for future use */ +static inline +int do_vfsub_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + LKTRTrace("i%lu, mask 0x%x, nd %d\n", inode->i_ino, mask, !!nd); + IMustLock(inode); + return permission(inode, mask, nd); +} + +/* ---------------------------------------------------------------------- */ + +struct file *vfsub_filp_open(const char *path, int oflags, int mode); +int vfsub_path_lookup(const char *name, unsigned int flags, + struct nameidata *nd); +struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent, + int len); + +#ifdef CONFIG_AUFS_LHASH_PATCH +struct dentry *vfsub__lookup_hash(struct qstr *name, struct dentry *parent, + struct nameidata *nd); +#endif + +/* ---------------------------------------------------------------------- */ + +int do_vfsub_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd); +int do_vfsub_symlink(struct inode *dir, struct dentry *dentry, + const char *symname, int mode); +int do_vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, + dev_t dev); +int do_vfsub_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry); +int do_vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry); +int do_vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode); +int do_vfsub_rmdir(struct inode *dir, struct dentry *dentry); +int do_vfsub_unlink(struct inode *dir, struct dentry *dentry); + +/* ---------------------------------------------------------------------- */ + +ssize_t do_vfsub_read_u(struct file *file, char __user *ubuf, size_t count, + loff_t *ppos); +/* todo: kernel_read()? */ +ssize_t do_vfsub_read_k(struct file *file, void *kbuf, size_t count, + loff_t *ppos); +ssize_t do_vfsub_write_u(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos); +ssize_t do_vfsub_write_k(struct file *file, void *kbuf, size_t count, + loff_t *ppos); +int do_vfsub_readdir(struct file *file, filldir_t filldir, void *arg); + +/* ---------------------------------------------------------------------- */ + +#ifndef CONFIG_AUFS_UNIONFS23_PATCH +#define vfs_splice_to do_splice_to +#define vfs_splice_from do_splice_from +#endif + +#ifdef CONFIG_AUFS_SPLICE_PATCH +long do_vfsub_splice_to(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags); +long do_vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, + loff_t *ppos, size_t len, unsigned int flags); +#else +static inline +long do_vfsub_splice_to(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags) +{ + return -ENOSYS; +} + +static inline +long do_vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, + loff_t *ppos, size_t len, unsigned int flags) +{ + return -ENOSYS; +} +#endif /* CONFIG_AUFS_SPLICE_PATCH */ + +/* ---------------------------------------------------------------------- */ + +static inline loff_t vfsub_llseek(struct file *file, loff_t offset, int origin) +{ + loff_t err; + + LKTRTrace("%.*s\n", AuDLNPair(file->f_dentry)); + + lockdep_off(); + err = vfs_llseek(file, offset, origin); + lockdep_on(); + return err; +} + +static inline int do_vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *st) +{ + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + return vfs_getattr(mnt, dentry, st); +} + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_HIN_OR_DLGT +/* hin_or_dlgt.c */ +int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd, + int dlgt); + +int vfsub_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd, struct vfsub_args *vargs); +int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname, + int mode, struct vfsub_args *vargs); +int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev, + struct vfsub_args *vargs); +int vfsub_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry, struct vfsub_args *vargs); +int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs); +int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode, + struct vfsub_args *vargs); +int vfsub_rmdir(struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs); + +ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count, + loff_t *ppos, int dlgt); +ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, loff_t *ppos, + int dlgt); +ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count, + loff_t *ppos, struct vfsub_args *vargs); +ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos, + struct vfsub_args *vargs); +int vfsub_readdir(struct file *file, filldir_t filldir, void *arg, int dlgt); +long vfsub_splice_to(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags, int dlgt); +long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, + loff_t *ppos, size_t len, unsigned int flags, + struct vfsub_args *vargs); + +int vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st, + int dlgt); +#else + +static inline +int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd, + int dlgt) +{ + return do_vfsub_permission(inode, mask, nd); +} + +static inline +int vfsub_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd, struct vfsub_args *vargs) +{ + return do_vfsub_create(dir, dentry, mode, nd); +} + +static inline +int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname, + int mode, struct vfsub_args *vargs) +{ + return do_vfsub_symlink(dir, dentry, symname, mode); +} + +static inline +int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev, + struct vfsub_args *vargs) +{ + return do_vfsub_mknod(dir, dentry, mode, dev); +} + +static inline +int vfsub_link(struct dentry *src_dentry, struct inode *dir, + struct dentry *dentry, struct vfsub_args *vargs) +{ + return do_vfsub_link(src_dentry, dir, dentry); +} + +static inline +int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry, + struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs) +{ + return do_vfsub_rename(src_dir, src_dentry, dir, dentry); +} + +static inline +int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode, + struct vfsub_args *vargs) +{ + return do_vfsub_mkdir(dir, dentry, mode); +} + +static inline +int vfsub_rmdir(struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs) +{ + return do_vfsub_rmdir(dir, dentry); +} + +static inline +ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count, + loff_t *ppos, int dlgt) +{ + return do_vfsub_read_u(file, ubuf, count, ppos); +} + +static inline +ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, loff_t *ppos, + int dlgt) +{ + return do_vfsub_read_k(file, kbuf, count, ppos); +} + +static inline +ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count, + loff_t *ppos, struct vfsub_args *vargs) +{ + return do_vfsub_write_u(file, ubuf, count, ppos); +} + +static inline +ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos, + struct vfsub_args *vargs) +{ + return do_vfsub_write_k(file, kbuf, count, ppos); +} + +static inline +int vfsub_readdir(struct file *file, filldir_t filldir, void *arg, int dlgt) +{ + return do_vfsub_readdir(file, filldir, arg); +} + +static inline +long vfsub_splice_to(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, size_t len, + unsigned int flags, int dlgt) +{ + return do_vfsub_splice_to(in, ppos, pipe, len, flags); +} + +static inline +long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out, + loff_t *ppos, size_t len, unsigned int flags, + struct vfsub_args *vargs) +{ + return do_vfsub_splice_from(pipe, out, ppos, len, flags); +} + +static inline +int vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st, + int dlgt) +{ + return do_vfsub_getattr(mnt, dentry, st); +} +#endif /* HIN_OR_DLGT */ + +/* ---------------------------------------------------------------------- */ + +int vfsub_sio_mkdir(struct au_hinode *hdir, struct dentry *dentry, int mode, + int dlgt); +int vfsub_sio_rmdir(struct au_hinode *hdir, struct dentry *dentry, int dlgt); +int vfsub_sio_notify_change(struct au_hinode *hdir, struct dentry *dentry, + struct iattr *ia); + +/* ---------------------------------------------------------------------- */ + +int vfsub_notify_change(struct dentry *dentry, struct iattr *ia, + struct vfsub_args *vargs); +int vfsub_unlink(struct inode *dir, struct dentry *dentry, + struct vfsub_args *vargs); +int vfsub_statfs(void *arg, struct kstatfs *buf, int dlgt); + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) +/* introduced in linux-2.6.20 */ +struct path { + struct vfsmount *mnt; + struct dentry *dentry; +}; +#endif + +/* introduced in linux-2.6.25 */ +static inline void path_get(struct path *path) +{ + mntget(path->mnt); + dget(path->dentry); +} + +static inline void path_put(struct path *path) +{ + dput(path->dentry); + mntput(path->mnt); +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_VFSUB_H__ */ diff --git a/fs/aufs/wbr_policy.c b/fs/aufs/wbr_policy.c new file mode 100644 index 0000000..701b4a1 --- /dev/null +++ b/fs/aufs/wbr_policy.c @@ -0,0 +1,702 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * policies for selecting one among multiple writable branches + * + * $Id: wbr_policy.c,v 1.20 2009/01/26 06:23:51 sfjro Exp $ + */ + +#include +#include "aufs.h" + +static int au_cpdown_attr(struct au_hinode *hdir, struct dentry *h_dst, + struct dentry *h_src) +{ + int err, sbits; + struct iattr ia; + struct inode *h_idst, *h_isrc; + + LKTRTrace("%.*s\n", AuDLNPair(h_dst)); + h_idst = h_dst->d_inode; + /* todo? IMustLock(h_idst); */ + h_isrc = h_src->d_inode; + /* todo? IMustLock(h_isrc); */ + + ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID; + ia.ia_mode = h_isrc->i_mode; + ia.ia_uid = h_isrc->i_uid; + ia.ia_gid = h_isrc->i_gid; + sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID)); + au_cpup_attr_flags(h_idst, h_isrc); + err = vfsub_sio_notify_change(hdir, h_dst, &ia); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + /* is this nfs only? */ + if (!err && sbits && au_test_nfs(h_dst->d_sb)) { + ia.ia_valid = ATTR_FORCE | ATTR_MODE; + ia.ia_mode = h_isrc->i_mode; + err = vfsub_sio_notify_change(hdir, h_dst, &ia); + } +#endif + + AuTraceErr(err); + return err; +} + +struct au_cpdown_dir_args { + struct dentry *parent; + unsigned int parent_opq; /* bit-flags */ +}; + +static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst, + struct dentry *h_parent, void *arg) +{ + int err, rerr; + struct au_cpdown_dir_args *args = arg; + aufs_bindex_t bend, bopq, bstart; + unsigned char parent_opq, whed, dlgt, do_opq, made_dir, diropq; + struct dentry *h_dentry, *opq_dentry, *wh_dentry, *parent; + struct inode *h_dir, *h_inode, *inode, *dir; + + LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bdst); + bstart = au_dbstart(dentry); + AuDebugOn(bstart <= bdst + && bdst <= au_dbend(dentry) + && au_h_dptr(dentry, bdst)); + AuDebugOn(!h_parent); + /* todo: safe? */ + parent = dget_parent(dentry); + dir = parent->d_inode; + dput(parent); + h_dir = h_parent->d_inode; + AuDebugOn(!h_dir); + AuDebugOn(h_dir != au_h_iptr(dir, bdst)); + IMustLock(h_dir); + + err = au_lkup_neg(dentry, bdst); + if (unlikely(err < 0)) + goto out; + h_dentry = au_h_dptr(dentry, bdst); + dlgt = !!au_test_dlgt(au_mntflags(dentry->d_sb)); + err = vfsub_sio_mkdir(au_hi(dir, bdst), h_dentry, + S_IRWXU | S_IRUGO | S_IXUGO, dlgt); + if (unlikely(err)) + goto out_put; + + made_dir = 1; + bend = au_dbend(dentry); + bopq = au_dbdiropq(dentry); + whed = (au_dbwh(dentry) == bdst); + if (!args->parent_opq) + args->parent_opq |= (bopq <= bdst); + parent_opq = (args->parent_opq && args->parent == dentry); + do_opq = 0; + diropq = 0; + h_inode = h_dentry->d_inode; + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + if (whed || (parent_opq && do_opq)) { + opq_dentry = au_diropq_create(dentry, bdst, dlgt); + err = PTR_ERR(opq_dentry); + if (IS_ERR(opq_dentry)) { + vfsub_i_unlock(h_inode); + goto out_dir; + } + dput(opq_dentry); + diropq = 1; + } + + err = au_cpdown_attr(au_hi(dir, bdst), h_dentry, + au_h_dptr(dentry, bstart)); + vfsub_i_unlock(h_inode); + if (unlikely(err)) + goto out_opq; + + wh_dentry = NULL; + if (whed) { + wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, /*ndx*/NULL); + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out_opq; + err = 0; + if (wh_dentry->d_inode) + err = au_wh_unlink_dentry(au_hi(dir, bdst), wh_dentry, + dentry, dlgt); + dput(wh_dentry); + if (unlikely(err)) + goto out_opq; + } + + inode = dentry->d_inode; + if (au_ibend(inode) < bdst) + au_set_ibend(inode, bdst); + au_set_h_iptr(inode, bdst, au_igrab(h_inode), au_hi_flags(inode, 1)); + goto out; /* success */ + + /* revert */ + out_opq: + if (diropq) { + vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); + rerr = au_diropq_remove(dentry, bdst, dlgt); + vfsub_i_unlock(h_inode); + if (unlikely(rerr)) { + AuIOErr("failed removing diropq for %.*s b%d (%d)\n", + AuDLNPair(dentry), bdst, rerr); + err = -EIO; + goto out; + } + } + out_dir: + if (made_dir) { + rerr = vfsub_sio_rmdir(au_hi(dir, bdst), h_dentry, dlgt); + if (unlikely(rerr)) { + AuIOErr("failed removing %.*s b%d (%d)\n", + AuDLNPair(dentry), bdst, rerr); + err = -EIO; + } + } + out_put: + au_set_h_dptr(dentry, bdst, NULL); + if (au_dbend(dentry) == bdst) + au_update_dbend(dentry); + out: + AuTraceErr(err); + return err; +} + +int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst) +{ + int err; + struct au_cpdown_dir_args args = { + .parent = dget_parent(dentry), + .parent_opq = 0 + }; + + LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bdst); + + err = au_cp_dirs(dentry, bdst, au_cpdown_dir, &args); + dput(args.parent); + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* policies for create */ + +static int au_wbr_bu(struct super_block *sb, aufs_bindex_t bindex) +{ + for (; bindex >= 0; bindex--) + if (!au_br_rdonly(au_sbr(sb, bindex))) + return bindex; + return -EROFS; +} + +/* top down parent */ +static int au_wbr_create_tdp(struct dentry *dentry, int isdir) +{ + int err; + struct super_block *sb; + aufs_bindex_t bstart, bindex; + unsigned char dirperm1; + struct dentry *parent, *h_parent; + struct inode *h_dir; + + LKTRTrace("%.*s, dir %d\n", AuDLNPair(dentry), isdir); + + sb = dentry->d_sb; + dirperm1 = !!au_test_dirperm1(au_mntflags(sb)); + bstart = au_dbstart(dentry); + AuDebugOn(bstart < 0); + err = bstart; + /* todo: can 'err' be an illegal? */ + if (/* err >= 0 && */ !au_br_rdonly(au_sbr(sb, bstart))) + goto out; + + err = -EROFS; + parent = dget_parent(dentry); + for (bindex = au_dbstart(parent); bindex < bstart; bindex++) { + h_parent = au_h_dptr(parent, bindex); + if (!h_parent) + continue; + h_dir = h_parent->d_inode; + if (!h_dir) + continue; + + if (!au_br_rdonly(au_sbr(sb, bindex)) + && (!dirperm1 + || au_test_h_perm(h_dir, MAY_WRITE | MAY_EXEC, + /*dlgt*/0))) { + err = bindex; + break; + } + } + dput(parent); + + /* bottom up here */ + if (unlikely(err < 0)) + err = au_wbr_bu(sb, bstart - 1); + + out: + LKTRTrace("b%d\n", err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* an exception for the policy other than tdp */ +static int au_wbr_create_exp(struct dentry *dentry) +{ + int err; + struct dentry *parent; + aufs_bindex_t bwh, bdiropq; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + err = -1; + bwh = au_dbwh(dentry); + parent = dget_parent(dentry); + bdiropq = au_dbdiropq(parent); + if (bwh >= 0) { + if (bdiropq >= 0) + err = min(bdiropq, bwh); + else + err = bwh; + LKTRTrace("%d\n", err); + } else if (bdiropq >= 0) { + err = bdiropq; + LKTRTrace("%d\n", err); + } + dput(parent); + + if (err >= 0 && au_br_rdonly(au_sbr(dentry->d_sb, err))) + err = -1; + + LKTRTrace("%d\n", err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* round robin */ +static int au_wbr_create_init_rr(struct super_block *sb) +{ + int err; + + err = au_wbr_bu(sb, au_sbend(sb)); + atomic_set(&au_sbi(sb)->si_wbr_rr_next, -err); /* less important */ + + LKTRTrace("b%d\n", err); + return err; +} + +static int au_wbr_create_rr(struct dentry *dentry, int isdir) +{ + int err, nbr; + struct super_block *sb; + atomic_t *next; + unsigned int u; + aufs_bindex_t bindex, bend; + + LKTRTrace("%.*s, dir %d\n", AuDLNPair(dentry), isdir); + + sb = dentry->d_sb; + next = NULL; + err = au_wbr_create_exp(dentry); + if (err >= 0) + goto out; + + next = &au_sbi(sb)->si_wbr_rr_next; + bend = au_sbend(sb); + nbr = bend + 1; + for (bindex = 0; bindex <= bend; bindex++) { + if (!isdir) { + err = atomic_dec_return(next) + 1; + /* modulo for 0 is meaningless */ + if (unlikely(!err)) + err = atomic_dec_return(next) + 1; + } else + err = atomic_read(next); + LKTRTrace("%d\n", err); + u = err; + err = u % nbr; + LKTRTrace("%d\n", err); + if (!au_br_rdonly(au_sbr(sb, err))) + break; + err = -EROFS; + } + + out: + LKTRTrace("%d\n", err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* most free space */ +static void *au_wbr_statfs_arg(struct au_branch *br, struct super_block *sb, + aufs_bindex_t bindex) +{ + struct super_block *h_sb; + + h_sb = br->br_mnt->mnt_sb; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) + return h_sb; +#else + if (!au_test_nfs(h_sb)) + return h_sb->s_root; + + /* sigh,,, why nfs s_root has wrong inode? */ + return au_di(sb->s_root)->di_hdentry[0 + bindex].hd_dentry; +#endif +} + +static void au_mfs(struct dentry *dentry) +{ + struct super_block *sb; + aufs_bindex_t bindex, bend; + unsigned char dlgt; + int err; + struct kstatfs st; + unsigned long long b, bavail; + void *arg; + struct au_branch *br; + struct au_wbr_mfs *mfs; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + bavail = 0; + sb = dentry->d_sb; + mfs = &au_sbi(sb)->si_wbr_mfs; + mfs->mfs_bindex = -EROFS; + mfs->mfsrr_bytes = 0; + dlgt = !!au_test_dlgt(au_mntflags(sb)); + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) { + br = au_sbr(sb, bindex); + if (au_br_rdonly(br)) + continue; + AuDebugOn(!br->br_wbr); + arg = au_wbr_statfs_arg(br, sb, bindex); + if (!arg) + continue; + + err = vfsub_statfs(arg, &st, dlgt); + LKTRTrace("b%d, %d, %llu\n", + bindex, err, (unsigned long long)st.f_bavail); + if (unlikely(err)) { + AuWarn1("failed statfs, b%d, %d\n", bindex, err); + continue; + } + + /* when the available size is equal, select lower one */ + b = st.f_bavail * st.f_bsize; + br->br_wbr->wbr_bytes = b; + if (b >= bavail) { + bavail = b; + mfs->mfs_bindex = bindex; + mfs->mfs_jiffy = jiffies; + } + } + + mfs->mfsrr_bytes = bavail; + LKTRTrace("b%d\n", mfs->mfs_bindex); +} + +static int au_wbr_create_mfs(struct dentry *dentry, int isdir) +{ + int err; + struct super_block *sb; + struct au_wbr_mfs *mfs; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + sb = dentry->d_sb; + err = au_wbr_create_exp(dentry); + if (err >= 0) + goto out; + + mfs = &au_sbi(sb)->si_wbr_mfs; + mutex_lock(&mfs->mfs_lock); + if (unlikely(time_after(jiffies, mfs->mfs_jiffy + mfs->mfs_expire) + || mfs->mfs_bindex < 0 + || au_br_rdonly(au_sbr(sb, mfs->mfs_bindex)))) + au_mfs(dentry); + mutex_unlock(&mfs->mfs_lock); + err = mfs->mfs_bindex; + + out: + LKTRTrace("b%d\n", err); + return err; +} + +static int au_wbr_create_init_mfs(struct super_block *sb) +{ + struct au_wbr_mfs *mfs; + + mfs = &au_sbi(sb)->si_wbr_mfs; + LKTRTrace("expire %lu\n", mfs->mfs_expire); + + mutex_init(&mfs->mfs_lock); + mfs->mfs_jiffy = 0; + mfs->mfs_bindex = -EROFS; + + return 0; +} + +static int au_wbr_create_fin_mfs(struct super_block *sb) +{ + AuTraceEnter(); + mutex_destroy(&au_sbi(sb)->si_wbr_mfs.mfs_lock); + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* most free space and then round robin */ +static int au_wbr_create_mfsrr(struct dentry *dentry, int isdir) +{ + int err; + struct au_wbr_mfs *mfs; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), isdir); + + err = au_wbr_create_mfs(dentry, isdir); + if (err >= 0) { + mfs = &au_sbi(dentry->d_sb)->si_wbr_mfs; + LKTRTrace("%llu bytes, %llu wmark\n", + mfs->mfsrr_bytes, mfs->mfsrr_watermark); + if (mfs->mfsrr_bytes < mfs->mfsrr_watermark) + err = au_wbr_create_rr(dentry, isdir); + } + + LKTRTrace("b%d\n", err); + return err; +} + +static int au_wbr_create_init_mfsrr(struct super_block *sb) +{ + int err; + + au_wbr_create_init_mfs(sb); /* ignore */ + err = au_wbr_create_init_rr(sb); + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* top down parent and most free space */ +static int au_wbr_create_pmfs(struct dentry *dentry, int isdir) +{ + int err, e2; + struct super_block *sb; + struct dentry *parent, *h_parent; + aufs_bindex_t bindex, bstart, bend; + unsigned char dirperm1; + struct au_branch *br; + unsigned long long b; + struct inode *h_dir; + + LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), isdir); + + err = au_wbr_create_tdp(dentry, isdir); + if (unlikely(err < 0)) + goto out; + parent = dget_parent(dentry); + bstart = au_dbstart(parent); + bend = au_dbtaildir(parent); + if (bstart == bend) + goto out_parent; /* success */ + + e2 = au_wbr_create_mfs(dentry, isdir); + if (e2 < 0) + goto out_parent; /* success */ + + /* when the available size is equal, select upper one */ + sb = dentry->d_sb; + br = au_sbr(sb, err); + AuDebugOn(!br->br_wbr); + dirperm1 = !!au_test_dirperm1(au_mntflags(sb)); + b = br->br_wbr->wbr_bytes; + LKTRTrace("b%d, %llu\n", err, b); + + if (dirperm1) { + for (bindex = bstart; bindex <= bend; bindex++) { + h_parent = au_h_dptr(parent, bindex); + if (!h_parent) + continue; + h_dir = h_parent->d_inode; + if (!h_dir) + continue; + + br = au_sbr(sb, bindex); + if (!au_br_rdonly(br) + && au_test_h_perm(h_dir, MAY_WRITE | MAY_EXEC, + /*dlgt*/0) + && br->br_wbr->wbr_bytes > b) { + b = br->br_wbr->wbr_bytes; + err = bindex; + LKTRTrace("b%d, %llu\n", err, b); + } + } + if (err >= 0) + goto out_parent; + } + for (bindex = bstart; bindex <= bend; bindex++) { + h_parent = au_h_dptr(parent, bindex); + if (!h_parent || !h_parent->d_inode) + continue; + + br = au_sbr(sb, bindex); + if (!au_br_rdonly(br) && br->br_wbr->wbr_bytes > b) { + b = br->br_wbr->wbr_bytes; + err = bindex; + LKTRTrace("b%d, %llu\n", err, b); + } + } + + out_parent: + dput(parent); + out: + LKTRTrace("b%d\n", err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* policies for copyup */ + +/* top down parent */ +static int au_wbr_copyup_tdp(struct dentry *dentry) +{ + return au_wbr_create_tdp(dentry, /*isdir, anything is ok*/0); +} + +/* bottom up parent */ +static int au_wbr_copyup_bup(struct dentry *dentry) +{ + int err; + struct dentry *parent, *h_parent; + aufs_bindex_t bindex, bstart; + unsigned char dirperm1; + struct super_block *sb; + struct inode *h_dir; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + err = -EROFS; + sb = dentry->d_sb; + dirperm1 = !!au_test_dirperm1(au_mntflags(sb)); + parent = dget_parent(dentry); + bstart = au_dbstart(parent); + for (bindex = au_dbstart(dentry); bindex >= bstart; bindex--) { + h_parent = au_h_dptr(parent, bindex); + if (!h_parent) + continue; + h_dir = h_parent->d_inode; + if (!h_dir) + continue; + + if (!au_br_rdonly(au_sbr(sb, bindex)) + && (!dirperm1 + || au_test_h_perm(h_dir, MAY_WRITE | MAY_EXEC, + /*dlgt*/0))) { + err = bindex; + break; + } + } + dput(parent); + + /* bottom up here */ + if (unlikely(err < 0)) + err = au_wbr_bu(sb, bstart - 1); + + LKTRTrace("b%d\n", err); + return err; +} + +/* bottom up */ +static int au_wbr_copyup_bu(struct dentry *dentry) +{ + int err; + + LKTRTrace("%.*s\n", AuDLNPair(dentry)); + + err = au_wbr_bu(dentry->d_sb, au_dbstart(dentry)); + + LKTRTrace("b%d\n", err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct au_wbr_copyup_operations au_wbr_copyup_ops[] = { + [AuWbrCopyup_TDP] = { + .copyup = au_wbr_copyup_tdp + }, + [AuWbrCopyup_BUP] = { + .copyup = au_wbr_copyup_bup + }, + [AuWbrCopyup_BU] = { + .copyup = au_wbr_copyup_bu + } +}; + +struct au_wbr_create_operations au_wbr_create_ops[] = { + [AuWbrCreate_TDP] = { + .create = au_wbr_create_tdp + }, + [AuWbrCreate_RR] = { + .create = au_wbr_create_rr, + .init = au_wbr_create_init_rr + }, + [AuWbrCreate_MFS] = { + .create = au_wbr_create_mfs, + .init = au_wbr_create_init_mfs, + .fin = au_wbr_create_fin_mfs + }, + [AuWbrCreate_MFSV] = { + .create = au_wbr_create_mfs, + .init = au_wbr_create_init_mfs, + .fin = au_wbr_create_fin_mfs + }, + [AuWbrCreate_MFSRR] = { + .create = au_wbr_create_mfsrr, + .init = au_wbr_create_init_mfsrr, + .fin = au_wbr_create_fin_mfs + }, + [AuWbrCreate_MFSRRV] = { + .create = au_wbr_create_mfsrr, + .init = au_wbr_create_init_mfsrr, + .fin = au_wbr_create_fin_mfs + }, + [AuWbrCreate_PMFS] = { + .create = au_wbr_create_pmfs, + .init = au_wbr_create_init_mfs, + .fin = au_wbr_create_fin_mfs + }, + [AuWbrCreate_PMFSV] = { + .create = au_wbr_create_pmfs, + .init = au_wbr_create_init_mfs, + .fin = au_wbr_create_fin_mfs + } +}; diff --git a/fs/aufs/whout.c b/fs/aufs/whout.c new file mode 100644 index 0000000..79f44dd --- /dev/null +++ b/fs/aufs/whout.c @@ -0,0 +1,1162 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * whiteout for logical deletion and opaque directory + * + * $Id: whout.c,v 1.47 2009/01/26 06:24:19 sfjro Exp $ + */ + +#include +#include +#include +#include +#include "aufs.h" + +#define WH_MASK S_IRUGO + +/* If a directory contains this file, then it is opaque. We start with the + * .wh. flag so that it is blocked by lookup. + */ +static struct qstr diropq_name = { + .name = AUFS_WH_DIROPQ, + .len = sizeof(AUFS_WH_DIROPQ) - 1 +}; + +/* + * generate whiteout name, which is NOT terminated by NULL. + * @name: original d_name.name + * @len: original d_name.len + * @wh: whiteout qstr + * returns zero when succeeds, otherwise error. + * succeeded value as wh->name should be freed by au_wh_name_free(). + */ +int au_wh_name_alloc(const char *name, int len, struct qstr *wh) +{ + char *p; + + AuDebugOn(!name || !len || !wh); + + if (unlikely(len > PATH_MAX - AUFS_WH_PFX_LEN)) + return -ENAMETOOLONG; + + wh->len = len + AUFS_WH_PFX_LEN; + p = kmalloc(wh->len, GFP_NOFS); + wh->name = p; + if (p) { + memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN); + memcpy(p + AUFS_WH_PFX_LEN, name, len); + /* smp_mb(); */ + return 0; + } + return -ENOMEM; +} + +void au_wh_name_free(struct qstr *wh) +{ + AuDebugOn(!wh || !wh->name); + kfree(wh->name); +} + +/* ---------------------------------------------------------------------- */ + +/* + * test if the @wh_name exists under @h_parent. + * @try_sio specifies the necessary of super-io. + */ +int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio, + struct au_ndx *ndx) +{ + int err; + struct dentry *wh_dentry; + struct inode *h_dir; + unsigned int flags; + + LKTRTrace("%.*s/%.*s, ndx{%p, 0x%x}\n", AuDLNPair(h_parent), + wh_name->len, wh_name->name, ndx->nfsmnt, ndx->flags); + h_dir = h_parent->d_inode; + AuDebugOn(!S_ISDIR(h_dir->i_mode)); + + flags = 0; + if (ndx && ndx->nd) { + flags = ndx->nd->flags; + ndx->nd->flags &= ~(LOOKUP_OPEN | LOOKUP_CREATE); + } + + if (!try_sio) + wh_dentry = au_lkup_one(wh_name->name, h_parent, + wh_name->len, ndx); + else + wh_dentry = au_sio_lkup_one(wh_name->name, h_parent, + wh_name->len, ndx); + if (flags) + ndx->nd->flags = flags; + err = PTR_ERR(wh_dentry); + if (IS_ERR(wh_dentry)) + goto out; + + err = 0; + if (!wh_dentry->d_inode) + goto out_wh; /* success */ + + err = 1; + if (S_ISREG(wh_dentry->d_inode->i_mode)) + goto out_wh; /* success */ + + err = -EIO; + AuIOErr("%.*s Invalid whiteout entry type 0%o.\n", + AuDLNPair(wh_dentry), wh_dentry->d_inode->i_mode); + + out_wh: + dput(wh_dentry); + out: + AuTraceErr(err); + return err; +} + +/* + * test if the @h_dentry sets opaque or not. + */ +int au_diropq_test(struct dentry *h_dentry, struct au_ndx *ndx) +{ + int err, try_sio; + struct inode *h_dir; + + LKTRTrace("dentry %.*s\n", AuDLNPair(h_dentry)); + h_dir = h_dentry->d_inode; + AuDebugOn(!S_ISDIR(h_dir->i_mode)); + + try_sio = au_test_h_perm_sio(h_dir, MAY_EXEC, + au_ftest_ndx(ndx->flags, DLGT)); + err = au_wh_test(h_dentry, &diropq_name, try_sio, ndx); + AuTraceErr(err); + return err; +} + +/* + * returns a negative dentry whose name is unique and temporary. + */ +struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct qstr *prefix, + struct au_ndx *ndx) +{ +#define HEX_LEN 4 + struct dentry *dentry; + int len, i; + char defname[AUFS_WH_PFX_LEN * 2 + DNAME_INLINE_LEN_MIN + 1 + + HEX_LEN + 1], *name, *p; + static unsigned char cnt; + + LKTRTrace("hp %.*s, prefix %.*s\n", + AuDLNPair(h_parent), prefix->len, prefix->name); + AuDebugOn(!h_parent->d_inode); + + name = defname; + len = sizeof(defname) - DNAME_INLINE_LEN_MIN + prefix->len - 1; + if (unlikely(prefix->len > DNAME_INLINE_LEN_MIN)) { + dentry = ERR_PTR(-ENAMETOOLONG); + if (unlikely(len >= PATH_MAX)) + goto out; + dentry = ERR_PTR(-ENOMEM); + name = kmalloc(len + 1, GFP_NOFS); + if (unlikely(!name)) + goto out; + } + + /* doubly whiteout-ed */ + memcpy(name, AUFS_WH_PFX AUFS_WH_PFX, AUFS_WH_PFX_LEN * 2); + p = name + AUFS_WH_PFX_LEN * 2; + memcpy(p, prefix->name, prefix->len); + p += prefix->len; + *p++ = '.'; + AuDebugOn(name + len + 1 - p <= HEX_LEN); + + for (i = 0; i < 3; i++) { + sprintf(p, "%.*d", HEX_LEN, cnt++); + dentry = au_sio_lkup_one(name, h_parent, len, ndx); + if (IS_ERR(dentry) || !dentry->d_inode) + goto out_name; + dput(dentry); + } + /* AuWarn("could not get random name\n"); */ + dentry = ERR_PTR(-EEXIST); + AuDbg("%.*s\n", len, name); + BUG(); + + out_name: + if (name != defname) + kfree(name); + out: + AuTraceErrPtr(dentry); + return dentry; +#undef HEX_LEN +} + +/* + * rename the @dentry of @bindex to the whiteouted temporary name. + */ +int au_whtmp_ren(struct inode *dir, aufs_bindex_t bindex, + struct dentry *h_dentry) +{ + int err, dlgt; + struct inode *h_dir; + struct dentry *h_parent, *tmp_dentry; + struct super_block *sb; + unsigned int mnt_flags; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct au_ndx ndx = { + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + + LKTRTrace("%.*s\n", AuDLNPair(h_dentry)); + AuDebugOn(!h_dentry->d_inode); + h_parent = h_dentry->d_parent; /* dir inode is locked */ + h_dir = h_parent->d_inode; + IMustLock(h_dir); + + sb = dir->i_sb; + mnt_flags = au_mntflags(sb); + dlgt = !!au_test_dlgt(mnt_flags); + if (dlgt) + au_fset_ndx(ndx.flags, DLGT); + ndx.nfsmnt = au_nfsmnt(sb, bindex); + tmp_dentry = au_whtmp_lkup(h_parent, &h_dentry->d_name, &ndx); + err = PTR_ERR(tmp_dentry); + if (IS_ERR(tmp_dentry)) + goto out; + + /* under the same dir, no need to lock_rename() */ + vfsub_args_init(&vargs, &ign, dlgt, 0); + AuDebugOn(!S_ISDIR(h_dentry->d_inode->i_mode)); + vfsub_ign_hinode(&vargs, IN_MOVED_FROM | IN_MOVED_TO, + au_hi(dir, bindex)); + err = vfsub_rename(h_dir, h_dentry, h_dir, tmp_dentry, &vargs); + AuTraceErr(err); + dput(tmp_dentry); + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static int do_unlink_wh(struct au_hinode *hdir, struct inode *h_dir, + struct dentry *wh_dentry, const int dlgt) +{ + int err; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + AuDebugOn(hdir && h_dir); + AuDebugOn(!hdir && !h_dir); + if (!h_dir) + h_dir = hdir->hi_inode; + LKTRTrace("hi%lu, wh %.*s\n", h_dir->i_ino, AuDLNPair(wh_dentry)); + AuDebugOn(!wh_dentry->d_inode || !S_ISREG(wh_dentry->d_inode->i_mode)); + + /* + * forces superio when the dir has a sticky bit. + * this may be a violation of unix fs semantics. + */ + vfsub_args_init(&vargs, &ign, dlgt, + (h_dir->i_mode & S_ISVTX) + && wh_dentry->d_inode->i_uid != current->fsuid); + vfsub_ign_hinode(&vargs, IN_DELETE, hdir); + err = vfsub_unlink(h_dir, wh_dentry, &vargs); + AuTraceErr(err); + return err; +} + +int au_wh_unlink_dentry(struct au_hinode *hdir, struct dentry *wh_dentry, + struct dentry *dentry, int dlgt) +{ + int err; + + LKTRTrace("i%lu, wh %.*s, d %p\n", + hdir->hi_inode->i_ino, AuDLNPair(wh_dentry), dentry); + AuDebugOn((dentry && au_dbwh(dentry) < 0) + || !wh_dentry->d_inode + || !S_ISREG(wh_dentry->d_inode->i_mode)); + + err = do_unlink_wh(hdir, /*h_dir*/NULL, wh_dentry, dlgt); + if (!err && dentry) + au_set_dbwh(dentry, -1); + + AuTraceErr(err); + return err; +} + +static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh, + struct au_ndx *ndx) +{ + int err; + struct dentry *wh_dentry; + + LKTRTrace("%.*s/%.*s\n", AuDLNPair(h_parent), AuLNPair(wh)); + + /* au_test_h_perm() is already done */ + wh_dentry = au_lkup_one(wh->name, h_parent, wh->len, ndx); + if (IS_ERR(wh_dentry)) + err = PTR_ERR(wh_dentry); + else { + err = 0; + if (wh_dentry->d_inode && S_ISREG(wh_dentry->d_inode->i_mode)) + err = do_unlink_wh(/*hdir*/NULL, h_parent->d_inode, + wh_dentry, + au_ftest_ndx(ndx->flags, DLGT)); + dput(wh_dentry); + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static void clean_wh(struct inode *h_dir, struct path *whpath, + struct au_hinode *hdir, struct vfsub_args *vargs) +{ + int err; + + AuTraceEnter(); + + if (!whpath->dentry->d_inode) + return; + + err = au_mnt_want_write(whpath->mnt); + if (!err) { + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_DELETE, hdir); + err = vfsub_unlink(h_dir, whpath->dentry, vargs); + au_mnt_drop_write(whpath->mnt); + } + if (unlikely(err)) + AuWarn("failed unlink %.*s (%d), ignored.\n", + AuDLNPair(whpath->dentry), err); +} + +static void au_whdir_clean(struct inode *h_dir, struct path *whpath, + struct au_hinode *hdir, struct vfsub_args *vargs) +{ + int err; + + AuTraceEnter(); + + if (!whpath->dentry->d_inode) + return; + + err = au_mnt_want_write(whpath->mnt); + if (!err) { + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_DELETE, hdir); + err = vfsub_rmdir(h_dir, whpath->dentry, vargs); + au_mnt_drop_write(whpath->mnt); + } + if (unlikely(err)) + AuWarn("failed rmdir %.*s (%d), ignored.\n", + AuDLNPair(whpath->dentry), err); +} + +static int test_linkable(struct inode *h_dir) +{ + if (h_dir->i_op && h_dir->i_op->link) + return 0; + return -ENOSYS; +} + +/* todo: should this mkdir be done in /sbin/mount.aufs script? */ +static int au_whdir(struct inode *h_dir, struct path *path, + struct au_hinode *hdir, struct vfsub_args *vargs) +{ + int err; + + err = -EEXIST; + if (!path->dentry->d_inode) { + int mode = S_IRWXU; + if (au_test_nfs(path->dentry->d_sb)) + mode |= S_IXUGO; + err = au_mnt_want_write(path->mnt); + if (!err) { + vfsub_args_reinit(vargs); + vfsub_ign_hinode(vargs, IN_CREATE, hdir); + err = vfsub_mkdir(h_dir, path->dentry, mode, vargs); + au_mnt_drop_write(path->mnt); + } + } else if (S_ISDIR(path->dentry->d_inode->i_mode)) + err = 0; + else + AuErr("unknown %.*s exists\n", AuDLNPair(path->dentry)); + + return err; +} + +/* + * initialize the whiteout base file/dir for @br. + */ +int au_wh_init(struct dentry *h_root, struct au_branch *br, + struct vfsmount *h_mnt, struct super_block *sb, + aufs_bindex_t bindex) +{ + int err, i; + const unsigned int mnt_flags = au_mntflags(sb); + const unsigned char do_plink = !!au_opt_test(mnt_flags, PLINK), + do_hinotify = au_opt_test(mnt_flags, UDBA_INOTIFY); + struct path path = { + .mnt = h_mnt + }; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct inode *h_dir; + struct au_hinode *hdir; + struct au_wbr *wbr = br->br_wbr; + struct vfsmount *nfsmnt = au_do_nfsmnt(h_mnt); + static const struct qstr base_name[] = { + [AuBrWh_BASE] = { + .name = AUFS_BASE_NAME, + .len = sizeof(AUFS_BASE_NAME) - 1 + }, + [AuBrWh_PLINK] = { + .name = AUFS_PLINKDIR_NAME, + .len = sizeof(AUFS_PLINKDIR_NAME) - 1 + }, + [AuBrWh_TMP] = { + .name = AUFS_TMPDIR_NAME, + .len = sizeof(AUFS_TMPDIR_NAME) - 1 + } + }; + struct { + const struct qstr *name; + struct dentry *dentry; + } base[] = { + [AuBrWh_BASE] = { + .name = base_name + AuBrWh_BASE, + .dentry = NULL + }, + [AuBrWh_PLINK] = { + .name = base_name + AuBrWh_PLINK, + .dentry = NULL + }, + [AuBrWh_TMP] = { + .name = base_name + AuBrWh_TMP, + .dentry = NULL + } + }; + struct au_ndx ndx = { + .nfsmnt = nfsmnt, + .flags = 0, /* always no dlgt */ + .nd = NULL, + /* .br = NULL */ + }; + + LKTRTrace("nfsmnt %p\n", nfsmnt); + WbrWhMustWriteLock(wbr); + SiMustWriteLock(sb); + h_dir = h_root->d_inode; + + for (i = 0; i < AuBrWh_Last; i++) { + /* doubly whiteouted */ + struct dentry *d; + d = au_wh_lkup(h_root, (void *)base[i].name, &ndx); + err = PTR_ERR(d); + if (IS_ERR(d)) + goto out; + base[i].dentry = d; + if (!au_test_ecryptfs(d->d_sb)) + AuDebugOn(wbr + && wbr->wbr_wh[i] + && wbr->wbr_wh[i] != base[i].dentry); + else + /* ecryptfs problem?: it returns different dentry */ + AuDebugOn(wbr + && wbr->wbr_wh[i] + && !!wbr->wbr_wh[i] != !!base[i].dentry); + } + + if (wbr) + for (i = 0; i < AuBrWh_Last; i++) { + dput(wbr->wbr_wh[i]); + wbr->wbr_wh[i] = NULL; + } + + err = 0; + hdir = NULL; + if (bindex >= 0 && do_hinotify) + hdir = au_hi(sb->s_root->d_inode, bindex); + vfsub_args_init(&vargs, &ign, au_test_dlgt(mnt_flags), 0); + + switch (br->br_perm) { + case AuBrPerm_RR: + case AuBrPerm_RO: + case AuBrPerm_RRWH: + case AuBrPerm_ROWH: + path.dentry = base[AuBrWh_BASE].dentry; + clean_wh(h_dir, &path, hdir, &vargs); + path.dentry = base[AuBrWh_PLINK].dentry; + au_whdir_clean(h_dir, &path, hdir, &vargs); + path.dentry = base[AuBrWh_TMP].dentry; + au_whdir_clean(h_dir, &path, hdir, &vargs); + break; + + case AuBrPerm_RWNoLinkWH: + path.dentry = base[AuBrWh_BASE].dentry; + clean_wh(h_dir, &path, hdir, &vargs); + path.dentry = base[AuBrWh_PLINK].dentry; + if (do_plink) { + err = test_linkable(h_dir); + if (unlikely(err)) + goto out_nolink; + + err = au_whdir(h_dir, &path, hdir, &vargs); + if (unlikely(err)) + goto out_err; + wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry); + } else + au_whdir_clean(h_dir, &path, hdir, &vargs); + path.dentry = base[AuBrWh_TMP].dentry; + err = au_whdir(h_dir, &path, hdir, &vargs); + if (unlikely(err)) + goto out_err; + wbr->wbr_tmp = dget(base[AuBrWh_TMP].dentry); + break; + + case AuBrPerm_RW: + /* + * for the moment, aufs supports the branch filesystem + * which does not support link(2). + * testing on FAT which does not support i_op->setattr() fully + * either, copyup failed. + * finally, such filesystem will not be used as the writable + * branch. + */ + err = test_linkable(h_dir); + if (unlikely(err)) + goto out_nolink; + + err = -EEXIST; + /* + * todo: should this create be done + * in /sbin/mount.aufs script? + */ + if (!base[AuBrWh_BASE].dentry->d_inode) { + err = au_mnt_want_write(h_mnt); + if (!err) { + vfsub_args_reinit(&vargs); + vfsub_ign_hinode(&vargs, IN_CREATE, hdir); + err = au_h_create + (h_dir, base[AuBrWh_BASE].dentry, + WH_MASK, &vargs, /*nd*/NULL, nfsmnt); + au_mnt_drop_write(h_mnt); + } + } else if (S_ISREG(base[AuBrWh_BASE].dentry->d_inode->i_mode)) + err = 0; + else + AuErr("unknown %.*s/%.*s exists\n", + AuDLNPair(h_root), + AuDLNPair(base[AuBrWh_BASE].dentry)); + if (unlikely(err)) + goto out_err; + + path.dentry = base[AuBrWh_PLINK].dentry; + if (do_plink) { + err = au_whdir(h_dir, &path, hdir, &vargs); + if (unlikely(err)) + goto out_err; + wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry); + } else + au_whdir_clean(h_dir, &path, hdir, &vargs); + wbr->wbr_whbase = dget(base[AuBrWh_BASE].dentry); + + path.dentry = base[AuBrWh_TMP].dentry; + err = au_whdir(h_dir, &path, hdir, &vargs); + if (unlikely(err)) + goto out_err; + wbr->wbr_tmp = dget(base[AuBrWh_TMP].dentry); + break; + + default: + BUG(); + } + + out: + for (i = 0; i < AuBrWh_Last; i++) + dput(base[i].dentry); + AuTraceErr(err); + return err; + out_nolink: + AuErr("%.*s doesn't support link(2), use noplink and rw+nolwh\n", + AuDLNPair(h_root)); + goto out; + out_err: + AuErr("an error(%d) on the writable branch %.*s(%s)\n", + err, AuDLNPair(h_root), au_sbtype(h_root->d_sb)); + goto out; +} + +struct reinit_br_wh { + struct super_block *sb; + struct au_branch *br; +}; + +static void reinit_br_wh(void *arg) +{ + int err; + struct reinit_br_wh *a = arg; + struct au_wbr *wbr; + struct inode *h_dir, *dir; + struct dentry *h_root; + aufs_bindex_t bindex; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + AuTraceEnter(); + AuDebugOn(current->fsuid); + + err = 0; + wbr = a->br->br_wbr; + /* big aufs lock */ + si_noflush_write_lock(a->sb); + if (!au_br_writable(a->br->br_perm)) + goto out; + bindex = au_br_index(a->sb, a->br->br_id); + if (unlikely(bindex < 0)) + goto out; + + AuDebugOn(!wbr); + AuDebugOn(!wbr->wbr_whbase || !wbr->wbr_whbase->d_inode); + + dir = a->sb->s_root->d_inode; + ii_read_lock_parent(dir); + h_root = dget_parent(wbr->wbr_whbase); + h_dir = h_root->d_inode; + AuDebugOn(!h_dir->i_op || !h_dir->i_op->link); + vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT); + wbr_wh_write_lock(wbr); + if (!au_verify_parent(wbr->wbr_whbase, h_dir)) { + err = au_br_want_write(a->br); + if (!err) { + vfsub_args_init(&vargs, &ign, /*dlgt*/0, 0); + vfsub_ign_hinode(&vargs, IN_DELETE, au_hi(dir, bindex)); + err = vfsub_unlink(h_dir, wbr->wbr_whbase, &vargs); + au_br_drop_write(a->br); + } + } else { + AuWarn("%.*s is moved, ignored\n", AuDLNPair(wbr->wbr_whbase)); + err = 0; + } + dput(wbr->wbr_whbase); + wbr->wbr_whbase = NULL; + if (!err) + err = au_wh_init(h_root, a->br, a->br->br_mnt, a->sb, bindex); + wbr_wh_write_unlock(wbr); + vfsub_i_unlock(h_dir); + dput(h_root); + ii_read_unlock(dir); + + out: + if (wbr) + atomic_dec_return(&wbr->wbr_wh_running); + au_br_put(a->br); + au_nwt_done(&au_sbi(a->sb)->si_nowait); + si_write_unlock(a->sb); + kfree(arg); + if (unlikely(err)) + AuIOErr("err %d\n", err); +} + +static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br) +{ + int do_dec, wkq_err; + struct reinit_br_wh *arg; + + AuTraceEnter(); + AuDebugOn(!br->br_wbr); + + do_dec = 1; + if (atomic_inc_return(&br->br_wbr->wbr_wh_running) != 1) + goto out; + + /* ignore ENOMEM */ + arg = kmalloc(sizeof(*arg), GFP_NOFS); + if (arg) { + /* + * dec(wh_running), kfree(arg) and au_br_put() + * in reinit function + */ + arg->sb = sb; + arg->br = br; + au_br_get(br); + wkq_err = au_wkq_nowait(reinit_br_wh, arg, sb, /*dlgt*/0); + if (unlikely(wkq_err)) { + atomic_dec_return(&br->br_wbr->wbr_wh_running); + au_br_put(br); + kfree(arg); + } + do_dec = 0; + } + + out: + if (do_dec) + atomic_dec_return(&br->br_wbr->wbr_wh_running); +} + +/* + * create the whiteout @wh. + */ +static int link_or_create_wh(struct super_block *sb, aufs_bindex_t bindex, + struct dentry *wh, struct inode *dir) +{ + int err, dlgt; + struct au_branch *br; + struct au_wbr *wbr; + struct dentry *h_parent; + struct inode *h_dir; + struct au_hin_ignore ign; + struct vfsub_args vargs; + + LKTRTrace("%.*s\n", AuDLNPair(wh)); + h_parent = wh->d_parent; /* dir inode is locked */ + h_dir = h_parent->d_inode; + IMustLock(h_dir); + br = au_sbr(sb, bindex); + wbr = br->br_wbr; + AuDebugOn(!wbr); + + dlgt = !!au_test_dlgt(au_mntflags(sb)); + wbr_wh_read_lock(wbr); + if (wbr->wbr_whbase) { + vfsub_args_init(&vargs, &ign, dlgt, 0); + if (dir) + vfsub_ign_hinode(&vargs, IN_CREATE, au_hi(dir, bindex)); + err = vfsub_link(wbr->wbr_whbase, h_dir, wh, &vargs); + if (!err || err != -EMLINK) + goto out; + + /* link count full. re-initialize br_whbase. */ + kick_reinit_br_wh(sb, br); + } + + /* return this error in this context */ + vfsub_args_init(&vargs, &ign, dlgt, 0); + if (dir) + vfsub_ign_hinode(&vargs, IN_CREATE, au_hi(dir, bindex)); + err = au_h_create(h_dir, wh, WH_MASK, &vargs, /*nd*/NULL, + au_do_nfsmnt(br->br_mnt)); + + out: + wbr_wh_read_unlock(wbr); + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* + * create or remove the diropq. + */ +static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex, + unsigned int flags) +{ + struct dentry *opq_dentry, *h_dentry; + struct inode *h_dir; + int err, dlgt; + struct super_block *sb; + struct au_ndx ndx = { + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + + LKTRTrace("%.*s, bindex %d, flags 0x%x\n", + AuDLNPair(dentry), bindex, flags); + h_dentry = au_h_dptr(dentry, bindex); + AuDebugOn(!h_dentry); + h_dir = h_dentry->d_inode; + AuDebugOn(!h_dir || !S_ISDIR(h_dir->i_mode)); + + /* already checked by au_test_h_perm(). */ + sb = dentry->d_sb; + ndx.nfsmnt = au_nfsmnt(sb, bindex); + dlgt = 0; + if (au_ftest_diropq(flags, DLGT)) { + dlgt = 1; + au_fset_ndx(ndx.flags, DLGT); + } + opq_dentry = au_lkup_one(diropq_name.name, h_dentry, diropq_name.len, + &ndx); + if (IS_ERR(opq_dentry)) + goto out; + + if (au_ftest_diropq(flags, CREATE)) { + AuDebugOn(opq_dentry->d_inode); + err = link_or_create_wh(dentry->d_sb, bindex, opq_dentry, + dentry->d_inode); + if (!err) { + au_set_dbdiropq(dentry, bindex); + goto out; /* success */ + } + } else { + AuDebugOn(/* !S_ISDIR(dentry->d_inode->i_mode) + * || */!opq_dentry->d_inode); + err = do_unlink_wh(au_hi(dentry->d_inode, bindex), + /*h_dir*/NULL, opq_dentry, dlgt); + if (!err) + au_set_dbdiropq(dentry, -1); + } + dput(opq_dentry); + opq_dentry = ERR_PTR(err); + + out: + AuTraceErrPtr(opq_dentry); + return opq_dentry; +} + +struct do_diropq_args { + struct dentry **errp; + struct dentry *dentry; + aufs_bindex_t bindex; + unsigned int flags; +}; + +static void call_do_diropq(void *args) +{ + struct do_diropq_args *a = args; + *a->errp = do_diropq(a->dentry, a->bindex, a->flags); +} + +struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex, + unsigned int flags) +{ + struct dentry *diropq, *h_dentry; + + LKTRTrace("%.*s, bindex %d, flags 0x%x\n", + AuDLNPair(dentry), bindex, flags); + + h_dentry = au_h_dptr(dentry, bindex); + if (!au_test_h_perm_sio(h_dentry->d_inode, MAY_EXEC | MAY_WRITE, + au_ftest_diropq(flags, DLGT))) + diropq = do_diropq(dentry, bindex, flags); + else { + int wkq_err; + struct do_diropq_args args = { + .errp = &diropq, + .dentry = dentry, + .bindex = bindex, + .flags = flags + }; + wkq_err = au_wkq_wait(call_do_diropq, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + diropq = ERR_PTR(wkq_err); + } + + AuTraceErrPtr(diropq); + return diropq; +} + +/* ---------------------------------------------------------------------- */ + +/* + * lookup whiteout dentry. + * @h_parent: hidden parent dentry which must exist and be locked + * @base_name: name of dentry which will be whiteouted + * returns dentry for whiteout. + */ +struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name, + struct au_ndx *ndx) +{ + int err; + struct qstr wh_name; + struct dentry *wh_dentry; + + LKTRTrace("%.*s/%.*s\n", AuDLNPair(h_parent), AuLNPair(base_name)); + + err = au_wh_name_alloc(base_name->name, base_name->len, &wh_name); + wh_dentry = ERR_PTR(err); + if (!err) { + /* do not superio. */ + wh_dentry = au_lkup_one(wh_name.name, h_parent, + wh_name.len, ndx); + au_wh_name_free(&wh_name); + } + AuTraceErrPtr(wh_dentry); + return wh_dentry; +} + +/* + * link/create a whiteout for @dentry on @bindex. + */ +struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_parent, struct au_ndx *ndx) +{ + struct dentry *wh_dentry; + struct inode *dir; + int err; + struct super_block *sb; + + LKTRTrace("%.*s/%.*s on b%d\n", AuDLNPair(h_parent), + AuDLNPair(dentry), bindex); + + sb = dentry->d_sb; + wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, ndx); + if (!IS_ERR(wh_dentry) && !wh_dentry->d_inode) { + dir = dentry->d_parent->d_inode; /* dir is locked */ + IMustLock(dir); + err = link_or_create_wh(sb, bindex, wh_dentry, dir); + if (!err) + au_set_dbwh(dentry, bindex); + else { + dput(wh_dentry); + wh_dentry = ERR_PTR(err); + } + } + + AuTraceErrPtr(wh_dentry); + return wh_dentry; +} + +/* ---------------------------------------------------------------------- */ + +/* Delete all whiteouts in this directory on branch bindex. */ +static int del_wh_children(struct dentry *h_dentry, struct au_nhash *whlist, + aufs_bindex_t bindex, struct au_ndx *ndx) +{ + int err, i; + struct qstr wh_name; + char *p; + struct inode *h_inode; + struct hlist_head *head; + struct au_vdir_wh *tpos; + struct hlist_node *pos; + struct au_vdir_destr *str; + + LKTRTrace("%.*s\n", AuDLNPair(h_dentry)); + h_inode = h_dentry->d_inode; + AuDebugOn(IS_RDONLY(h_inode)); + + err = -ENOMEM; + p = __getname(); + wh_name.name = p; + if (unlikely(!wh_name.name)) + goto out; + memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN); + p += AUFS_WH_PFX_LEN; + + /* already checked by au_test_h_perm(). */ + err = 0; + for (i = 0; !err && i < AuSize_NHASH; i++) { + head = whlist->heads + i; + hlist_for_each_entry(tpos, pos, head, wh_hash) { + if (tpos->wh_bindex != bindex) + continue; + str = &tpos->wh_str; + if (str->len + AUFS_WH_PFX_LEN <= PATH_MAX) { + memcpy(p, str->name, str->len); + wh_name.len = AUFS_WH_PFX_LEN + str->len; + err = unlink_wh_name(h_dentry, &wh_name, ndx); + if (!err) + continue; + break; + } + AuIOErr("whiteout name too long %.*s\n", + str->len, str->name); + err = -EIO; + break; + } + } + __putname(wh_name.name); + + out: + AuTraceErr(err); + return err; +} + +struct del_wh_children_args { + int *errp; + struct dentry *h_dentry; + struct au_nhash *whlist; + aufs_bindex_t bindex; + struct au_ndx *ndx; +}; + +static void call_del_wh_children(void *args) +{ + struct del_wh_children_args *a = args; + *a->errp = del_wh_children(a->h_dentry, a->whlist, a->bindex, a->ndx); +} + +/* ---------------------------------------------------------------------- */ + +/* + * rmdir the whiteouted temporary named dir @h_dentry. + * @whlist: whiteouted children. + */ +int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex, + struct dentry *wh_dentry, struct au_nhash *whlist) +{ + int err, dlgt; + struct inode *wh_inode, *h_dir; + struct super_block *sb; + unsigned int mnt_flags; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct au_ndx ndx = { + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + + LKTRTrace("i%lu, %.*s, b%d\n", + dir->i_ino, AuDLNPair(wh_dentry), bindex); + /* IMustLock(dir); */ + IiMustAnyLock(dir); + h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */ + IMustLock(h_dir); + + sb = dir->i_sb; + mnt_flags = au_mntflags(sb); + dlgt = !!au_test_dlgt(mnt_flags); + if (dlgt) + au_fset_ndx(ndx.flags, DLGT); + ndx.nfsmnt = au_nfsmnt(sb, bindex); + wh_inode = wh_dentry->d_inode; + vfsub_i_lock_nested(wh_inode, AuLsc_I_CHILD); + + /* + * someone else might change some whiteouts while we were sleeping. + * it means this whlist may have an obsoleted entry. + */ + if (!au_test_h_perm_sio(wh_inode, MAY_EXEC | MAY_WRITE, dlgt)) + err = del_wh_children(wh_dentry, whlist, bindex, &ndx); + else { + int wkq_err; + /* ugly */ + unsigned int flags = ndx.flags; + struct del_wh_children_args args = { + .errp = &err, + .h_dentry = wh_dentry, + .whlist = whlist, + .bindex = bindex, + .ndx = &ndx + }; + + ndx.flags = 0; + wkq_err = au_wkq_wait(call_del_wh_children, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + ndx.flags = flags; + } + vfsub_i_unlock(wh_inode); + + if (!err) { + vfsub_args_init(&vargs, &ign, dlgt, 0); + vfsub_ign_hinode(&vargs, IN_DELETE, au_hi(dir, bindex)); + err = vfsub_rmdir(h_dir, wh_dentry, &vargs); + /* d_drop(h_dentry); */ + } + + if (!err) { + if (au_ibstart(dir) == bindex) { + au_cpup_attr_timesizes(dir); + /* au_cpup_attr_nlink(dir); */ + dir->i_nlink--; + } + return 0; /* success */ + } + + AuWarn("failed removing %.*s(%d), ignored\n", + AuDLNPair(wh_dentry), err); + return err; +} + +static void au_whtmp_rmdir_free_args(struct au_whtmp_rmdir_args *args) +{ + au_nhash_fin(&args->whlist); + dput(args->wh_dentry); + iput(args->dir); + kfree(args); +} + +static void call_rmdir_whtmp(void *args) +{ + int err; + struct au_whtmp_rmdir_args *a = args; + struct super_block *sb; + struct dentry *h_parent; + struct inode *h_dir; + + LKTRTrace("%.*s, b%d, dir i%lu\n", + AuDLNPair(a->wh_dentry), a->bindex, a->dir->i_ino); + + /* rmdir by nfsd may cause deadlock with this i_mutex */ + /* vfsub_i_lock(a->dir); */ + sb = a->dir->i_sb; + si_noflush_read_lock(sb); + err = au_test_ro(sb, a->bindex, NULL); + if (unlikely(err)) + goto out; + + err = -EIO; + ii_write_lock_parent(a->dir); + h_parent = dget_parent(a->wh_dentry); + h_dir = h_parent->d_inode; + vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT); + if (!au_verify_parent(a->wh_dentry, h_dir)) { + err = au_br_want_write(au_sbr(sb, a->bindex)); + if (!err) { + err = au_whtmp_rmdir(a->dir, a->bindex, a->wh_dentry, + &a->whlist); + au_br_drop_write(au_sbr(sb, a->bindex)); + } + } + vfsub_i_unlock(h_dir); + dput(h_parent); + ii_write_unlock(a->dir); + + out: + /* vfsub_i_unlock(a->dir); */ + au_nwt_done(&au_sbi(sb)->si_nowait); + si_read_unlock(sb); + au_whtmp_rmdir_free_args(a); + if (unlikely(err)) + AuIOErr("err %d\n", err); +} + +void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex, + struct dentry *wh_dentry, struct au_nhash *whlist, + struct au_whtmp_rmdir_args *args) +{ + int wkq_err; + + LKTRTrace("%.*s\n", AuDLNPair(wh_dentry)); + IMustLock(dir); + + /* all post-process will be done in do_rmdir_whtmp(). */ + args->dir = au_igrab(dir); + args->bindex = bindex; + args->wh_dentry = dget(wh_dentry); + au_nhash_init(&args->whlist); + au_nhash_move(&args->whlist, whlist); + wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, dir->i_sb, /*dlgt*/0); + if (unlikely(wkq_err)) { + AuWarn("rmdir error %.*s (%d), ignored\n", + AuDLNPair(wh_dentry), wkq_err); + au_whtmp_rmdir_free_args(args); + } +} diff --git a/fs/aufs/whout.h b/fs/aufs/whout.h new file mode 100644 index 0000000..056ca13 --- /dev/null +++ b/fs/aufs/whout.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * whiteout for logical deletion and opaque directory + * + * $Id: whout.h,v 1.20 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_WHOUT_H__ +#define __AUFS_WHOUT_H__ + +#ifdef __KERNEL__ + +#include +#include + +int au_wh_name_alloc(const char *name, int len, struct qstr *wh); +void au_wh_name_free(struct qstr *wh); + +struct au_ndx; +int au_wh_test(struct dentry *h_parent, struct qstr *wh_name, int try_sio, + struct au_ndx *ndx); +int au_diropq_test(struct dentry *h_dentry, struct au_ndx *ndx); + +struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct qstr *prefix, + struct au_ndx *ndx); +int au_whtmp_ren(struct inode *dir, aufs_bindex_t bindex, + struct dentry *h_dentry); +int au_wh_unlink_dentry(struct au_hinode *dir, struct dentry *wh_dentry, + struct dentry *dentry, int dlgt); + +struct au_branch; +int au_wh_init(struct dentry *h_parent, struct au_branch *br, + struct vfsmount *nfsmnt, struct super_block *sb, + aufs_bindex_t bindex); + +/* diropq flags */ +#define AuDiropq_CREATE 1 +#define AuDiropq_DLGT (1 << 1) +#define au_ftest_diropq(flags, name) ((flags) & AuDiropq_##name) +#define au_fset_diropq(flags, name) { (flags) |= AuDiropq_##name; } +#define au_fclr_diropq(flags, name) { (flags) &= ~AuDiropq_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuDiropq_DLGT +#define AuDiropq_DLGT 0 +#endif + +struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex, + unsigned int flags); + +struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name, + struct au_ndx *ndx); +struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex, + struct dentry *h_parent, struct au_ndx *ndx); + +/* real rmdir the whiteout-ed dir */ +struct au_whtmp_rmdir_args { + struct inode *dir; + aufs_bindex_t bindex; + struct dentry *wh_dentry; + struct au_nhash whlist; +}; + +struct au_nhash; +int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex, + struct dentry *wh_dentry, struct au_nhash *whlist); +void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex, + struct dentry *wh_dentry, struct au_nhash *whlist, + struct au_whtmp_rmdir_args *args); + +/* ---------------------------------------------------------------------- */ + +static inline +struct dentry *au_diropq_create(struct dentry *dentry, aufs_bindex_t bindex, + int dlgt) +{ + unsigned int flags = AuDiropq_CREATE; + if (dlgt) + au_fset_diropq(flags, DLGT); + return au_diropq_sio(dentry, bindex, flags); +} + +static inline +int au_diropq_remove(struct dentry *dentry, aufs_bindex_t bindex, int dlgt) +{ + unsigned int flags = !AuDiropq_CREATE; + if (dlgt) + au_fset_diropq(flags, DLGT); + return PTR_ERR(au_diropq_sio(dentry, bindex, flags)); +} + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_ROBR +/* robr.c */ +int au_test_robr_wh(struct qstr *name, struct dentry *h_parent, + struct qstr *wh_name, int try_sio, struct au_ndx *ndx); +int au_test_robr_shwh(struct super_block *sb, const struct qstr *name); +#else +static inline +int au_test_robr_wh(struct qstr *name, struct dentry *h_parent, + struct qstr *wh_name, int try_sio, struct au_ndx *ndx) +{ + return au_wh_test(h_parent, wh_name, try_sio, ndx); +} + +static inline +int au_test_robr_shwh(struct super_block *sb, const struct qstr *name) +{ + if (unlikely(!au_opt_test(au_mntflags(sb), SHWH) + && !strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))) + return -EPERM; + return 0; +} +#endif /* CONFIG_AUFS_ROBR */ + +#endif /* __KERNEL__ */ +#endif /* __AUFS_WHOUT_H__ */ diff --git a/fs/aufs/wkq.c b/fs/aufs/wkq.c new file mode 100644 index 0000000..e770747 --- /dev/null +++ b/fs/aufs/wkq.c @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * workqueue for asynchronous/super-io/delegated operations + * + * $Id: wkq.c,v 1.40 2009/01/26 06:24:45 sfjro Exp $ + */ + +#include +#include "aufs.h" + +struct au_wkq *au_wkq; + +struct au_cred { +#ifdef CONFIG_AUFS_DLGT + int umask; + uid_t fsuid; + gid_t fsgid; + kernel_cap_t cap_effective, cap_inheritable, cap_permitted; +#if 0 /* reserved for future use */ + unsigned keep_capabilities:1; + struct user_struct *user; + struct fs_struct *fs; + struct nsproxy *nsproxy; +#endif +#endif +}; + +struct au_wkinfo { + struct work_struct wk; + struct super_block *sb; + + unsigned int flags; + struct au_cred cred; + + au_wkq_func_t func; + void *args; + + atomic_t *busyp; + struct completion *comp; +}; + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_DLGT +static void cred_store(struct au_cred *cred) +{ + cred->umask = current->fs->umask; + cred->fsuid = current->fsuid; + cred->fsgid = current->fsgid; + cred->cap_effective = current->cap_effective; + cred->cap_inheritable = current->cap_inheritable; + cred->cap_permitted = current->cap_permitted; +} + +static void cred_revert(struct au_cred *cred) +{ + AuDebugOn(!au_test_wkq(current)); + current->fs->umask = cred->umask; + current->fsuid = cred->fsuid; + current->fsgid = cred->fsgid; + current->cap_effective = cred->cap_effective; + current->cap_inheritable = cred->cap_inheritable; + current->cap_permitted = cred->cap_permitted; +} + +static void cred_switch(struct au_cred *old, struct au_cred *new) +{ + cred_store(old); + cred_revert(new); +} + +static void dlgt_cred_store(unsigned int flags, struct au_wkinfo *wkinfo) +{ + if (au_ftest_wkq(flags, DLGT)) + cred_store(&wkinfo->cred); +} + +static void dlgt_func(struct au_wkinfo *wkinfo) +{ + if (!au_ftest_wkq(wkinfo->flags, DLGT)) + wkinfo->func(wkinfo->args); + else { + struct au_cred cred; + cred_switch(&cred, &wkinfo->cred); + wkinfo->func(wkinfo->args); + cred_revert(&cred); + } +} +#else +static void dlgt_cred_store(unsigned int flags, struct au_wkinfo *wkinfo) +{ + /* empty */ +} + +static void dlgt_func(struct au_wkinfo *wkinfo) +{ + wkinfo->func(wkinfo->args); +} +#endif /* CONFIG_AUFS_DLGT */ + +/* ---------------------------------------------------------------------- */ + +static void update_busy(struct au_wkq *wkq, struct au_wkinfo *wkinfo) +{ +#ifdef CONFIG_AUFS_SYSAUFS + unsigned int new, old; + + do { + new = atomic_read(wkinfo->busyp); + old = atomic_read(&wkq->max_busy); + if (new <= old) + break; + } while (atomic_cmpxchg(&wkq->max_busy, old, new) == old); +#endif +} + +static int enqueue(struct au_wkq *wkq, struct au_wkinfo *wkinfo) +{ + AuTraceEnter(); + + wkinfo->busyp = &wkq->busy; + update_busy(wkq, wkinfo); + if (au_ftest_wkq(wkinfo->flags, WAIT)) + return !queue_work(wkq->q, &wkinfo->wk); + else + return !schedule_work(&wkinfo->wk); +} + +static void do_wkq(struct au_wkinfo *wkinfo) +{ + unsigned int idle, n; + int i, idle_idx; + + AuTraceEnter(); + + while (1) { + if (au_ftest_wkq(wkinfo->flags, WAIT)) { + idle_idx = 0; + idle = UINT_MAX; + for (i = 0; i < aufs_nwkq; i++) { + n = atomic_inc_return(&au_wkq[i].busy); + if (n == 1 && !enqueue(au_wkq + i, wkinfo)) + return; /* success */ + + if (n < idle) { + idle_idx = i; + idle = n; + } + atomic_dec_return(&au_wkq[i].busy); + } + } else + idle_idx = aufs_nwkq; + + atomic_inc_return(&au_wkq[idle_idx].busy); + if (!enqueue(au_wkq + idle_idx, wkinfo)) + return; /* success */ + + /* impossible? */ + AuWarn1("failed to queue_work()\n"); + yield(); + } +} + +static AuWkqFunc(wkq_func, wk) +{ + struct au_wkinfo *wkinfo = container_of(wk, struct au_wkinfo, wk); + + LKTRTrace("wkinfo{0x%x, %p, %p, %p}\n", + wkinfo->flags, wkinfo->func, wkinfo->busyp, wkinfo->comp); + + dlgt_func(wkinfo); + atomic_dec_return(wkinfo->busyp); + if (au_ftest_wkq(wkinfo->flags, WAIT)) + complete(wkinfo->comp); + else { + au_mntput(wkinfo->sb); + module_put(THIS_MODULE); + kfree(wkinfo); + } +} + +#if defined(CONFIG_4KSTACKS) || defined(Test4KSTACKS) +#define AuWkqCompDeclare(name) struct completion *comp = NULL + +static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp) +{ + *comp = kmalloc(sizeof(**comp), GFP_NOFS); + if (*comp) { + init_completion(*comp); + wkinfo->comp = *comp; + return 0; + } + return -ENOMEM; +} + +static void au_wkq_comp_free(struct completion *comp) +{ + kfree(comp); +} + +#else + +/* no braces */ +#define AuWkqCompDeclare(name) \ + DECLARE_COMPLETION_ONSTACK(_ ## name); \ + struct completion *comp = &_ ## name + +static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp) +{ + wkinfo->comp = *comp; + return 0; +} + +static void au_wkq_comp_free(struct completion *comp) +{ + /* empty */ +} +#endif /* 4KSTACKS */ + +static void au_wkq_run(struct au_wkinfo *wkinfo) +{ +#if 1 /* tmp debug */ + if (au_test_wkq(current)) + au_dbg_blocked(); +#endif + AuDebugOn(au_test_wkq(current)); + + AuInitWkq(&wkinfo->wk, wkq_func); + dlgt_cred_store(wkinfo->flags, wkinfo); + do_wkq(wkinfo); +} + +int au_wkq_wait(au_wkq_func_t func, void *args, int dlgt) +{ + int err; + AuWkqCompDeclare(comp); + struct au_wkinfo wkinfo = { + .flags = AuWkq_WAIT, + .func = func, + .args = args + }; + + LKTRTrace("dlgt %d\n", dlgt); + + err = au_wkq_comp_alloc(&wkinfo, &comp); + if (!err) { + if (dlgt) + au_fset_wkq(wkinfo.flags, DLGT); + au_wkq_run(&wkinfo); + /* no timeout, no interrupt */ + wait_for_completion(wkinfo.comp); + au_wkq_comp_free(comp); + } + + AuTraceErr(err); + return err; + +} + +int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb, + int dlgt) +{ + int err; + struct au_wkinfo *wkinfo; + + LKTRTrace("dlgt %d\n", dlgt); + AuDebugOn(!sb); + + atomic_inc_return(&au_sbi(sb)->si_nowait.nw_len); + + /* + * wkq_func() must free this wkinfo. + * it highly depends upon the implementation of workqueue. + */ + err = 0; + wkinfo = kmalloc(sizeof(*wkinfo), GFP_NOFS); + if (wkinfo) { + wkinfo->sb = sb; + wkinfo->flags = !AuWkq_WAIT; + wkinfo->func = func; + wkinfo->args = args; + wkinfo->comp = NULL; + if (dlgt) + au_fset_wkq(wkinfo->flags, DLGT); + /* prohibit umount */ + __module_get(THIS_MODULE); + au_mntget(sb); + + au_wkq_run(wkinfo); + } else { + err = -ENOMEM; + atomic_dec_return(&au_sbi(sb)->si_nowait.nw_len); + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +void au_wkq_fin(void) +{ + int i; + + AuTraceEnter(); + + for (i = 0; i < aufs_nwkq; i++) + if (au_wkq[i].q && !IS_ERR(au_wkq[i].q)) + destroy_workqueue(au_wkq[i].q); + kfree(au_wkq); +} + +int __init au_wkq_init(void) +{ + int err, i; + struct au_wkq *nowaitq; + + LKTRTrace("%d\n", aufs_nwkq); + + /* '+1' is for accounting of nowait queue */ + err = -ENOMEM; + au_wkq = kcalloc(aufs_nwkq + 1, sizeof(*au_wkq), GFP_NOFS); + if (unlikely(!au_wkq)) + goto out; + + err = 0; + for (i = 0; i < aufs_nwkq; i++) { + au_wkq[i].q = create_singlethread_workqueue(AUFS_WKQ_NAME); + if (au_wkq[i].q && !IS_ERR(au_wkq[i].q)) { + atomic_set(&au_wkq[i].busy, 0); + atomic_set(&au_wkq[i].max_busy, 0); + continue; + } + + err = PTR_ERR(au_wkq[i].q); + au_wkq_fin(); + break; + } + + /* nowait accounting */ + nowaitq = au_wkq + aufs_nwkq; + atomic_set(&nowaitq->busy, 0); + atomic_set(&nowaitq->max_busy, 0); + nowaitq->q = NULL; + /* smp_mb(); */ /* atomic_set */ + +#if 0 // test accouting + if (!err) { + static void f(void *args) + { + DbgSleep(1); + } + int i; + //au_debug_on(); + LKTRTrace("f %p\n", f); + for (i = 0; i < 10; i++) + au_wkq_nowait(f, NULL, 0); + for (i = 0; i < aufs_nwkq; i++) + au_wkq_wait(f, NULL, 0); + DbgSleep(11); + //au_debug_off(); + } +#endif + + out: + AuTraceErr(err); + return err; +} diff --git a/fs/aufs/wkq.h b/fs/aufs/wkq.h new file mode 100644 index 0000000..ae14367 --- /dev/null +++ b/fs/aufs/wkq.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * workqueue for asynchronous/super-io/delegated operations + * + * $Id: wkq.h,v 1.26 2009/01/26 06:24:45 sfjro Exp $ + */ + +#ifndef __AUFS_WKQ_H__ +#define __AUFS_WKQ_H__ + +#ifdef __KERNEL__ + +#include +#include +#include +#include + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +#define DECLARE_COMPLETION_ONSTACK(work) DECLARE_COMPLETION(work) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) +#define AuInitWkq(wk, func) INIT_WORK(wk, func) +#define AuWkqFunc(name, arg) void name(struct work_struct *arg) +#else +#define AuInitWkq(wk, func) INIT_WORK(wk, func, wk) +#define AuWkqFunc(name, arg) void name(void *arg) +#endif + +/* ---------------------------------------------------------------------- */ + +/* internal workqueue named AUFS_WKQ_NAME */ +struct au_wkq { + struct workqueue_struct *q; + + /* accounting */ + atomic_t busy; + atomic_t max_busy; +}; + +/* + * in the next operation, wait for the 'nowait' tasks in system-wide workqueue + */ +struct au_nowait_tasks { + atomic_t nw_len; + wait_queue_head_t nw_wq; +}; + +/* ---------------------------------------------------------------------- */ + +extern struct au_wkq *au_wkq; +typedef void (*au_wkq_func_t)(void *args); + +/* wkq flags */ +#define AuWkq_WAIT 1 +#define AuWkq_DLGT (1 << 1) +#define au_ftest_wkq(flags, name) ((flags) & AuWkq_##name) +#define au_fset_wkq(flags, name) { (flags) |= AuWkq_##name; } +#define au_fclr_wkq(flags, name) { (flags) &= ~AuWkq_##name; } +#ifndef CONFIG_AUFS_DLGT +#undef AuWkq_DLGT +#define AuWkq_DLGT 0 +#endif + +int au_wkq_wait(au_wkq_func_t func, void *args, int dlgt); +int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb, + int dlgt); +int __init au_wkq_init(void); +void au_wkq_fin(void); + +/* ---------------------------------------------------------------------- */ + +static inline int au_test_nowait_wkq(struct task_struct *tsk) +{ + static const char *p = "events"; + return !tsk->mm && !strncmp(tsk->comm, p, strlen(p)); +} + +static inline int au_test_wkq(struct task_struct *tsk) +{ + return !tsk->mm && !strcmp(tsk->comm, AUFS_WKQ_NAME); +#if 0 /* reserved for future use, per-cpu workqueue */ + return !tsk->mm + && !memcmp(tsk->comm, AUFS_WKQ_NAME "/", + sizeof(AUFS_WKQ_NAME)); +#endif +} + +static inline void au_nwt_init(struct au_nowait_tasks *nwt) +{ + atomic_set(&nwt->nw_len, 0); + smp_mb(); /* atomic_set */ + init_waitqueue_head(&nwt->nw_wq); +} + +static inline void au_nwt_done(struct au_nowait_tasks *nwt) +{ + AuTraceEnter(); + + if (!atomic_dec_return(&nwt->nw_len)) + wake_up_all(&nwt->nw_wq); +} + +static inline int au_nwt_flush(struct au_nowait_tasks *nwt) +{ + wait_event(nwt->nw_wq, !atomic_read(&nwt->nw_len)); + return 0; +} + +#endif /* __KERNEL__ */ +#endif /* __AUFS_WKQ_H__ */ diff --git a/fs/aufs/xino.c b/fs/aufs/xino.c new file mode 100644 index 0000000..8c4bb7f --- /dev/null +++ b/fs/aufs/xino.c @@ -0,0 +1,1276 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * external inode number translation table and bitmap + * + * $Id: xino.c,v 1.65 2009/01/26 06:24:24 sfjro Exp $ + */ + +#include +#include +#include "aufs.h" + +/* ---------------------------------------------------------------------- */ + +ssize_t xino_fread(au_readf_t func, struct file *file, void *buf, size_t size, + loff_t *pos) +{ + ssize_t err; + mm_segment_t oldfs; + + LKTRTrace("%.*s, sz %zu, *pos %lld\n", + AuDLNPair(file->f_dentry), size, *pos); + + oldfs = get_fs(); + set_fs(KERNEL_DS); + do { + /* todo: signal_pending? */ + err = func(file, (char __user *)buf, size, pos); + } while (err == -EAGAIN || err == -EINTR); + set_fs(oldfs); + +#if 0 /* reserved for future use */ + if (err > 0) + fsnotify_access(file->f_dentry); +#endif + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +static ssize_t do_xino_fwrite(au_writef_t func, struct file *file, void *buf, + size_t size, loff_t *pos) +{ + ssize_t err; + mm_segment_t oldfs; + + lockdep_off(); + oldfs = get_fs(); + set_fs(KERNEL_DS); + do { + /* todo: signal_pending? */ + err = func(file, (const char __user *)buf, size, pos); + } while (err == -EAGAIN || err == -EINTR); + set_fs(oldfs); + lockdep_on(); + + if (err >= 0) + au_update_fuse_h_inode(file->f_vfsmnt, file->f_dentry); + /*ignore*/ + +#if 0 /* reserved for future use */ + if (err > 0) + fsnotify_modify(file->f_dentry); +#endif + + AuTraceErr(err); + return err; +} + +struct do_xino_fwrite_args { + ssize_t *errp; + au_writef_t func; + struct file *file; + void *buf; + size_t size; + loff_t *pos; +}; + +static void call_do_xino_fwrite(void *args) +{ + struct do_xino_fwrite_args *a = args; + *a->errp = do_xino_fwrite(a->func, a->file, a->buf, a->size, a->pos); +} + +ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size, + loff_t *pos) +{ + ssize_t err; + + LKTRTrace("%.*s, sz %zu, *pos %lld\n", + AuDLNPair(file->f_dentry), size, *pos); + + /* todo: signal block and no wkq? */ + /* + * it breaks RLIMIT_FSIZE and normal user's limit, + * users should care about quota and real 'filesystem full.' + */ + if (!au_test_wkq(current)) { + int wkq_err; + struct do_xino_fwrite_args args = { + .errp = &err, + .func = func, + .file = file, + .buf = buf, + .size = size, + .pos = pos + }; + wkq_err = au_wkq_wait(call_do_xino_fwrite, &args, /*dlgt*/0); + if (unlikely(wkq_err)) + err = wkq_err; + } else + err = do_xino_fwrite(func, file, buf, size, pos); + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) +struct xino_do_trunc_args { + struct vfsmount *mnt; + struct au_branch *br; +}; + +static void xino_do_trunc(void *_args) +{ + struct xino_do_trunc_args *args = _args; + struct super_block *sb; + aufs_bindex_t bindex; + int err; + struct file *file; + struct inode *dir; + + err = 0; + sb = args->mnt->mnt_sb; + dir = sb->s_root->d_inode; + si_noflush_write_lock(sb); + ii_read_lock_parent(dir); + bindex = au_br_index(sb, args->br->br_id); + AuDebugOn(bindex < 0); + err = au_xino_trunc(sb, bindex); + if (unlikely(err)) + goto out; + + file = args->br->br_xino.xi_file; + au_update_fuse_h_inode(args->br->br_mnt, file->f_dentry); /*ignore*/ + if (file->f_dentry->d_inode->i_blocks >= args->br->br_xino_upper) + args->br->br_xino_upper += AUFS_XINO_TRUNC_STEP; + + out: + ii_read_unlock(dir); + if (unlikely(err)) + AuWarn("err b%d, (%d)\n", bindex, err); + atomic_dec_return(&args->br->br_xino_running); + au_br_put(args->br); + au_nwt_done(&au_sbi(sb)->si_nowait); + si_write_unlock(sb); + mntput(args->mnt); + kfree(args); +} + +static void xino_try_trunc(struct super_block *sb, struct au_branch *br) +{ + struct xino_do_trunc_args *args; + struct au_sbinfo *sbinfo; + struct file *file = br->br_xino.xi_file; + int wkq_err; + + au_update_fuse_h_inode(br->br_mnt, file->f_dentry); /*ignore*/ + if (file->f_dentry->d_inode->i_blocks < br->br_xino_upper) + return; + if (atomic_inc_return(&br->br_xino_running) > 1) + goto out; + + /* lock and kfree() will be called in trunc_xino() */ + args = kmalloc(sizeof(*args), GFP_NOFS); + if (unlikely(!args)) { + AuErr1("no memory\n"); + goto out_args; + } + + sbinfo = au_sbi(sb); + args->mnt = mntget(sbinfo->si_mnt); + au_br_get(br); + args->br = br; + wkq_err = au_wkq_nowait(xino_do_trunc, args, sb, /*dlgt*/0); + if (!wkq_err) + return; /* success */ + + AuErr("wkq %d\n", wkq_err); + mntput(sbinfo->si_mnt); + au_br_put(br); + + out_args: + kfree(args); + out: + atomic_dec_return(&br->br_xino_running); +} +#else +static void xino_try_trunc(struct super_block *sb, struct au_branch *br) +{ + /* nothing */ +} +#endif + +/* ---------------------------------------------------------------------- */ + +static int au_xino_do_write(au_writef_t write, struct file *file, + ino_t h_ino, struct au_xino_entry *xinoe) +{ + loff_t pos; + ssize_t sz; + + AuTraceEnter(); + + pos = h_ino; + if (unlikely(Au_LOFF_MAX / sizeof(*xinoe) - 1 < pos)) { + AuIOErr1("too large hi%lu\n", (unsigned long)h_ino); + return -EFBIG; + } + pos *= sizeof(*xinoe); + sz = xino_fwrite(write, file, xinoe, sizeof(*xinoe), &pos); + if (sz == sizeof(*xinoe)) + return 0; /* success */ + + AuIOErr("write failed (%zd)\n", sz); + return -EIO; +} + +/* + * write @ino to the xinofile for the specified branch{@sb, @bindex} + * at the position of @_ino. + * when @ino is zero, it is written to the xinofile and means no entry. + */ +int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + struct au_xino_entry *xinoe) +{ + int err; + struct file *file; + struct au_branch *br; + unsigned int mnt_flags; + + LKTRTrace("b%d, hi%lu, i%lu\n", + bindex, (unsigned long)h_ino, (unsigned long)xinoe->ino); + BUILD_BUG_ON(sizeof(long long) != sizeof(Au_LOFF_MAX) + || ((loff_t)-1) > 0); + + mnt_flags = au_mntflags(sb); + if (!au_opt_test_xino(mnt_flags)) + return 0; + + br = au_sbr(sb, bindex); + file = br->br_xino.xi_file; + AuDebugOn(!file); + + err = au_xino_do_write(au_sbi(sb)->si_xwrite, file, h_ino, xinoe); + if (!err) { + if (au_opt_test(mnt_flags, TRUNC_XINO) + && au_test_trunc_xino(br->br_mnt->mnt_sb)) + xino_try_trunc(sb, br); + return 0; /* success */ + } + + AuIOErr("write failed (%d)\n", err); + return -EIO; +} + +/* ---------------------------------------------------------------------- */ + +static const int page_bits = (int)PAGE_SIZE * BITS_PER_BYTE; +//static const int page_bits = 4; +static ino_t xib_calc_ino(unsigned long pindex, int bit) +{ + ino_t ino; + + AuDebugOn(bit < 0 || page_bits <= bit); + ino = AUFS_FIRST_INO + pindex * page_bits + bit; + return ino; +} + +static void xib_calc_bit(ino_t ino, unsigned long *pindex, int *bit) +{ + AuDebugOn(ino < AUFS_FIRST_INO); + ino -= AUFS_FIRST_INO; + *pindex = ino / page_bits; + *bit = ino % page_bits; +} + +static int xib_pindex(struct super_block *sb, unsigned long pindex) +{ + int err; + struct au_sbinfo *sbinfo; + loff_t pos; + ssize_t sz; + struct file *xib; + unsigned long *p; + + LKTRTrace("pindex %lu\n", pindex); + sbinfo = au_sbi(sb); + MtxMustLock(&sbinfo->si_xib_mtx); + AuDebugOn(pindex > ULONG_MAX / PAGE_SIZE + || !au_opt_test_xino(sbinfo->si_mntflags)); + + if (pindex == sbinfo->si_xib_last_pindex) + return 0; + + xib = sbinfo->si_xib; + p = sbinfo->si_xib_buf; + pos = sbinfo->si_xib_last_pindex; + pos *= PAGE_SIZE; + sz = xino_fwrite(sbinfo->si_xwrite, xib, p, PAGE_SIZE, &pos); + if (unlikely(sz != PAGE_SIZE)) + goto out; + + pos = pindex; + pos *= PAGE_SIZE; + if (i_size_read(xib->f_dentry->d_inode) >= pos + PAGE_SIZE) + sz = xino_fread(sbinfo->si_xread, xib, p, PAGE_SIZE, &pos); + else { + memset(p, 0, PAGE_SIZE); + sz = xino_fwrite(sbinfo->si_xwrite, xib, p, PAGE_SIZE, &pos); + } + if (sz == PAGE_SIZE) { + sbinfo->si_xib_last_pindex = pindex; + return 0; /* success */ + } + + out: + AuIOErr1("write failed (%zd)\n", sz); + err = sz; + if (sz >= 0) + err = -EIO; + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +int au_xino_write0(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + ino_t ino) +{ + int err, bit; + unsigned long pindex; + struct au_sbinfo *sbinfo; + struct au_xino_entry xinoe = { + .ino = 0 + }; + + LKTRTrace("b%d, hi%lu, i%lu\n", + bindex, (unsigned long)h_ino, (unsigned long)ino); + + if (!au_opt_test_xino(au_mntflags(sb))) + return 0; + + err = 0; + sbinfo = au_sbi(sb); + if (ino) { + AuDebugOn(ino < AUFS_FIRST_INO); + xib_calc_bit(ino, &pindex, &bit); + AuDebugOn(page_bits <= bit); + mutex_lock(&sbinfo->si_xib_mtx); + err = xib_pindex(sb, pindex); + if (!err) { + clear_bit(bit, sbinfo->si_xib_buf); + sbinfo->si_xib_next_bit = bit; + } + mutex_unlock(&sbinfo->si_xib_mtx); + } + + if (!err) + err = au_xino_write(sb, bindex, h_ino, &xinoe); + return err; +} + +ino_t au_xino_new_ino(struct super_block *sb) +{ + ino_t ino; + struct au_sbinfo *sbinfo; + int free_bit, err; + unsigned long *p, pindex, ul, pend; + struct file *file; + + AuTraceEnter(); + + if (!au_opt_test_xino(au_mntflags(sb))) + return iunique(sb, AUFS_FIRST_INO); + + sbinfo = au_sbi(sb); + mutex_lock(&sbinfo->si_xib_mtx); + p = sbinfo->si_xib_buf; + free_bit = sbinfo->si_xib_next_bit; + if (free_bit < page_bits && !test_bit(free_bit, p)) + goto out; /* success */ + free_bit = find_first_zero_bit(p, page_bits); + if (free_bit < page_bits) + goto out; /* success */ + + pindex = sbinfo->si_xib_last_pindex; + for (ul = pindex - 1; ul < ULONG_MAX; ul--) { + err = xib_pindex(sb, ul); + if (unlikely(err)) + goto out_err; + free_bit = find_first_zero_bit(p, page_bits); + if (free_bit < page_bits) + goto out; /* success */ + } + + file = sbinfo->si_xib; + pend = i_size_read(file->f_dentry->d_inode) / PAGE_SIZE; + for (ul = pindex + 1; ul <= pend; ul++) { + err = xib_pindex(sb, ul); + if (unlikely(err)) + goto out_err; + free_bit = find_first_zero_bit(p, page_bits); + if (free_bit < page_bits) + goto out; /* success */ + } + BUG(); + + out: + set_bit(free_bit, p); + sbinfo->si_xib_next_bit++; + pindex = sbinfo->si_xib_last_pindex; + mutex_unlock(&sbinfo->si_xib_mtx); + ino = xib_calc_ino(pindex, free_bit); + LKTRTrace("i%lu\n", (unsigned long)ino); + return ino; + out_err: + mutex_unlock(&sbinfo->si_xib_mtx); + LKTRTrace("i0\n"); + return 0; +} + +/* + * read @ino from xinofile for the specified branch{@sb, @bindex} + * at the position of @h_ino. + * if @ino does not exist and @do_new is true, get new one. + */ +int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino, + struct au_xino_entry *xinoe) +{ + int err; + struct file *file; + loff_t pos; + ssize_t sz; + struct au_sbinfo *sbinfo; + + LKTRTrace("b%d, hi%lu\n", bindex, (unsigned long)h_ino); + + xinoe->ino = 0; + if (!au_opt_test_xino(au_mntflags(sb))) + return 0; /* no ino */ + + err = 0; + sbinfo = au_sbi(sb); + pos = h_ino; + if (unlikely(Au_LOFF_MAX / sizeof(*xinoe) - 1 < pos)) { + AuIOErr1("too large hi%lu\n", (unsigned long)h_ino); + return -EFBIG; + } + pos *= sizeof(*xinoe); + + file = au_sbr(sb, bindex)->br_xino.xi_file; + AuDebugOn(!file); + if (i_size_read(file->f_dentry->d_inode) < pos + sizeof(*xinoe)) + return 0; /* no ino */ + + sz = xino_fread(sbinfo->si_xread, file, xinoe, sizeof(*xinoe), &pos); + if (sz == sizeof(*xinoe)) + return 0; /* success */ + + err = sz; + if (unlikely(sz >= 0)) { + err = -EIO; + AuIOErr("xino read error (%zd)\n", sz); + } + + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +struct file *au_xino_create(struct super_block *sb, char *fname, int silent) +{ + struct file *file; + int err; + struct dentry *h_parent; + struct inode *h_dir; + struct vfsub_args vargs; + + LKTRTrace("%s\n", fname); + + /* + * at mount-time, and the xino file is the default path, + * hinotify is disabled so we have no inotify events to ignore. + * when a user specified the xino, we cannot get au_hdir to be ignored. + */ + vfsub_args_init(&vargs, /*ign*/NULL, /*dlgt*/0, 0); + file = vfsub_filp_open(fname, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE, + S_IRUGO | S_IWUGO); + if (IS_ERR(file)) { + if (!silent) + AuErr("open %s(%ld)\n", fname, PTR_ERR(file)); + return file; + } + + /* keep file count */ + h_parent = dget_parent(file->f_dentry); + h_dir = h_parent->d_inode; + vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT); + /* mnt_want_write() is unnecessary here */ + err = vfsub_unlink(h_dir, file->f_dentry, &vargs); + vfsub_i_unlock(h_dir); + dput(h_parent); + if (unlikely(err)) { + if (!silent) + AuErr("unlink %s(%d)\n", fname, err); + goto out; + } + + if (sb != file->f_dentry->d_sb) + return file; /* success */ + + if (!silent) + AuErr("%s must be outside\n", fname); + err = -EINVAL; + + out: + fput(file); + file = ERR_PTR(err); + return file; +} + +/* + * find another branch who is on the same filesystem of the specified + * branch{@btgt}. search until @bend. + */ +static int is_sb_shared(struct super_block *sb, aufs_bindex_t btgt, + aufs_bindex_t bend) +{ + aufs_bindex_t bindex; + struct super_block *tgt_sb = au_sbr_sb(sb, btgt); + + for (bindex = 0; bindex < btgt; bindex++) + if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) + return bindex; + for (bindex++; bindex <= bend; bindex++) + if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) + return bindex; + return -1; +} + +/* + * create a new xinofile at the same place/path as @base_file. + */ +struct file *au_xino_create2(struct super_block *sb, struct file *base_file, + struct file *copy_src) +{ + struct file *file; + int err; + struct dentry *base, *dentry, *parent; + struct inode *dir, *inode; + struct qstr *name; + struct au_hinode *hdir; + struct au_branch *br; + aufs_bindex_t bindex; + struct au_hin_ignore ign; + struct vfsub_args vargs; + struct au_ndx ndx = { + .nfsmnt = NULL, + .flags = 0, + .nd = NULL, + /* .br = NULL */ + }; + + base = base_file->f_dentry; + LKTRTrace("%.*s\n", AuDLNPair(base)); + parent = base->d_parent; /* dir inode is locked */ + dir = parent->d_inode; + IMustLock(dir); + + file = ERR_PTR(-EINVAL); + if (unlikely(au_test_nfs(parent->d_sb) + || au_test_ecryptfs(parent->d_sb))) + goto out; + + /* do not superio, nor NFS. */ + name = &base->d_name; + dentry = au_lkup_one(name->name, parent, name->len, &ndx); + if (IS_ERR(dentry)) { + file = (void *)dentry; + AuErr("%.*s lookup err %ld\n", AuLNPair(name), PTR_ERR(dentry)); + goto out; + } + + hdir = NULL; + br = au_xino_def_br(au_sbi(sb)); + if (br) { + bindex = au_find_bindex(sb, br); + if (bindex >= 0) + hdir = au_hi(sb->s_root->d_inode, bindex); + } + vfsub_args_init(&vargs, &ign, 0, 0); + vfsub_ign_hinode(&vargs, IN_CREATE, hdir); + /* no need to mnt_want_write() since we call dentry_open() later */ + err = vfsub_create(dir, dentry, S_IRUGO | S_IWUGO, NULL, &vargs); + if (unlikely(err)) { + file = ERR_PTR(err); + AuErr("%.*s create err %d\n", AuLNPair(name), err); + goto out_dput; + } + file = dentry_open(dget(dentry), mntget(base_file->f_vfsmnt), + O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE); + if (IS_ERR(file)) { + AuErr("%.*s open err %ld\n", AuLNPair(name), PTR_ERR(file)); + goto out_dput; + } + vfsub_args_reinit(&vargs); + vfsub_ign_hinode(&vargs, IN_DELETE, hdir); + err = vfsub_unlink(dir, dentry, &vargs); + if (unlikely(err)) { + AuErr("%.*s unlink err %d\n", AuLNPair(name), err); + goto out_fput; + } + + if (copy_src) { + inode = copy_src->f_dentry->d_inode; + err = au_copy_file(file, copy_src, i_size_read(inode), + hdir, sb, &vargs); + if (unlikely(err)) { + AuErr("%.*s copy err %d\n", AuLNPair(name), err); + goto out_fput; + } + } + goto out_dput; /* success */ + + out_fput: + fput(file); + file = ERR_PTR(err); + out_dput: + dput(dentry); + out: + AuTraceErrPtr(file); + return file; +} + +/* ---------------------------------------------------------------------- */ + +/* + * initialize the xinofile for the specified branch{@sb, @bindex} + * at the place/path where @base_file indicates. + * test whether another branch is on the same filesystem or not, + * if @do_test is true. + */ +int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t h_ino, + struct file *base_file, int do_test) +{ + int err; + struct au_branch *shared_br; + aufs_bindex_t bshared, bend, bindex; + unsigned char do_create; + struct inode *dir; + struct au_xino_entry xinoe; + struct dentry *parent; + struct file *file; + struct super_block *tgt_sb; + + LKTRTrace("base_file %p, do_test %d\n", base_file, do_test); + SiMustWriteLock(sb); + AuDebugOn(!au_opt_test_xino(au_mntflags(sb))); + AuDebugOn(br->br_xino.xi_file); + + do_create = 1; + bshared = -1; + shared_br = NULL; + bend = au_sbend(sb); + if (do_test) { + tgt_sb = br->br_mnt->mnt_sb; + for (bindex = 0; bindex <= bend; bindex++) + if (tgt_sb == au_sbr_sb(sb, bindex)) { + bshared = bindex; + break; + } + } + if (bshared >= 0) { + shared_br = au_sbr(sb, bshared); + do_create = !shared_br->br_xino.xi_file; + } + + if (do_create) { + parent = dget_parent(base_file->f_dentry); + dir = parent->d_inode; + vfsub_i_lock_nested(dir, AuLsc_I_PARENT); + /* mnt_want_write() is unnecessary here */ + file = au_xino_create2(sb, base_file, NULL); + vfsub_i_unlock(dir); + dput(parent); + err = PTR_ERR(file); + if (IS_ERR(file)) + goto out; + br->br_xino.xi_file = file; + } else { + br->br_xino.xi_file = shared_br->br_xino.xi_file; + get_file(br->br_xino.xi_file); + } + + xinoe.ino = AUFS_ROOT_INO; +#if 0 /* reserved for future use */ + xinoe.h_gen = h_inode->i_generation; + WARN_ON(xinoe.h_gen == AuXino_INVALID_HGEN); +#endif + err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino.xi_file, + h_ino, &xinoe); + if (!err) + return 0; /* success */ + + + out: + AuTraceErr(err); + return err; +} + +/* too slow */ +static int do_xib_restore(struct super_block *sb, struct file *file, void *page) +{ + int err, bit; + struct au_sbinfo *sbinfo; + au_readf_t func; + loff_t pos, pend; + ssize_t sz; + struct au_xino_entry *xinoe; + unsigned long pindex; + + AuTraceEnter(); + SiMustWriteLock(sb); + + err = 0; + sbinfo = au_sbi(sb); + func = sbinfo->si_xread; + pend = i_size_read(file->f_dentry->d_inode); +#ifdef CONFIG_AUFS_DEBUG + if (unlikely(pend > (1 << 22))) + AuWarn("testing a large xino file %lld\n", (long long)pend); +#endif + pos = 0; + while (pos < pend) { + sz = xino_fread(func, file, page, PAGE_SIZE, &pos); + err = sz; + if (unlikely(sz <= 0)) + goto out; + + err = 0; + for (xinoe = page; sz > 0; xinoe++, sz -= sizeof(xinoe)) { + if (unlikely(xinoe->ino < AUFS_FIRST_INO)) + continue; + + xib_calc_bit(xinoe->ino, &pindex, &bit); + AuDebugOn(page_bits <= bit); + err = xib_pindex(sb, pindex); + if (!err) + set_bit(bit, sbinfo->si_xib_buf); + else + goto out; + } + } + + out: + AuTraceErr(err); + return err; +} + +static int xib_restore(struct super_block *sb) +{ + int err; + aufs_bindex_t bindex, bend; + void *page; + + AuTraceEnter(); + + err = -ENOMEM; + page = (void *)__get_free_page(GFP_NOFS); + if (unlikely(!page)) + goto out; + + err = 0; + bend = au_sbend(sb); + for (bindex = 0; !err && bindex <= bend; bindex++) + if (!bindex || is_sb_shared(sb, bindex, bindex - 1) < 0) + err = do_xib_restore + (sb, au_sbr(sb, bindex)->br_xino.xi_file, page); + else + LKTRTrace("b%d\n", bindex); + free_page((unsigned long)page); + + out: + AuTraceErr(err); + return err; +} + +int au_xib_trunc(struct super_block *sb) +{ + int err; + unsigned int mnt_flags; + ssize_t sz; + loff_t pos; + struct au_sbinfo *sbinfo; + unsigned long *p; + struct dentry *parent; + struct inode *dir; + struct file *file; + + AuTraceEnter(); + SiMustWriteLock(sb); + + err = 0; + mnt_flags = au_mntflags(sb); + if (!au_opt_test_xino(mnt_flags)) + goto out; + + sbinfo = au_sbi(sb); + if (i_size_read(sbinfo->si_xib->f_dentry->d_inode) <= PAGE_SIZE) + goto out; + parent = dget_parent(sbinfo->si_xib->f_dentry); + dir = parent->d_inode; + vfsub_i_lock_nested(dir, AuLsc_I_PARENT); + /* mnt_want_write() is unnecessary here */ + file = au_xino_create2(sb, sbinfo->si_xib, NULL); + vfsub_i_unlock(dir); + dput(parent); + err = PTR_ERR(file); + if (IS_ERR(file)) + goto out; + fput(sbinfo->si_xib); + sbinfo->si_xib = file; + + p = sbinfo->si_xib_buf; + memset(p, 0, PAGE_SIZE); + pos = 0; + sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xib, p, PAGE_SIZE, &pos); + if (unlikely(sz != PAGE_SIZE)) { + err = sz; + AuIOErr("err %d\n", err); + if (sz >= 0) + err = -EIO; + goto out; + } + + if (au_opt_test_xino(mnt_flags)) { + mutex_lock(&sbinfo->si_xib_mtx); + /* mnt_want_write() is unnecessary here */ + err = xib_restore(sb); + mutex_unlock(&sbinfo->si_xib_mtx); +#if 0 /* reserved for future use */ + } else { + /* is it really safe? */ + /* dont trust BKL */ + AuDebugOn(!kernel_locked()); + ino = AUFS_FIRST_INO; + list_for_each_entry(inode, &sb->s_inodes, i_sb_list) + if (ino < inode->i_ino) + ino = inode->i_ino; + + /* make iunique to return larger than active max inode number */ + iunique(sb, ino); + err = 0; +#endif + } + +out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* + * xino mount option handlers + */ +static au_readf_t find_readf(struct file *h_file) +{ + const struct file_operations *fop = h_file->f_op; + + if (fop) { + if (fop->read) + return fop->read; + if (fop->aio_read) + return do_sync_read; + } + return ERR_PTR(-ENOSYS); +} + +static au_writef_t find_writef(struct file *h_file) +{ + const struct file_operations *fop = h_file->f_op; + + if (fop) { + if (fop->write) + return fop->write; + if (fop->aio_write) + return do_sync_write; + } + return ERR_PTR(-ENOSYS); +} + +/* xino bitmap */ +static void xino_clear_xib(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + SiMustWriteLock(sb); + + sbinfo = au_sbi(sb); + sbinfo->si_xread = NULL; + sbinfo->si_xwrite = NULL; + if (sbinfo->si_xib) + fput(sbinfo->si_xib); + sbinfo->si_xib = NULL; + free_page((unsigned long)sbinfo->si_xib_buf); + sbinfo->si_xib_buf = NULL; +} + +static int au_xino_set_xib(struct super_block *sb, struct file *base) +{ + int err; + struct au_sbinfo *sbinfo; + struct file *file; + loff_t pos; + + LKTRTrace("%.*s\n", AuDLNPair(base->f_dentry)); + SiMustWriteLock(sb); + + sbinfo = au_sbi(sb); + file = au_xino_create2(sb, base, sbinfo->si_xib); + err = PTR_ERR(file); + if (IS_ERR(file)) + goto out; + if (sbinfo->si_xib) + fput(sbinfo->si_xib); + sbinfo->si_xib = file; + sbinfo->si_xread = find_readf(file); + AuDebugOn(IS_ERR(sbinfo->si_xread)); + sbinfo->si_xwrite = find_writef(file); + AuDebugOn(IS_ERR(sbinfo->si_xwrite)); + + err = -ENOMEM; + if (!sbinfo->si_xib_buf) + sbinfo->si_xib_buf = (void *)get_zeroed_page(GFP_NOFS); + if (unlikely(!sbinfo->si_xib_buf)) + goto out_unset; + + sbinfo->si_xib_last_pindex = 0; + sbinfo->si_xib_next_bit = 0; + + /* no need to lock for i_size_read() */ + if (i_size_read(file->f_dentry->d_inode) < PAGE_SIZE) { + pos = 0; + err = xino_fwrite(sbinfo->si_xwrite, file, sbinfo->si_xib_buf, + PAGE_SIZE, &pos); + if (unlikely(err != PAGE_SIZE)) + goto out_free; + } + err = 0; + goto out; /* success */ + + out_free: + free_page((unsigned long)sbinfo->si_xib_buf); + sbinfo->si_xib_buf = NULL; + if (err >= 0) + err = -EIO; + out_unset: + fput(sbinfo->si_xib); + sbinfo->si_xib = NULL; + sbinfo->si_xread = NULL; + sbinfo->si_xwrite = NULL; + out: + AuTraceErr(err); + return err; +} + +/* xino for each branch */ +static void xino_clear_br(struct super_block *sb) +{ + aufs_bindex_t bindex, bend; + struct au_branch *br; + + AuTraceEnter(); + SiMustWriteLock(sb); + + bend = au_sbend(sb); + for (bindex = 0; bindex <= bend; bindex++) { + br = au_sbr(sb, bindex); + if (!br || !br->br_xino.xi_file) + continue; + + fput(br->br_xino.xi_file); + br->br_xino.xi_file = NULL; + } +} + +static int au_xino_set_br(struct super_block *sb, struct file *base) +{ + int err; + aufs_bindex_t bindex, bend, bshared; + struct { + struct file *old, *new; + } *fpair, *p; + struct au_branch *br; + struct au_xino_entry xinoe; + struct inode *inode; + au_writef_t writef; + + LKTRTrace("%.*s\n", AuDLNPair(base->f_dentry)); + SiMustWriteLock(sb); + + err = -ENOMEM; + bend = au_sbend(sb); + fpair = kcalloc(bend + 1, sizeof(*fpair), GFP_NOFS); + if (unlikely(!fpair)) + goto out; + + inode = sb->s_root->d_inode; + xinoe.ino = AUFS_ROOT_INO; + writef = au_sbi(sb)->si_xwrite; + for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) { + br = au_sbr(sb, bindex); + bshared = is_sb_shared(sb, bindex, bindex - 1); + if (bshared >= 0) { + /* shared xino */ + *p = fpair[bshared]; + get_file(p->new); + } + + if (!p->new) { + /* new xino */ + p->old = br->br_xino.xi_file; + p->new = au_xino_create2(sb, base, br->br_xino.xi_file); + err = PTR_ERR(p->new); + if (IS_ERR(p->new)) { + p->new = NULL; + goto out_pair; + } + } + + err = au_xino_do_write(writef, p->new, + au_h_iptr(inode, bindex)->i_ino, &xinoe); + if (unlikely(err)) + goto out_pair; + } + + for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) { + br = au_sbr(sb, bindex); + AuDebugOn(p->old != br->br_xino.xi_file); + if (br->br_xino.xi_file) + fput(br->br_xino.xi_file); + get_file(p->new); + br->br_xino.xi_file = p->new; + } + + out_pair: + for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) + if (p->new) + fput(p->new); + else + break; + kfree(fpair); + out: + AuTraceErr(err); + return err; +} + +void au_xino_clr(struct super_block *sb) +{ + struct au_sbinfo *sbinfo; + + AuTraceEnter(); + SiMustWriteLock(sb); + + au_xigen_clr(sb); + xino_clear_xib(sb); + xino_clear_br(sb); + sbinfo = au_sbi(sb); + /* lvalue, do not call au_mntflags() */ + au_opt_clr(sbinfo->si_mntflags, XINO); + au_xino_def_br_set(NULL, sbinfo); +} + +int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount) +{ + int err, skip; + struct dentry *parent, *cur_parent; + struct qstr *dname, *cur_name; + struct file *cur_xino; + struct inode *dir; + struct au_sbinfo *sbinfo; + + LKTRTrace("remount %d\n", remount); + SiMustWriteLock(sb); + + err = 0; + sbinfo = au_sbi(sb); + parent = dget_parent(xino->file->f_dentry); + if (remount) { + skip = 0; + dname = &xino->file->f_dentry->d_name; + cur_xino = sbinfo->si_xib; + if (cur_xino) { + cur_parent = dget_parent(cur_xino->f_dentry); + cur_name = &cur_xino->f_dentry->d_name; + skip = (cur_parent == parent + && dname->len == cur_name->len + && !memcmp(dname->name, cur_name->name, + dname->len)); + dput(cur_parent); + } + if (skip) + goto out; + } + + au_opt_set(sbinfo->si_mntflags, XINO); + au_xino_def_br_set(NULL, sbinfo); + dir = parent->d_inode; + vfsub_i_lock_nested(dir, AuLsc_I_PARENT); + /* mnt_want_write() is unnecessary here */ + err = au_xino_set_xib(sb, xino->file); + if (!err) + err = au_xigen_set(sb, xino->file); + if (!err) + err = au_xino_set_br(sb, xino->file); + vfsub_i_unlock(dir); + if (!err) + goto out; /* success */ + + /* reset all */ + AuIOErr("failed creating xino(%d).\n", err); + + out: + dput(parent); + AuTraceErr(err); + return err; +} + +int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex) +{ + int err; + struct au_branch *br; + struct file *new_xino; + struct super_block *h_sb; + aufs_bindex_t bi, bend; + struct dentry *parent; + struct inode *dir; + + LKTRTrace("b%d\n", bindex); + SiMustWriteLock(sb); + + err = -EINVAL; + bend = au_sbend(sb); + if (unlikely(bindex < 0 || bend < bindex)) + goto out; + br = au_sbr(sb, bindex); + if (!br->br_xino.xi_file) + goto out; + + parent = dget_parent(br->br_xino.xi_file->f_dentry); + dir = parent->d_inode; + vfsub_i_lock_nested(dir, AuLsc_I_PARENT); + /* mnt_want_write() is unnecessary here */ + new_xino = au_xino_create2(sb, br->br_xino.xi_file, + br->br_xino.xi_file); + vfsub_i_unlock(dir); + dput(parent); + err = PTR_ERR(new_xino); + if (IS_ERR(new_xino)) + goto out; + err = 0; + fput(br->br_xino.xi_file); + br->br_xino.xi_file = new_xino; + + h_sb = br->br_mnt->mnt_sb; + for (bi = 0; bi <= bend; bi++) { + if (unlikely(bi == bindex)) + continue; + br = au_sbr(sb, bi); + if (br->br_mnt->mnt_sb != h_sb) + continue; + + fput(br->br_xino.xi_file); + br->br_xino.xi_file = new_xino; + get_file(new_xino); + } + + out: + AuTraceErr(err); + return err; +} + +/* ---------------------------------------------------------------------- */ + +/* + * create a xinofile at the default place/path. + */ +struct file *au_xino_def(struct super_block *sb) +{ + struct file *file; + aufs_bindex_t bend, bindex, bwr; + char *page, *p; + struct au_branch *br; + + AuTraceEnter(); + + bend = au_sbend(sb); + bwr = -1; + for (bindex = 0; bindex <= bend; bindex++) { + br = au_sbr(sb, bindex); + if (au_br_writable(br->br_perm) + && !au_test_nfs(br->br_mnt->mnt_sb) + && !au_test_ecryptfs(br->br_mnt->mnt_sb)) { + bwr = bindex; + break; + } + } + + if (bwr >= 0) { + file = ERR_PTR(-ENOMEM); + page = __getname(); + if (unlikely(!page)) + goto out; + p = d_path(au_h_dptr(sb->s_root, bwr), au_sbr_mnt(sb, bwr), + page, PATH_MAX - sizeof(AUFS_XINO_FNAME)); + //if (LktrCond) p = ERR_PTR(-1); + file = (void *)p; + if (!IS_ERR(p)) { + strcat(p, "/" AUFS_XINO_FNAME); + LKTRTrace("%s\n", p); + file = au_xino_create(sb, p, /*silent*/0); + if (!IS_ERR(file)) + au_xino_def_br_set(au_sbr(sb, bwr), au_sbi(sb)); + } + __putname(page); + } else { + file = au_xino_create(sb, AUFS_XINO_DEFPATH, /*silent*/0); + if (unlikely(au_test_nfs(file->f_dentry->d_sb) + || au_test_ecryptfs(file->f_dentry->d_sb))) { + AuErr("xino or noxino option is required " + "since %s is NFS\n", AUFS_XINO_DEFPATH); + fput(file); + file = ERR_PTR(-EINVAL); + } + if (!IS_ERR(file)) + au_xino_def_br_set(NULL, au_sbi(sb)); + } + + out: + AuTraceErrPtr(file); + return file; +} diff --git a/fs/yaffs2/Kconfig b/fs/yaffs2/Kconfig new file mode 100644 index 0000000..de15163 --- /dev/null +++ b/fs/yaffs2/Kconfig @@ -0,0 +1,156 @@ +# +# YAFFS file system configurations +# + +config YAFFS_FS + tristate "YAFFS2 file system support" + default n + depends on MTD + select YAFFS_YAFFS1 + select YAFFS_YAFFS2 + help + YAFFS2, or Yet Another Flash Filing System, is a filing system + optimised for NAND Flash chips. + + To compile the YAFFS2 file system support as a module, choose M + here: the module will be called yaffs2. + + If unsure, say N. + + Further information on YAFFS2 is available at + . + +config YAFFS_YAFFS1 + bool "512 byte / page devices" + depends on YAFFS_FS + default y + help + Enable YAFFS1 support -- yaffs for 512 byte / page devices + + Not needed for 2K-page devices. + + If unsure, say Y. + +config YAFFS_9BYTE_TAGS + bool "Use older-style on-NAND data format with pageStatus byte" + depends on YAFFS_YAFFS1 + default n + help + + Older-style on-NAND data format has a "pageStatus" byte to record + chunk/page state. This byte is zero when the page is discarded. + Choose this option if you have existing on-NAND data using this + format that you need to continue to support. New data written + also uses the older-style format. Note: Use of this option + generally requires that MTD's oob layout be adjusted to use the + older-style format. See notes on tags formats and MTD versions + in yaffs_mtdif1.c. + + If unsure, say N. + +config YAFFS_DOES_ECC + bool "Lets Yaffs do its own ECC" + depends on YAFFS_FS && YAFFS_YAFFS1 && !YAFFS_9BYTE_TAGS + default n + help + This enables Yaffs to use its own ECC functions instead of using + the ones from the generic MTD-NAND driver. + + If unsure, say N. + +config YAFFS_ECC_WRONG_ORDER + bool "Use the same ecc byte order as Steven Hill's nand_ecc.c" + depends on YAFFS_FS && YAFFS_DOES_ECC && !YAFFS_9BYTE_TAGS + default n + help + This makes yaffs_ecc.c use the same ecc byte order as Steven + Hill's nand_ecc.c. If not set, then you get the same ecc byte + order as SmartMedia. + + If unsure, say N. + +config YAFFS_YAFFS2 + bool "2048 byte (or larger) / page devices" + depends on YAFFS_FS + default y + help + Enable YAFFS2 support -- yaffs for >= 2K bytes per page devices + + If unsure, say Y. + +config YAFFS_AUTO_YAFFS2 + bool "Autoselect yaffs2 format" + depends on YAFFS_YAFFS2 + default y + help + Without this, you need to explicitely use yaffs2 as the file + system type. With this, you can say "yaffs" and yaffs or yaffs2 + will be used depending on the device page size (yaffs on + 512-byte page devices, yaffs2 on 2K page devices). + + If unsure, say Y. + +config YAFFS_DISABLE_LAZY_LOAD + bool "Disable lazy loading" + depends on YAFFS_YAFFS2 + default n + help + "Lazy loading" defers loading file details until they are + required. This saves mount time, but makes the first look-up + a bit longer. + + Lazy loading will only happen if enabled by this option being 'n' + and if the appropriate tags are available, else yaffs2 will + automatically fall back to immediate loading and do the right + thing. + + Lazy laoding will be required by checkpointing. + + Setting this to 'y' will disable lazy loading. + + If unsure, say N. + + +config YAFFS_DISABLE_WIDE_TNODES + bool "Turn off wide tnodes" + depends on YAFFS_FS + default n + help + Wide tnodes are only used for NAND arrays >=32MB for 512-byte + page devices and >=128MB for 2k page devices. They use slightly + more RAM but are faster since they eliminate chunk group + searching. + + Setting this to 'y' will force tnode width to 16 bits and save + memory but make large arrays slower. + + If unsure, say N. + +config YAFFS_ALWAYS_CHECK_CHUNK_ERASED + bool "Force chunk erase check" + depends on YAFFS_FS + default n + help + Normally YAFFS only checks chunks before writing until an erased + chunk is found. This helps to detect any partially written + chunks that might have happened due to power loss. + + Enabling this forces on the test that chunks are erased in flash + before writing to them. This takes more time but is potentially + a bit more secure. + + Suggest setting Y during development and ironing out driver + issues etc. Suggest setting to N if you want faster writing. + + If unsure, say Y. + +config YAFFS_SHORT_NAMES_IN_RAM + bool "Cache short names in RAM" + depends on YAFFS_FS + default y + help + If this config is set, then short names are stored with the + yaffs_Object. This costs an extra 16 bytes of RAM per object, + but makes look-ups faster. + + If unsure, say Y. diff --git a/fs/yaffs2/Makefile b/fs/yaffs2/Makefile new file mode 100644 index 0000000..382ee61 --- /dev/null +++ b/fs/yaffs2/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the linux YAFFS filesystem routines. +# + +obj-$(CONFIG_YAFFS_FS) += yaffs.o + +yaffs-y := yaffs_ecc.o yaffs_fs.o yaffs_guts.o yaffs_checkptrw.o +yaffs-y += yaffs_packedtags1.o yaffs_packedtags2.o yaffs_nand.o yaffs_qsort.o +yaffs-y += yaffs_tagscompat.o yaffs_tagsvalidity.o +yaffs-y += yaffs_mtdif.o yaffs_mtdif1.o yaffs_mtdif2.o diff --git a/fs/yaffs2/devextras.h b/fs/yaffs2/devextras.h new file mode 100644 index 0000000..a9366fd --- /dev/null +++ b/fs/yaffs2/devextras.h @@ -0,0 +1,199 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +/* + * This file is just holds extra declarations of macros that would normally + * be providesd in the Linux kernel. These macros have been written from + * scratch but are functionally equivalent to the Linux ones. + * + */ + +#ifndef __EXTRAS_H__ +#define __EXTRAS_H__ + + +#if !(defined __KERNEL__) + +/* Definition of types */ +typedef unsigned char __u8; +typedef unsigned short __u16; +typedef unsigned __u32; + +#endif + +/* + * This is a simple doubly linked list implementation that matches the + * way the Linux kernel doubly linked list implementation works. + */ + +struct ylist_head { + struct ylist_head *next; /* next in chain */ + struct ylist_head *prev; /* previous in chain */ +}; + + +/* Initialise a static list */ +#define YLIST_HEAD(name) \ +struct ylist_head name = { &(name),&(name)} + + + +/* Initialise a list head to an empty list */ +#define YINIT_LIST_HEAD(p) \ +do { \ + (p)->next = (p);\ + (p)->prev = (p); \ +} while(0) + + +/* Add an element to a list */ +static __inline__ void ylist_add(struct ylist_head *newEntry, + struct ylist_head *list) +{ + struct ylist_head *listNext = list->next; + + list->next = newEntry; + newEntry->prev = list; + newEntry->next = listNext; + listNext->prev = newEntry; + +} + +static __inline__ void ylist_add_tail(struct ylist_head *newEntry, + struct ylist_head *list) +{ + struct ylist_head *listPrev = list->prev; + + list->prev = newEntry; + newEntry->next = list; + newEntry->prev = listPrev; + listPrev->next = newEntry; + +} + + +/* Take an element out of its current list, with or without + * reinitialising the links.of the entry*/ +static __inline__ void ylist_del(struct ylist_head *entry) +{ + struct ylist_head *listNext = entry->next; + struct ylist_head *listPrev = entry->prev; + + listNext->prev = listPrev; + listPrev->next = listNext; + +} + +static __inline__ void ylist_del_init(struct ylist_head *entry) +{ + ylist_del(entry); + entry->next = entry->prev = entry; +} + + +/* Test if the list is empty */ +static __inline__ int ylist_empty(struct ylist_head *entry) +{ + return (entry->next == entry); +} + + +/* ylist_entry takes a pointer to a list entry and offsets it to that + * we can find a pointer to the object it is embedded in. + */ + + +#define ylist_entry(entry, type, member) \ + ((type *)((char *)(entry)-(unsigned long)(&((type *)NULL)->member))) + + +/* ylist_for_each and list_for_each_safe iterate over lists. + * ylist_for_each_safe uses temporary storage to make the list delete safe + */ + +#define ylist_for_each(itervar, list) \ + for (itervar = (list)->next; itervar != (list); itervar = itervar->next ) + +#define ylist_for_each_safe(itervar,saveVar, list) \ + for (itervar = (list)->next, saveVar = (list)->next->next; itervar != (list); \ + itervar = saveVar, saveVar = saveVar->next) + + +#if !(defined __KERNEL__) + + +#ifndef WIN32 +#include +#endif + + +#ifdef CONFIG_YAFFS_PROVIDE_DEFS +/* File types */ + + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 + + +#ifndef WIN32 +#include +#endif + +/* + * Attribute flags. These should be or-ed together to figure out what + * has been changed! + */ +#define ATTR_MODE 1 +#define ATTR_UID 2 +#define ATTR_GID 4 +#define ATTR_SIZE 8 +#define ATTR_ATIME 16 +#define ATTR_MTIME 32 +#define ATTR_CTIME 64 + +struct iattr { + unsigned int ia_valid; + unsigned ia_mode; + unsigned ia_uid; + unsigned ia_gid; + unsigned ia_size; + unsigned ia_atime; + unsigned ia_mtime; + unsigned ia_ctime; + unsigned int ia_attr_flags; +}; + +#endif + + +#define KERN_DEBUG + +#else + +#include +#include +#include + +#endif + + +#endif diff --git a/fs/yaffs2/moduleconfig.h b/fs/yaffs2/moduleconfig.h new file mode 100644 index 0000000..ac5af6f --- /dev/null +++ b/fs/yaffs2/moduleconfig.h @@ -0,0 +1,65 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Martin Fouts + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_CONFIG_H__ +#define __YAFFS_CONFIG_H__ + +#ifdef YAFFS_OUT_OF_TREE + +/* DO NOT UNSET THESE THREE. YAFFS2 will not compile if you do. */ +#define CONFIG_YAFFS_FS +#define CONFIG_YAFFS_YAFFS1 +#define CONFIG_YAFFS_YAFFS2 + +/* These options are independent of each other. Select those that matter. */ + +/* Default: Not selected */ +/* Meaning: Yaffs does its own ECC, rather than using MTD ECC */ +//#define CONFIG_YAFFS_DOES_ECC + +/* Default: Not selected */ +/* Meaning: ECC byte order is 'wrong'. Only meaningful if */ +/* CONFIG_YAFFS_DOES_ECC is set */ +//#define CONFIG_YAFFS_ECC_WRONG_ORDER + +/* Default: Selected */ +/* Meaning: Disables testing whether chunks are erased before writing to them*/ +#define CONFIG_YAFFS_DISABLE_CHUNK_ERASED_CHECK + +/* Default: Selected */ +/* Meaning: Cache short names, taking more RAM, but faster look-ups */ +#define CONFIG_YAFFS_SHORT_NAMES_IN_RAM + +/* Default: 10 */ +/* Meaning: set the count of blocks to reserve for checkpointing */ +#define CONFIG_YAFFS_CHECKPOINT_RESERVED_BLOCKS 10 + +/* +Older-style on-NAND data format has a "pageStatus" byte to record +chunk/page state. This byte is zeroed when the page is discarded. +Choose this option if you have existing on-NAND data in this format +that you need to continue to support. New data written also uses the +older-style format. +Note: Use of this option generally requires that MTD's oob layout be +adjusted to use the older-style format. See notes on tags formats and +MTD versions in yaffs_mtdif1.c. +*/ +/* Default: Not selected */ +/* Meaning: Use older-style on-NAND data format with pageStatus byte */ +//#define CONFIG_YAFFS_9BYTE_TAGS + +#endif /* YAFFS_OUT_OF_TREE */ + +#endif /* __YAFFS_CONFIG_H__ */ diff --git a/fs/yaffs2/yaffs_checkptrw.c b/fs/yaffs2/yaffs_checkptrw.c new file mode 100644 index 0000000..7cf3776 --- /dev/null +++ b/fs/yaffs2/yaffs_checkptrw.c @@ -0,0 +1,405 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +const char *yaffs_checkptrw_c_version = + "$Id: yaffs_checkptrw.c,v 1.17 2008/08/12 22:51:57 charles Exp $"; + + +#include "yaffs_checkptrw.h" +#include "yaffs_getblockinfo.h" + +static int yaffs_CheckpointSpaceOk(yaffs_Device *dev) +{ + + int blocksAvailable = dev->nErasedBlocks - dev->nReservedBlocks; + + T(YAFFS_TRACE_CHECKPOINT, + (TSTR("checkpt blocks available = %d" TENDSTR), + blocksAvailable)); + + + return (blocksAvailable <= 0) ? 0 : 1; +} + + +static int yaffs_CheckpointErase(yaffs_Device *dev) +{ + + int i; + + + if(!dev->eraseBlockInNAND) + return 0; + T(YAFFS_TRACE_CHECKPOINT,(TSTR("checking blocks %d to %d"TENDSTR), + dev->internalStartBlock,dev->internalEndBlock)); + + for(i = dev->internalStartBlock; i <= dev->internalEndBlock; i++) { + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev,i); + if(bi->blockState == YAFFS_BLOCK_STATE_CHECKPOINT){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("erasing checkpt block %d"TENDSTR),i)); + if(dev->eraseBlockInNAND(dev,i- dev->blockOffset /* realign */)){ + bi->blockState = YAFFS_BLOCK_STATE_EMPTY; + dev->nErasedBlocks++; + dev->nFreeChunks += dev->nChunksPerBlock; + } + else { + dev->markNANDBlockBad(dev,i); + bi->blockState = YAFFS_BLOCK_STATE_DEAD; + } + } + } + + dev->blocksInCheckpoint = 0; + + return 1; +} + + +static void yaffs_CheckpointFindNextErasedBlock(yaffs_Device *dev) +{ + int i; + int blocksAvailable = dev->nErasedBlocks - dev->nReservedBlocks; + T(YAFFS_TRACE_CHECKPOINT, + (TSTR("allocating checkpt block: erased %d reserved %d avail %d next %d "TENDSTR), + dev->nErasedBlocks,dev->nReservedBlocks,blocksAvailable,dev->checkpointNextBlock)); + + if(dev->checkpointNextBlock >= 0 && + dev->checkpointNextBlock <= dev->internalEndBlock && + blocksAvailable > 0){ + + for(i = dev->checkpointNextBlock; i <= dev->internalEndBlock; i++){ + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev,i); + if(bi->blockState == YAFFS_BLOCK_STATE_EMPTY){ + dev->checkpointNextBlock = i + 1; + dev->checkpointCurrentBlock = i; + T(YAFFS_TRACE_CHECKPOINT,(TSTR("allocating checkpt block %d"TENDSTR),i)); + return; + } + } + } + T(YAFFS_TRACE_CHECKPOINT,(TSTR("out of checkpt blocks"TENDSTR))); + + dev->checkpointNextBlock = -1; + dev->checkpointCurrentBlock = -1; +} + +static void yaffs_CheckpointFindNextCheckpointBlock(yaffs_Device *dev) +{ + int i; + yaffs_ExtendedTags tags; + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("find next checkpt block: start: blocks %d next %d" TENDSTR), + dev->blocksInCheckpoint, dev->checkpointNextBlock)); + + if(dev->blocksInCheckpoint < dev->checkpointMaxBlocks) + for(i = dev->checkpointNextBlock; i <= dev->internalEndBlock; i++){ + int chunk = i * dev->nChunksPerBlock; + int realignedChunk = chunk - dev->chunkOffset; + + dev->readChunkWithTagsFromNAND(dev,realignedChunk,NULL,&tags); + T(YAFFS_TRACE_CHECKPOINT,(TSTR("find next checkpt block: search: block %d oid %d seq %d eccr %d" TENDSTR), + i, tags.objectId,tags.sequenceNumber,tags.eccResult)); + + if(tags.sequenceNumber == YAFFS_SEQUENCE_CHECKPOINT_DATA){ + /* Right kind of block */ + dev->checkpointNextBlock = tags.objectId; + dev->checkpointCurrentBlock = i; + dev->checkpointBlockList[dev->blocksInCheckpoint] = i; + dev->blocksInCheckpoint++; + T(YAFFS_TRACE_CHECKPOINT,(TSTR("found checkpt block %d"TENDSTR),i)); + return; + } + } + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("found no more checkpt blocks"TENDSTR))); + + dev->checkpointNextBlock = -1; + dev->checkpointCurrentBlock = -1; +} + + +int yaffs_CheckpointOpen(yaffs_Device *dev, int forWriting) +{ + + /* Got the functions we need? */ + if (!dev->writeChunkWithTagsToNAND || + !dev->readChunkWithTagsFromNAND || + !dev->eraseBlockInNAND || + !dev->markNANDBlockBad) + return 0; + + if(forWriting && !yaffs_CheckpointSpaceOk(dev)) + return 0; + + if(!dev->checkpointBuffer) + dev->checkpointBuffer = YMALLOC_DMA(dev->totalBytesPerChunk); + if(!dev->checkpointBuffer) + return 0; + + + dev->checkpointPageSequence = 0; + + dev->checkpointOpenForWrite = forWriting; + + dev->checkpointByteCount = 0; + dev->checkpointSum = 0; + dev->checkpointXor = 0; + dev->checkpointCurrentBlock = -1; + dev->checkpointCurrentChunk = -1; + dev->checkpointNextBlock = dev->internalStartBlock; + + /* Erase all the blocks in the checkpoint area */ + if(forWriting){ + memset(dev->checkpointBuffer,0,dev->nDataBytesPerChunk); + dev->checkpointByteOffset = 0; + return yaffs_CheckpointErase(dev); + + + } else { + int i; + /* Set to a value that will kick off a read */ + dev->checkpointByteOffset = dev->nDataBytesPerChunk; + /* A checkpoint block list of 1 checkpoint block per 16 block is (hopefully) + * going to be way more than we need */ + dev->blocksInCheckpoint = 0; + dev->checkpointMaxBlocks = (dev->internalEndBlock - dev->internalStartBlock)/16 + 2; + dev->checkpointBlockList = YMALLOC(sizeof(int) * dev->checkpointMaxBlocks); + for(i = 0; i < dev->checkpointMaxBlocks; i++) + dev->checkpointBlockList[i] = -1; + } + + return 1; +} + +int yaffs_GetCheckpointSum(yaffs_Device *dev, __u32 *sum) +{ + __u32 compositeSum; + compositeSum = (dev->checkpointSum << 8) | (dev->checkpointXor & 0xFF); + *sum = compositeSum; + return 1; +} + +static int yaffs_CheckpointFlushBuffer(yaffs_Device *dev) +{ + + int chunk; + int realignedChunk; + + yaffs_ExtendedTags tags; + + if(dev->checkpointCurrentBlock < 0){ + yaffs_CheckpointFindNextErasedBlock(dev); + dev->checkpointCurrentChunk = 0; + } + + if(dev->checkpointCurrentBlock < 0) + return 0; + + tags.chunkDeleted = 0; + tags.objectId = dev->checkpointNextBlock; /* Hint to next place to look */ + tags.chunkId = dev->checkpointPageSequence + 1; + tags.sequenceNumber = YAFFS_SEQUENCE_CHECKPOINT_DATA; + tags.byteCount = dev->nDataBytesPerChunk; + if(dev->checkpointCurrentChunk == 0){ + /* First chunk we write for the block? Set block state to + checkpoint */ + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev,dev->checkpointCurrentBlock); + bi->blockState = YAFFS_BLOCK_STATE_CHECKPOINT; + dev->blocksInCheckpoint++; + } + + chunk = dev->checkpointCurrentBlock * dev->nChunksPerBlock + dev->checkpointCurrentChunk; + + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("checkpoint wite buffer nand %d(%d:%d) objid %d chId %d" TENDSTR), + chunk, dev->checkpointCurrentBlock, dev->checkpointCurrentChunk,tags.objectId,tags.chunkId)); + + realignedChunk = chunk - dev->chunkOffset; + + dev->writeChunkWithTagsToNAND(dev,realignedChunk,dev->checkpointBuffer,&tags); + dev->checkpointByteOffset = 0; + dev->checkpointPageSequence++; + dev->checkpointCurrentChunk++; + if(dev->checkpointCurrentChunk >= dev->nChunksPerBlock){ + dev->checkpointCurrentChunk = 0; + dev->checkpointCurrentBlock = -1; + } + memset(dev->checkpointBuffer,0,dev->nDataBytesPerChunk); + + return 1; +} + + +int yaffs_CheckpointWrite(yaffs_Device *dev,const void *data, int nBytes) +{ + int i=0; + int ok = 1; + + + __u8 * dataBytes = (__u8 *)data; + + + + if(!dev->checkpointBuffer) + return 0; + + if(!dev->checkpointOpenForWrite) + return -1; + + while(i < nBytes && ok) { + + + + dev->checkpointBuffer[dev->checkpointByteOffset] = *dataBytes ; + dev->checkpointSum += *dataBytes; + dev->checkpointXor ^= *dataBytes; + + dev->checkpointByteOffset++; + i++; + dataBytes++; + dev->checkpointByteCount++; + + + if(dev->checkpointByteOffset < 0 || + dev->checkpointByteOffset >= dev->nDataBytesPerChunk) + ok = yaffs_CheckpointFlushBuffer(dev); + + } + + return i; +} + +int yaffs_CheckpointRead(yaffs_Device *dev, void *data, int nBytes) +{ + int i=0; + int ok = 1; + yaffs_ExtendedTags tags; + + + int chunk; + int realignedChunk; + + __u8 *dataBytes = (__u8 *)data; + + if(!dev->checkpointBuffer) + return 0; + + if(dev->checkpointOpenForWrite) + return -1; + + while(i < nBytes && ok) { + + + if(dev->checkpointByteOffset < 0 || + dev->checkpointByteOffset >= dev->nDataBytesPerChunk) { + + if(dev->checkpointCurrentBlock < 0){ + yaffs_CheckpointFindNextCheckpointBlock(dev); + dev->checkpointCurrentChunk = 0; + } + + if(dev->checkpointCurrentBlock < 0) + ok = 0; + else { + + chunk = dev->checkpointCurrentBlock * dev->nChunksPerBlock + + dev->checkpointCurrentChunk; + + realignedChunk = chunk - dev->chunkOffset; + + /* read in the next chunk */ + /* printf("read checkpoint page %d\n",dev->checkpointPage); */ + dev->readChunkWithTagsFromNAND(dev, realignedChunk, + dev->checkpointBuffer, + &tags); + + if(tags.chunkId != (dev->checkpointPageSequence + 1) || + tags.eccResult > YAFFS_ECC_RESULT_FIXED || + tags.sequenceNumber != YAFFS_SEQUENCE_CHECKPOINT_DATA) + ok = 0; + + dev->checkpointByteOffset = 0; + dev->checkpointPageSequence++; + dev->checkpointCurrentChunk++; + + if(dev->checkpointCurrentChunk >= dev->nChunksPerBlock) + dev->checkpointCurrentBlock = -1; + } + } + + if(ok){ + *dataBytes = dev->checkpointBuffer[dev->checkpointByteOffset]; + dev->checkpointSum += *dataBytes; + dev->checkpointXor ^= *dataBytes; + dev->checkpointByteOffset++; + i++; + dataBytes++; + dev->checkpointByteCount++; + } + } + + return i; +} + +int yaffs_CheckpointClose(yaffs_Device *dev) +{ + + if(dev->checkpointOpenForWrite){ + if(dev->checkpointByteOffset != 0) + yaffs_CheckpointFlushBuffer(dev); + } else { + int i; + for(i = 0; i < dev->blocksInCheckpoint && dev->checkpointBlockList[i] >= 0; i++){ + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev,dev->checkpointBlockList[i]); + if(bi->blockState == YAFFS_BLOCK_STATE_EMPTY) + bi->blockState = YAFFS_BLOCK_STATE_CHECKPOINT; + else { + // Todo this looks odd... + } + } + YFREE(dev->checkpointBlockList); + dev->checkpointBlockList = NULL; + } + + dev->nFreeChunks -= dev->blocksInCheckpoint * dev->nChunksPerBlock; + dev->nErasedBlocks -= dev->blocksInCheckpoint; + + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("checkpoint byte count %d" TENDSTR), + dev->checkpointByteCount)); + + if(dev->checkpointBuffer){ + /* free the buffer */ + YFREE(dev->checkpointBuffer); + dev->checkpointBuffer = NULL; + return 1; + } + else + return 0; + +} + +int yaffs_CheckpointInvalidateStream(yaffs_Device *dev) +{ + /* Erase the first checksum block */ + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("checkpoint invalidate"TENDSTR))); + + if(!yaffs_CheckpointSpaceOk(dev)) + return 0; + + return yaffs_CheckpointErase(dev); +} + + + diff --git a/fs/yaffs2/yaffs_checkptrw.h b/fs/yaffs2/yaffs_checkptrw.h new file mode 100644 index 0000000..d3ff174 --- /dev/null +++ b/fs/yaffs2/yaffs_checkptrw.h @@ -0,0 +1,35 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_CHECKPTRW_H__ +#define __YAFFS_CHECKPTRW_H__ + +#include "yaffs_guts.h" + +int yaffs_CheckpointOpen(yaffs_Device *dev, int forWriting); + +int yaffs_CheckpointWrite(yaffs_Device *dev,const void *data, int nBytes); + +int yaffs_CheckpointRead(yaffs_Device *dev,void *data, int nBytes); + +int yaffs_GetCheckpointSum(yaffs_Device *dev, __u32 *sum); + +int yaffs_CheckpointClose(yaffs_Device *dev); + +int yaffs_CheckpointInvalidateStream(yaffs_Device *dev); + + +#endif + diff --git a/fs/yaffs2/yaffs_ecc.c b/fs/yaffs2/yaffs_ecc.c new file mode 100644 index 0000000..b099835 --- /dev/null +++ b/fs/yaffs2/yaffs_ecc.c @@ -0,0 +1,331 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * This code implements the ECC algorithm used in SmartMedia. + * + * The ECC comprises 22 bits of parity information and is stuffed into 3 bytes. + * The two unused bit are set to 1. + * The ECC can correct single bit errors in a 256-byte page of data. Thus, two such ECC + * blocks are used on a 512-byte NAND page. + * + */ + +/* Table generated by gen-ecc.c + * Using a table means we do not have to calculate p1..p4 and p1'..p4' + * for each byte of data. These are instead provided in a table in bits7..2. + * Bit 0 of each entry indicates whether the entry has an odd or even parity, and therefore + * this bytes influence on the line parity. + */ + +const char *yaffs_ecc_c_version = + "$Id: yaffs_ecc.c,v 1.10 2007/12/13 15:35:17 wookey Exp $"; + +#include "yportenv.h" + +#include "yaffs_ecc.h" + +static const unsigned char column_parity_table[] = { + 0x00, 0x55, 0x59, 0x0c, 0x65, 0x30, 0x3c, 0x69, + 0x69, 0x3c, 0x30, 0x65, 0x0c, 0x59, 0x55, 0x00, + 0x95, 0xc0, 0xcc, 0x99, 0xf0, 0xa5, 0xa9, 0xfc, + 0xfc, 0xa9, 0xa5, 0xf0, 0x99, 0xcc, 0xc0, 0x95, + 0x99, 0xcc, 0xc0, 0x95, 0xfc, 0xa9, 0xa5, 0xf0, + 0xf0, 0xa5, 0xa9, 0xfc, 0x95, 0xc0, 0xcc, 0x99, + 0x0c, 0x59, 0x55, 0x00, 0x69, 0x3c, 0x30, 0x65, + 0x65, 0x30, 0x3c, 0x69, 0x00, 0x55, 0x59, 0x0c, + 0xa5, 0xf0, 0xfc, 0xa9, 0xc0, 0x95, 0x99, 0xcc, + 0xcc, 0x99, 0x95, 0xc0, 0xa9, 0xfc, 0xf0, 0xa5, + 0x30, 0x65, 0x69, 0x3c, 0x55, 0x00, 0x0c, 0x59, + 0x59, 0x0c, 0x00, 0x55, 0x3c, 0x69, 0x65, 0x30, + 0x3c, 0x69, 0x65, 0x30, 0x59, 0x0c, 0x00, 0x55, + 0x55, 0x00, 0x0c, 0x59, 0x30, 0x65, 0x69, 0x3c, + 0xa9, 0xfc, 0xf0, 0xa5, 0xcc, 0x99, 0x95, 0xc0, + 0xc0, 0x95, 0x99, 0xcc, 0xa5, 0xf0, 0xfc, 0xa9, + 0xa9, 0xfc, 0xf0, 0xa5, 0xcc, 0x99, 0x95, 0xc0, + 0xc0, 0x95, 0x99, 0xcc, 0xa5, 0xf0, 0xfc, 0xa9, + 0x3c, 0x69, 0x65, 0x30, 0x59, 0x0c, 0x00, 0x55, + 0x55, 0x00, 0x0c, 0x59, 0x30, 0x65, 0x69, 0x3c, + 0x30, 0x65, 0x69, 0x3c, 0x55, 0x00, 0x0c, 0x59, + 0x59, 0x0c, 0x00, 0x55, 0x3c, 0x69, 0x65, 0x30, + 0xa5, 0xf0, 0xfc, 0xa9, 0xc0, 0x95, 0x99, 0xcc, + 0xcc, 0x99, 0x95, 0xc0, 0xa9, 0xfc, 0xf0, 0xa5, + 0x0c, 0x59, 0x55, 0x00, 0x69, 0x3c, 0x30, 0x65, + 0x65, 0x30, 0x3c, 0x69, 0x00, 0x55, 0x59, 0x0c, + 0x99, 0xcc, 0xc0, 0x95, 0xfc, 0xa9, 0xa5, 0xf0, + 0xf0, 0xa5, 0xa9, 0xfc, 0x95, 0xc0, 0xcc, 0x99, + 0x95, 0xc0, 0xcc, 0x99, 0xf0, 0xa5, 0xa9, 0xfc, + 0xfc, 0xa9, 0xa5, 0xf0, 0x99, 0xcc, 0xc0, 0x95, + 0x00, 0x55, 0x59, 0x0c, 0x65, 0x30, 0x3c, 0x69, + 0x69, 0x3c, 0x30, 0x65, 0x0c, 0x59, 0x55, 0x00, +}; + +/* Count the bits in an unsigned char or a U32 */ + +static int yaffs_CountBits(unsigned char x) +{ + int r = 0; + while (x) { + if (x & 1) + r++; + x >>= 1; + } + return r; +} + +static int yaffs_CountBits32(unsigned x) +{ + int r = 0; + while (x) { + if (x & 1) + r++; + x >>= 1; + } + return r; +} + +/* Calculate the ECC for a 256-byte block of data */ +void yaffs_ECCCalculate(const unsigned char *data, unsigned char *ecc) +{ + unsigned int i; + + unsigned char col_parity = 0; + unsigned char line_parity = 0; + unsigned char line_parity_prime = 0; + unsigned char t; + unsigned char b; + + for (i = 0; i < 256; i++) { + b = column_parity_table[*data++]; + col_parity ^= b; + + if (b & 0x01) // odd number of bits in the byte + { + line_parity ^= i; + line_parity_prime ^= ~i; + } + + } + + ecc[2] = (~col_parity) | 0x03; + + t = 0; + if (line_parity & 0x80) + t |= 0x80; + if (line_parity_prime & 0x80) + t |= 0x40; + if (line_parity & 0x40) + t |= 0x20; + if (line_parity_prime & 0x40) + t |= 0x10; + if (line_parity & 0x20) + t |= 0x08; + if (line_parity_prime & 0x20) + t |= 0x04; + if (line_parity & 0x10) + t |= 0x02; + if (line_parity_prime & 0x10) + t |= 0x01; + ecc[1] = ~t; + + t = 0; + if (line_parity & 0x08) + t |= 0x80; + if (line_parity_prime & 0x08) + t |= 0x40; + if (line_parity & 0x04) + t |= 0x20; + if (line_parity_prime & 0x04) + t |= 0x10; + if (line_parity & 0x02) + t |= 0x08; + if (line_parity_prime & 0x02) + t |= 0x04; + if (line_parity & 0x01) + t |= 0x02; + if (line_parity_prime & 0x01) + t |= 0x01; + ecc[0] = ~t; + +#ifdef CONFIG_YAFFS_ECC_WRONG_ORDER + // Swap the bytes into the wrong order + t = ecc[0]; + ecc[0] = ecc[1]; + ecc[1] = t; +#endif +} + + +/* Correct the ECC on a 256 byte block of data */ + +int yaffs_ECCCorrect(unsigned char *data, unsigned char *read_ecc, + const unsigned char *test_ecc) +{ + unsigned char d0, d1, d2; /* deltas */ + + d0 = read_ecc[0] ^ test_ecc[0]; + d1 = read_ecc[1] ^ test_ecc[1]; + d2 = read_ecc[2] ^ test_ecc[2]; + + if ((d0 | d1 | d2) == 0) + return 0; /* no error */ + + if (((d0 ^ (d0 >> 1)) & 0x55) == 0x55 && + ((d1 ^ (d1 >> 1)) & 0x55) == 0x55 && + ((d2 ^ (d2 >> 1)) & 0x54) == 0x54) { + /* Single bit (recoverable) error in data */ + + unsigned byte; + unsigned bit; + +#ifdef CONFIG_YAFFS_ECC_WRONG_ORDER + // swap the bytes to correct for the wrong order + unsigned char t; + + t = d0; + d0 = d1; + d1 = t; +#endif + + bit = byte = 0; + + if (d1 & 0x80) + byte |= 0x80; + if (d1 & 0x20) + byte |= 0x40; + if (d1 & 0x08) + byte |= 0x20; + if (d1 & 0x02) + byte |= 0x10; + if (d0 & 0x80) + byte |= 0x08; + if (d0 & 0x20) + byte |= 0x04; + if (d0 & 0x08) + byte |= 0x02; + if (d0 & 0x02) + byte |= 0x01; + + if (d2 & 0x80) + bit |= 0x04; + if (d2 & 0x20) + bit |= 0x02; + if (d2 & 0x08) + bit |= 0x01; + + data[byte] ^= (1 << bit); + + return 1; /* Corrected the error */ + } + + if ((yaffs_CountBits(d0) + + yaffs_CountBits(d1) + + yaffs_CountBits(d2)) == 1) { + /* Reccoverable error in ecc */ + + read_ecc[0] = test_ecc[0]; + read_ecc[1] = test_ecc[1]; + read_ecc[2] = test_ecc[2]; + + return 1; /* Corrected the error */ + } + + /* Unrecoverable error */ + + return -1; + +} + + +/* + * ECCxxxOther does ECC calcs on arbitrary n bytes of data + */ +void yaffs_ECCCalculateOther(const unsigned char *data, unsigned nBytes, + yaffs_ECCOther * eccOther) +{ + unsigned int i; + + unsigned char col_parity = 0; + unsigned line_parity = 0; + unsigned line_parity_prime = 0; + unsigned char b; + + for (i = 0; i < nBytes; i++) { + b = column_parity_table[*data++]; + col_parity ^= b; + + if (b & 0x01) { + /* odd number of bits in the byte */ + line_parity ^= i; + line_parity_prime ^= ~i; + } + + } + + eccOther->colParity = (col_parity >> 2) & 0x3f; + eccOther->lineParity = line_parity; + eccOther->lineParityPrime = line_parity_prime; +} + +int yaffs_ECCCorrectOther(unsigned char *data, unsigned nBytes, + yaffs_ECCOther * read_ecc, + const yaffs_ECCOther * test_ecc) +{ + unsigned char cDelta; /* column parity delta */ + unsigned lDelta; /* line parity delta */ + unsigned lDeltaPrime; /* line parity delta */ + unsigned bit; + + cDelta = read_ecc->colParity ^ test_ecc->colParity; + lDelta = read_ecc->lineParity ^ test_ecc->lineParity; + lDeltaPrime = read_ecc->lineParityPrime ^ test_ecc->lineParityPrime; + + if ((cDelta | lDelta | lDeltaPrime) == 0) + return 0; /* no error */ + + if (lDelta == ~lDeltaPrime && + (((cDelta ^ (cDelta >> 1)) & 0x15) == 0x15)) + { + /* Single bit (recoverable) error in data */ + + bit = 0; + + if (cDelta & 0x20) + bit |= 0x04; + if (cDelta & 0x08) + bit |= 0x02; + if (cDelta & 0x02) + bit |= 0x01; + + if(lDelta >= nBytes) + return -1; + + data[lDelta] ^= (1 << bit); + + return 1; /* corrected */ + } + + if ((yaffs_CountBits32(lDelta) + yaffs_CountBits32(lDeltaPrime) + + yaffs_CountBits(cDelta)) == 1) { + /* Reccoverable error in ecc */ + + *read_ecc = *test_ecc; + return 1; /* corrected */ + } + + /* Unrecoverable error */ + + return -1; + +} + diff --git a/fs/yaffs2/yaffs_ecc.h b/fs/yaffs2/yaffs_ecc.h new file mode 100644 index 0000000..79bc3d1 --- /dev/null +++ b/fs/yaffs2/yaffs_ecc.h @@ -0,0 +1,44 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + + /* + * This code implements the ECC algorithm used in SmartMedia. + * + * The ECC comprises 22 bits of parity information and is stuffed into 3 bytes. + * The two unused bit are set to 1. + * The ECC can correct single bit errors in a 256-byte page of data. Thus, two such ECC + * blocks are used on a 512-byte NAND page. + * + */ + +#ifndef __YAFFS_ECC_H__ +#define __YAFFS_ECC_H__ + +typedef struct { + unsigned char colParity; + unsigned lineParity; + unsigned lineParityPrime; +} yaffs_ECCOther; + +void yaffs_ECCCalculate(const unsigned char *data, unsigned char *ecc); +int yaffs_ECCCorrect(unsigned char *data, unsigned char *read_ecc, + const unsigned char *test_ecc); + +void yaffs_ECCCalculateOther(const unsigned char *data, unsigned nBytes, + yaffs_ECCOther * ecc); +int yaffs_ECCCorrectOther(unsigned char *data, unsigned nBytes, + yaffs_ECCOther * read_ecc, + const yaffs_ECCOther * test_ecc); +#endif diff --git a/fs/yaffs2/yaffs_fs.c b/fs/yaffs2/yaffs_fs.c new file mode 100644 index 0000000..0c09033 --- /dev/null +++ b/fs/yaffs2/yaffs_fs.c @@ -0,0 +1,2393 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * Acknowledgements: + * Luc van OostenRyck for numerous patches. + * Nick Bane for numerous patches. + * Nick Bane for 2.5/2.6 integration. + * Andras Toth for mknod rdev issue. + * Michael Fischer for finding the problem with inode inconsistency. + * Some code bodily lifted from JFFS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * + * This is the file system front-end to YAFFS that hooks it up to + * the VFS. + * + * Special notes: + * >> 2.4: sb->u.generic_sbp points to the yaffs_Device associated with + * this superblock + * >> 2.6: sb->s_fs_info points to the yaffs_Device associated with this + * superblock + * >> inode->u.generic_ip points to the associated yaffs_Object. + */ + +const char *yaffs_fs_c_version = + "$Id: yaffs_fs.c,v 1.69 2008/08/28 02:42:11 charles Exp $"; +extern const char *yaffs_guts_c_version; + +#include +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "asm/div64.h" + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + +#include /* Added NCB 15-8-2003 */ +#include +#define UnlockPage(p) unlock_page(p) +#define Page_Uptodate(page) test_bit(PG_uptodate, &(page)->flags) + +/* FIXME: use sb->s_id instead ? */ +#define yaffs_devname(sb, buf) bdevname(sb->s_bdev, buf) + +#else + +#include +#define BDEVNAME_SIZE 0 +#define yaffs_devname(sb, buf) kdevname(sb->s_dev) + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) +/* added NCB 26/5/2006 for 2.4.25-vrs2-tcl1 kernel */ +#define __user +#endif + +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)) +#define YPROC_ROOT &proc_root +#else +#define YPROC_ROOT NULL +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +#define WRITE_SIZE_STR "writesize" +#define WRITE_SIZE(mtd) (mtd)->writesize +#else +#define WRITE_SIZE_STR "oobblock" +#define WRITE_SIZE(mtd) (mtd)->oobblock +#endif + +#include + +#include "yportenv.h" +#include "yaffs_guts.h" + +#include +#include "yaffs_mtdif.h" +#include "yaffs_mtdif1.h" +#include "yaffs_mtdif2.h" + +unsigned int yaffs_traceMask = YAFFS_TRACE_BAD_BLOCKS; +unsigned int yaffs_wr_attempts = YAFFS_WR_ATTEMPTS; + +/* Module Parameters */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +module_param(yaffs_traceMask,uint,0644); +module_param(yaffs_wr_attempts,uint,0644); +#else +MODULE_PARM(yaffs_traceMask,"i"); +MODULE_PARM(yaffs_wr_attempts,"i"); +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)) +/* use iget and read_inode */ +#define Y_IGET(sb,inum) iget((sb),(inum)) +static void yaffs_read_inode(struct inode *inode); + +#else +/* Call local equivalent */ +#define YAFFS_USE_OWN_IGET +#define Y_IGET(sb,inum) yaffs_iget((sb),(inum)) + +static struct inode * yaffs_iget(struct super_block *sb, unsigned long ino); +#endif + +/*#define T(x) printk x */ + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) +#define yaffs_InodeToObjectLV(iptr) (iptr)->i_private +#else +#define yaffs_InodeToObjectLV(iptr) (iptr)->u.generic_ip +#endif + +#define yaffs_InodeToObject(iptr) ((yaffs_Object *)(yaffs_InodeToObjectLV(iptr))) +#define yaffs_DentryToObject(dptr) yaffs_InodeToObject((dptr)->d_inode) + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +#define yaffs_SuperToDevice(sb) ((yaffs_Device *)sb->s_fs_info) +#else +#define yaffs_SuperToDevice(sb) ((yaffs_Device *)sb->u.generic_sbp) +#endif + +static void yaffs_put_super(struct super_block *sb); + +static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n, + loff_t * pos); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_file_flush(struct file *file, fl_owner_t id); +#else +static int yaffs_file_flush(struct file *file); +#endif + +static int yaffs_sync_object(struct file *file, struct dentry *dentry, + int datasync); + +static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *n); +static struct dentry *yaffs_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *n); +#else +static int yaffs_create(struct inode *dir, struct dentry *dentry, int mode); +static struct dentry *yaffs_lookup(struct inode *dir, struct dentry *dentry); +#endif +static int yaffs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry); +static int yaffs_unlink(struct inode *dir, struct dentry *dentry); +static int yaffs_symlink(struct inode *dir, struct dentry *dentry, + const char *symname); +static int yaffs_mkdir(struct inode *dir, struct dentry *dentry, int mode); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, + dev_t dev); +#else +static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, + int dev); +#endif +static int yaffs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); +static int yaffs_setattr(struct dentry *dentry, struct iattr *attr); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_sync_fs(struct super_block *sb, int wait); +static void yaffs_write_super(struct super_block *sb); +#else +static int yaffs_sync_fs(struct super_block *sb); +static int yaffs_write_super(struct super_block *sb); +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_statfs(struct dentry *dentry, struct kstatfs *buf); +#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_statfs(struct super_block *sb, struct kstatfs *buf); +#else +static int yaffs_statfs(struct super_block *sb, struct statfs *buf); +#endif + +#ifdef YAFFS_HAS_PUT_INODE +static void yaffs_put_inode(struct inode *inode); +#endif + +static void yaffs_delete_inode(struct inode *); +static void yaffs_clear_inode(struct inode *); + +static int yaffs_readpage(struct file *file, struct page *page); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_writepage(struct page *page, struct writeback_control *wbc); +#else +static int yaffs_writepage(struct page *page); +#endif +static int yaffs_prepare_write(struct file *f, struct page *pg, + unsigned offset, unsigned to); +static int yaffs_commit_write(struct file *f, struct page *pg, unsigned offset, + unsigned to); + +static int yaffs_readlink(struct dentry *dentry, char __user * buffer, + int buflen); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13)) +static void *yaffs_follow_link(struct dentry *dentry, struct nameidata *nd); +#else +static int yaffs_follow_link(struct dentry *dentry, struct nameidata *nd); +#endif + +static struct address_space_operations yaffs_file_address_operations = { + .readpage = yaffs_readpage, + .writepage = yaffs_writepage, + .prepare_write = yaffs_prepare_write, + .commit_write = yaffs_commit_write, +}; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,22)) +static struct file_operations yaffs_file_operations = { + .read = do_sync_read, + .write = do_sync_write, + .aio_read = generic_file_aio_read, + .aio_write = generic_file_aio_write, + .mmap = generic_file_mmap, + .flush = yaffs_file_flush, + .fsync = yaffs_sync_object, + .splice_read = generic_file_splice_read, + .splice_write = generic_file_splice_write, +}; + +#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) + +static struct file_operations yaffs_file_operations = { + .read = do_sync_read, + .write = do_sync_write, + .aio_read = generic_file_aio_read, + .aio_write = generic_file_aio_write, + .mmap = generic_file_mmap, + .flush = yaffs_file_flush, + .fsync = yaffs_sync_object, + .sendfile = generic_file_sendfile, +}; + +#else + +static struct file_operations yaffs_file_operations = { + .read = generic_file_read, + .write = generic_file_write, + .mmap = generic_file_mmap, + .flush = yaffs_file_flush, + .fsync = yaffs_sync_object, +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + .sendfile = generic_file_sendfile, +#endif +}; +#endif + +static struct inode_operations yaffs_file_inode_operations = { + .setattr = yaffs_setattr, +}; + +static struct inode_operations yaffs_symlink_inode_operations = { + .readlink = yaffs_readlink, + .follow_link = yaffs_follow_link, + .setattr = yaffs_setattr, +}; + +static struct inode_operations yaffs_dir_inode_operations = { + .create = yaffs_create, + .lookup = yaffs_lookup, + .link = yaffs_link, + .unlink = yaffs_unlink, + .symlink = yaffs_symlink, + .mkdir = yaffs_mkdir, + .rmdir = yaffs_unlink, + .mknod = yaffs_mknod, + .rename = yaffs_rename, + .setattr = yaffs_setattr, +}; + +static struct file_operations yaffs_dir_operations = { + .read = generic_read_dir, + .readdir = yaffs_readdir, + .fsync = yaffs_sync_object, +}; + +static struct super_operations yaffs_super_ops = { + .statfs = yaffs_statfs, + +#ifndef YAFFS_USE_OWN_IGET + .read_inode = yaffs_read_inode, +#endif +#ifdef YAFFS_HAS_PUT_INODE + .put_inode = yaffs_put_inode, +#endif + .put_super = yaffs_put_super, + .delete_inode = yaffs_delete_inode, + .clear_inode = yaffs_clear_inode, + .sync_fs = yaffs_sync_fs, + .write_super = yaffs_write_super, +}; + +static void yaffs_GrossLock(yaffs_Device * dev) +{ + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs locking\n")); + + down(&dev->grossLock); +} + +static void yaffs_GrossUnlock(yaffs_Device * dev) +{ + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs unlocking\n")); + up(&dev->grossLock); + +} + +static int yaffs_readlink(struct dentry *dentry, char __user * buffer, + int buflen) +{ + unsigned char *alias; + int ret; + + yaffs_Device *dev = yaffs_DentryToObject(dentry)->myDev; + + yaffs_GrossLock(dev); + + alias = yaffs_GetSymlinkAlias(yaffs_DentryToObject(dentry)); + + yaffs_GrossUnlock(dev); + + if (!alias) + return -ENOMEM; + + ret = vfs_readlink(dentry, buffer, buflen, alias); + kfree(alias); + return ret; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13)) +static void *yaffs_follow_link(struct dentry *dentry, struct nameidata *nd) +#else +static int yaffs_follow_link(struct dentry *dentry, struct nameidata *nd) +#endif +{ + unsigned char *alias; + int ret; + yaffs_Device *dev = yaffs_DentryToObject(dentry)->myDev; + + yaffs_GrossLock(dev); + + alias = yaffs_GetSymlinkAlias(yaffs_DentryToObject(dentry)); + + yaffs_GrossUnlock(dev); + + if (!alias) + { + ret = -ENOMEM; + goto out; + } + + ret = vfs_follow_link(nd, alias); + kfree(alias); +out: +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13)) + return ERR_PTR (ret); +#else + return ret; +#endif +} + +struct inode *yaffs_get_inode(struct super_block *sb, int mode, int dev, + yaffs_Object * obj); + +/* + * Lookup is used to find objects in the fs + */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + +static struct dentry *yaffs_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *n) +#else +static struct dentry *yaffs_lookup(struct inode *dir, struct dentry *dentry) +#endif +{ + yaffs_Object *obj; + struct inode *inode = NULL; /* NCB 2.5/2.6 needs NULL here */ + + yaffs_Device *dev = yaffs_InodeToObject(dir)->myDev; + + yaffs_GrossLock(dev); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_lookup for %d:%s\n", + yaffs_InodeToObject(dir)->objectId, dentry->d_name.name)); + + obj = + yaffs_FindObjectByName(yaffs_InodeToObject(dir), + dentry->d_name.name); + + obj = yaffs_GetEquivalentObject(obj); /* in case it was a hardlink */ + + /* Can't hold gross lock when calling yaffs_get_inode() */ + yaffs_GrossUnlock(dev); + + if (obj) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_lookup found %d\n", obj->objectId)); + + inode = yaffs_get_inode(dir->i_sb, obj->yst_mode, 0, obj); + + if (inode) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_loookup dentry \n")); +/* #if 0 asserted by NCB for 2.5/6 compatability - falls through to + * d_add even if NULL inode */ +#if 0 + /*dget(dentry); // try to solve directory bug */ + d_add(dentry, inode); + + /* return dentry; */ + return NULL; +#endif + } + + } else { + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_lookup not found\n")); + + } + +/* added NCB for 2.5/6 compatability - forces add even if inode is + * NULL which creates dentry hash */ + d_add(dentry, inode); + + return NULL; + /* return (ERR_PTR(-EIO)); */ + +} + + +#ifdef YAFFS_HAS_PUT_INODE + +/* For now put inode is just for debugging + * Put inode is called when the inode **structure** is put. + */ +static void yaffs_put_inode(struct inode *inode) +{ + T(YAFFS_TRACE_OS, + ("yaffs_put_inode: ino %d, count %d\n", (int)inode->i_ino, + atomic_read(&inode->i_count))); + +} +#endif + +/* clear is called to tell the fs to release any per-inode data it holds */ +static void yaffs_clear_inode(struct inode *inode) +{ + yaffs_Object *obj; + yaffs_Device *dev; + + obj = yaffs_InodeToObject(inode); + + T(YAFFS_TRACE_OS, + ("yaffs_clear_inode: ino %d, count %d %s\n", (int)inode->i_ino, + atomic_read(&inode->i_count), + obj ? "object exists" : "null object")); + + if (obj) { + dev = obj->myDev; + yaffs_GrossLock(dev); + + /* Clear the association between the inode and + * the yaffs_Object. + */ + obj->myInode = NULL; + yaffs_InodeToObjectLV(inode) = NULL; + + /* If the object freeing was deferred, then the real + * free happens now. + * This should fix the inode inconsistency problem. + */ + + yaffs_HandleDeferedFree(obj); + + yaffs_GrossUnlock(dev); + } + +} + +/* delete is called when the link count is zero and the inode + * is put (ie. nobody wants to know about it anymore, time to + * delete the file). + * NB Must call clear_inode() + */ +static void yaffs_delete_inode(struct inode *inode) +{ + yaffs_Object *obj = yaffs_InodeToObject(inode); + yaffs_Device *dev; + + T(YAFFS_TRACE_OS, + ("yaffs_delete_inode: ino %d, count %d %s\n", (int)inode->i_ino, + atomic_read(&inode->i_count), + obj ? "object exists" : "null object")); + + if (obj) { + dev = obj->myDev; + yaffs_GrossLock(dev); + yaffs_DeleteFile(obj); + yaffs_GrossUnlock(dev); + } +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,13)) + truncate_inode_pages (&inode->i_data, 0); +#endif + clear_inode(inode); +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_file_flush(struct file *file, fl_owner_t id) +#else +static int yaffs_file_flush(struct file *file) +#endif +{ + yaffs_Object *obj = yaffs_DentryToObject(file->f_dentry); + + yaffs_Device *dev = obj->myDev; + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_file_flush object %d (%s)\n", obj->objectId, + obj->dirty ? "dirty" : "clean")); + + yaffs_GrossLock(dev); + + yaffs_FlushFile(obj, 1); + + yaffs_GrossUnlock(dev); + + return 0; +} + +static int yaffs_readpage_nolock(struct file *f, struct page *pg) +{ + /* Lifted from jffs2 */ + + yaffs_Object *obj; + unsigned char *pg_buf; + int ret; + + yaffs_Device *dev; + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_readpage at %08x, size %08x\n", + (unsigned)(pg->index << PAGE_CACHE_SHIFT), + (unsigned)PAGE_CACHE_SIZE)); + + obj = yaffs_DentryToObject(f->f_dentry); + + dev = obj->myDev; + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + BUG_ON(!PageLocked(pg)); +#else + if (!PageLocked(pg)) + PAGE_BUG(pg); +#endif + + pg_buf = kmap(pg); + /* FIXME: Can kmap fail? */ + + yaffs_GrossLock(dev); + + ret = + yaffs_ReadDataFromFile(obj, pg_buf, pg->index << PAGE_CACHE_SHIFT, + PAGE_CACHE_SIZE); + + yaffs_GrossUnlock(dev); + + if (ret >= 0) + ret = 0; + + if (ret) { + ClearPageUptodate(pg); + SetPageError(pg); + } else { + SetPageUptodate(pg); + ClearPageError(pg); + } + + flush_dcache_page(pg); + kunmap(pg); + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_readpage done\n")); + return ret; +} + +static int yaffs_readpage_unlock(struct file *f, struct page *pg) +{ + int ret = yaffs_readpage_nolock(f, pg); + UnlockPage(pg); + return ret; +} + +static int yaffs_readpage(struct file *f, struct page *pg) +{ + return yaffs_readpage_unlock(f, pg); +} + +/* writepage inspired by/stolen from smbfs */ + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_writepage(struct page *page, struct writeback_control *wbc) +#else +static int yaffs_writepage(struct page *page) +#endif +{ + struct address_space *mapping = page->mapping; + loff_t offset = (loff_t) page->index << PAGE_CACHE_SHIFT; + struct inode *inode; + unsigned long end_index; + char *buffer; + yaffs_Object *obj; + int nWritten = 0; + unsigned nBytes; + + if (!mapping) + BUG(); + inode = mapping->host; + if (!inode) + BUG(); + + if (offset > inode->i_size) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG + "yaffs_writepage at %08x, inode size = %08x!!!\n", + (unsigned)(page->index << PAGE_CACHE_SHIFT), + (unsigned)inode->i_size)); + T(YAFFS_TRACE_OS, + (KERN_DEBUG " -> don't care!!\n")); + unlock_page(page); + return 0; + } + + end_index = inode->i_size >> PAGE_CACHE_SHIFT; + + /* easy case */ + if (page->index < end_index) { + nBytes = PAGE_CACHE_SIZE; + } else { + nBytes = inode->i_size & (PAGE_CACHE_SIZE - 1); + } + + get_page(page); + + buffer = kmap(page); + + obj = yaffs_InodeToObject(inode); + yaffs_GrossLock(obj->myDev); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_writepage at %08x, size %08x\n", + (unsigned)(page->index << PAGE_CACHE_SHIFT), nBytes)); + T(YAFFS_TRACE_OS, + (KERN_DEBUG "writepag0: obj = %05x, ino = %05x\n", + (int)obj->variant.fileVariant.fileSize, (int)inode->i_size)); + + nWritten = + yaffs_WriteDataToFile(obj, buffer, page->index << PAGE_CACHE_SHIFT, + nBytes, 0); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "writepag1: obj = %05x, ino = %05x\n", + (int)obj->variant.fileVariant.fileSize, (int)inode->i_size)); + + yaffs_GrossUnlock(obj->myDev); + + kunmap(page); + SetPageUptodate(page); + UnlockPage(page); + put_page(page); + + return (nWritten == nBytes) ? 0 : -ENOSPC; +} + +static int yaffs_prepare_write(struct file *f, struct page *pg, + unsigned offset, unsigned to) +{ + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_prepair_write\n")); + if (!Page_Uptodate(pg) && (offset || to < PAGE_CACHE_SIZE)) + return yaffs_readpage_nolock(f, pg); + + return 0; + +} + +static int yaffs_commit_write(struct file *f, struct page *pg, unsigned offset, + unsigned to) +{ + + void *addr, *kva; + + loff_t pos = (((loff_t) pg->index) << PAGE_CACHE_SHIFT) + offset; + int nBytes = to - offset; + int nWritten; + + unsigned spos = pos; + unsigned saddr = (unsigned)addr; + + kva=kmap(pg); + addr = kva + offset; + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_commit_write addr %x pos %x nBytes %d\n", saddr, + spos, nBytes)); + + nWritten = yaffs_file_write(f, addr, nBytes, &pos); + + if (nWritten != nBytes) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG + "yaffs_commit_write not same size nWritten %d nBytes %d\n", + nWritten, nBytes)); + SetPageError(pg); + ClearPageUptodate(pg); + } else { + SetPageUptodate(pg); + } + + kunmap(pg); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_commit_write returning %d\n", + nWritten == nBytes ? 0 : nWritten)); + + return nWritten == nBytes ? 0 : nWritten; + +} + +static void yaffs_FillInodeFromObject(struct inode *inode, yaffs_Object * obj) +{ + if (inode && obj) { + + + /* Check mode against the variant type and attempt to repair if broken. */ + __u32 mode = obj->yst_mode; + switch( obj->variantType ){ + case YAFFS_OBJECT_TYPE_FILE : + if( ! S_ISREG(mode) ){ + obj->yst_mode &= ~S_IFMT; + obj->yst_mode |= S_IFREG; + } + + break; + case YAFFS_OBJECT_TYPE_SYMLINK : + if( ! S_ISLNK(mode) ){ + obj->yst_mode &= ~S_IFMT; + obj->yst_mode |= S_IFLNK; + } + + break; + case YAFFS_OBJECT_TYPE_DIRECTORY : + if( ! S_ISDIR(mode) ){ + obj->yst_mode &= ~S_IFMT; + obj->yst_mode |= S_IFDIR; + } + + break; + case YAFFS_OBJECT_TYPE_UNKNOWN : + case YAFFS_OBJECT_TYPE_HARDLINK : + case YAFFS_OBJECT_TYPE_SPECIAL : + default: + /* TODO? */ + break; + } + + inode->i_flags |= S_NOATIME; + + inode->i_ino = obj->objectId; + inode->i_mode = obj->yst_mode; + inode->i_uid = obj->yst_uid; + inode->i_gid = obj->yst_gid; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) + inode->i_blksize = inode->i_sb->s_blocksize; +#endif +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + + inode->i_rdev = old_decode_dev(obj->yst_rdev); + inode->i_atime.tv_sec = (time_t) (obj->yst_atime); + inode->i_atime.tv_nsec = 0; + inode->i_mtime.tv_sec = (time_t) obj->yst_mtime; + inode->i_mtime.tv_nsec = 0; + inode->i_ctime.tv_sec = (time_t) obj->yst_ctime; + inode->i_ctime.tv_nsec = 0; +#else + inode->i_rdev = obj->yst_rdev; + inode->i_atime = obj->yst_atime; + inode->i_mtime = obj->yst_mtime; + inode->i_ctime = obj->yst_ctime; +#endif + inode->i_size = yaffs_GetObjectFileLength(obj); + inode->i_blocks = (inode->i_size + 511) >> 9; + + inode->i_nlink = yaffs_GetObjectLinkCount(obj); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG + "yaffs_FillInode mode %x uid %d gid %d size %d count %d\n", + inode->i_mode, inode->i_uid, inode->i_gid, + (int)inode->i_size, atomic_read(&inode->i_count))); + + switch (obj->yst_mode & S_IFMT) { + default: /* fifo, device or socket */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + init_special_inode(inode, obj->yst_mode, + old_decode_dev(obj->yst_rdev)); +#else + init_special_inode(inode, obj->yst_mode, + (dev_t) (obj->yst_rdev)); +#endif + break; + case S_IFREG: /* file */ + inode->i_op = &yaffs_file_inode_operations; + inode->i_fop = &yaffs_file_operations; + inode->i_mapping->a_ops = + &yaffs_file_address_operations; + break; + case S_IFDIR: /* directory */ + inode->i_op = &yaffs_dir_inode_operations; + inode->i_fop = &yaffs_dir_operations; + break; + case S_IFLNK: /* symlink */ + inode->i_op = &yaffs_symlink_inode_operations; + break; + } + + yaffs_InodeToObjectLV(inode) = obj; + + obj->myInode = inode; + + } else { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_FileInode invalid parameters\n")); + } + +} + +struct inode *yaffs_get_inode(struct super_block *sb, int mode, int dev, + yaffs_Object * obj) +{ + struct inode *inode; + + if (!sb) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_get_inode for NULL super_block!!\n")); + return NULL; + + } + + if (!obj) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_get_inode for NULL object!!\n")); + return NULL; + + } + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_get_inode for object %d\n", obj->objectId)); + + inode = Y_IGET(sb, obj->objectId); + if(IS_ERR(inode)) + return NULL; + + /* NB Side effect: iget calls back to yaffs_read_inode(). */ + /* iget also increments the inode's i_count */ + /* NB You can't be holding grossLock or deadlock will happen! */ + + return inode; +} + +static ssize_t yaffs_file_write(struct file *f, const char *buf, size_t n, + loff_t * pos) +{ + yaffs_Object *obj; + int nWritten, ipos; + struct inode *inode; + yaffs_Device *dev; + + obj = yaffs_DentryToObject(f->f_dentry); + + dev = obj->myDev; + + yaffs_GrossLock(dev); + + inode = f->f_dentry->d_inode; + + if (!S_ISBLK(inode->i_mode) && f->f_flags & O_APPEND) { + ipos = inode->i_size; + } else { + ipos = *pos; + } + + if (!obj) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_file_write: hey obj is null!\n")); + } else { + T(YAFFS_TRACE_OS, + (KERN_DEBUG + "yaffs_file_write about to write writing %d bytes" + "to object %d at %d\n", + n, obj->objectId, ipos)); + } + + nWritten = yaffs_WriteDataToFile(obj, buf, ipos, n, 0); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_file_write writing %d bytes, %d written at %d\n", + n, nWritten, ipos)); + if (nWritten > 0) { + ipos += nWritten; + *pos = ipos; + if (ipos > inode->i_size) { + inode->i_size = ipos; + inode->i_blocks = (ipos + 511) >> 9; + + T(YAFFS_TRACE_OS, + (KERN_DEBUG + "yaffs_file_write size updated to %d bytes, " + "%d blocks\n", + ipos, (int)(inode->i_blocks))); + } + + } + yaffs_GrossUnlock(dev); + return nWritten == 0 ? -ENOSPC : nWritten; +} + +static int yaffs_readdir(struct file *f, void *dirent, filldir_t filldir) +{ + yaffs_Object *obj; + yaffs_Device *dev; + struct inode *inode = f->f_dentry->d_inode; + unsigned long offset, curoffs; + struct ylist_head *i; + yaffs_Object *l; + + char name[YAFFS_MAX_NAME_LENGTH + 1]; + + obj = yaffs_DentryToObject(f->f_dentry); + dev = obj->myDev; + + yaffs_GrossLock(dev); + + offset = f->f_pos; + + T(YAFFS_TRACE_OS, ("yaffs_readdir: starting at %d\n", (int)offset)); + + if (offset == 0) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_readdir: entry . ino %d \n", + (int)inode->i_ino)); + if (filldir(dirent, ".", 1, offset, inode->i_ino, DT_DIR) + < 0) { + goto out; + } + offset++; + f->f_pos++; + } + if (offset == 1) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_readdir: entry .. ino %d \n", + (int)f->f_dentry->d_parent->d_inode->i_ino)); + if (filldir + (dirent, "..", 2, offset, + f->f_dentry->d_parent->d_inode->i_ino, DT_DIR) < 0) { + goto out; + } + offset++; + f->f_pos++; + } + + curoffs = 1; + + /* If the directory has changed since the open or last call to + readdir, rewind to after the 2 canned entries. */ + + if (f->f_version != inode->i_version) { + offset = 2; + f->f_pos = offset; + f->f_version = inode->i_version; + } + + ylist_for_each(i, &obj->variant.directoryVariant.children) { + curoffs++; + if (curoffs >= offset) { + l = ylist_entry(i, yaffs_Object, siblings); + + yaffs_GetObjectName(l, name, + YAFFS_MAX_NAME_LENGTH + 1); + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_readdir: %s inode %d\n", name, + yaffs_GetObjectInode(l))); + + if (filldir(dirent, + name, + strlen(name), + offset, + yaffs_GetObjectInode(l), + yaffs_GetObjectType(l)) + < 0) { + goto up_and_out; + } + + offset++; + f->f_pos++; + } + } + + up_and_out: + out: + + yaffs_GrossUnlock(dev); + + return 0; +} + +/* + * File creation. Allocate an inode, and we're done.. + */ +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, + dev_t rdev) +#else +static int yaffs_mknod(struct inode *dir, struct dentry *dentry, int mode, + int rdev) +#endif +{ + struct inode *inode; + + yaffs_Object *obj = NULL; + yaffs_Device *dev; + + yaffs_Object *parent = yaffs_InodeToObject(dir); + + int error = -ENOSPC; + uid_t uid = current->fsuid; + gid_t gid = (dir->i_mode & S_ISGID) ? dir->i_gid : current->fsgid; + + if((dir->i_mode & S_ISGID) && S_ISDIR(mode)) + mode |= S_ISGID; + + if (parent) { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_mknod: parent object %d type %d\n", + parent->objectId, parent->variantType)); + } else { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_mknod: could not get parent object\n")); + return -EPERM; + } + + T(YAFFS_TRACE_OS, ("yaffs_mknod: making oject for %s, " + "mode %x dev %x\n", + dentry->d_name.name, mode, rdev)); + + dev = parent->myDev; + + yaffs_GrossLock(dev); + + switch (mode & S_IFMT) { + default: + /* Special (socket, fifo, device...) */ + T(YAFFS_TRACE_OS, (KERN_DEBUG + "yaffs_mknod: making special\n")); +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + obj = + yaffs_MknodSpecial(parent, dentry->d_name.name, mode, uid, + gid, old_encode_dev(rdev)); +#else + obj = + yaffs_MknodSpecial(parent, dentry->d_name.name, mode, uid, + gid, rdev); +#endif + break; + case S_IFREG: /* file */ + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_mknod: making file\n")); + obj = + yaffs_MknodFile(parent, dentry->d_name.name, mode, uid, + gid); + break; + case S_IFDIR: /* directory */ + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_mknod: making directory\n")); + obj = + yaffs_MknodDirectory(parent, dentry->d_name.name, mode, + uid, gid); + break; + case S_IFLNK: /* symlink */ + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_mknod: making file\n")); + obj = NULL; /* Do we ever get here? */ + break; + } + + /* Can not call yaffs_get_inode() with gross lock held */ + yaffs_GrossUnlock(dev); + + if (obj) { + inode = yaffs_get_inode(dir->i_sb, mode, rdev, obj); + d_instantiate(dentry, inode); + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_mknod created object %d count = %d\n", + obj->objectId, atomic_read(&inode->i_count))); + error = 0; + } else { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_mknod failed making object\n")); + error = -ENOMEM; + } + + return error; +} + +static int yaffs_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + int retVal; + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_mkdir\n")); + retVal = yaffs_mknod(dir, dentry, mode | S_IFDIR, 0); +#if 0 + /* attempt to fix dir bug - didn't work */ + if (!retVal) { + dget(dentry); + } +#endif + return retVal; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *n) +#else +static int yaffs_create(struct inode *dir, struct dentry *dentry, int mode) +#endif +{ + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_create\n")); + return yaffs_mknod(dir, dentry, mode | S_IFREG, 0); +} + +static int yaffs_unlink(struct inode *dir, struct dentry *dentry) +{ + int retVal; + + yaffs_Device *dev; + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_unlink %d:%s\n", (int)(dir->i_ino), + dentry->d_name.name)); + + dev = yaffs_InodeToObject(dir)->myDev; + + yaffs_GrossLock(dev); + + retVal = yaffs_Unlink(yaffs_InodeToObject(dir), dentry->d_name.name); + + if (retVal == YAFFS_OK) { + dentry->d_inode->i_nlink--; + dir->i_version++; + yaffs_GrossUnlock(dev); + mark_inode_dirty(dentry->d_inode); + return 0; + } + yaffs_GrossUnlock(dev); + return -ENOTEMPTY; +} + +/* + * Create a link... + */ +static int yaffs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + struct inode *inode = old_dentry->d_inode; + yaffs_Object *obj = NULL; + yaffs_Object *link = NULL; + yaffs_Device *dev; + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_link\n")); + + obj = yaffs_InodeToObject(inode); + dev = obj->myDev; + + yaffs_GrossLock(dev); + + if (!S_ISDIR(inode->i_mode)) /* Don't link directories */ + { + link = + yaffs_Link(yaffs_InodeToObject(dir), dentry->d_name.name, + obj); + } + + if (link) { + old_dentry->d_inode->i_nlink = yaffs_GetObjectLinkCount(obj); + d_instantiate(dentry, old_dentry->d_inode); + atomic_inc(&old_dentry->d_inode->i_count); + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_link link count %d i_count %d\n", + old_dentry->d_inode->i_nlink, + atomic_read(&old_dentry->d_inode->i_count))); + + } + + yaffs_GrossUnlock(dev); + + if (link) { + + return 0; + } + + return -EPERM; +} + +static int yaffs_symlink(struct inode *dir, struct dentry *dentry, + const char *symname) +{ + yaffs_Object *obj; + yaffs_Device *dev; + uid_t uid = current->fsuid; + gid_t gid = (dir->i_mode & S_ISGID) ? dir->i_gid : current->fsgid; + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_symlink\n")); + + dev = yaffs_InodeToObject(dir)->myDev; + yaffs_GrossLock(dev); + obj = yaffs_MknodSymLink(yaffs_InodeToObject(dir), dentry->d_name.name, + S_IFLNK | S_IRWXUGO, uid, gid, symname); + yaffs_GrossUnlock(dev); + + if (obj) { + + struct inode *inode; + + inode = yaffs_get_inode(dir->i_sb, obj->yst_mode, 0, obj); + d_instantiate(dentry, inode); + T(YAFFS_TRACE_OS, (KERN_DEBUG "symlink created OK\n")); + return 0; + } else { + T(YAFFS_TRACE_OS, (KERN_DEBUG "symlink not created\n")); + + } + + return -ENOMEM; +} + +static int yaffs_sync_object(struct file *file, struct dentry *dentry, + int datasync) +{ + + yaffs_Object *obj; + yaffs_Device *dev; + + obj = yaffs_DentryToObject(dentry); + + dev = obj->myDev; + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_sync_object\n")); + yaffs_GrossLock(dev); + yaffs_FlushFile(obj, 1); + yaffs_GrossUnlock(dev); + return 0; +} + +/* + * The VFS layer already does all the dentry stuff for rename. + * + * NB: POSIX says you can rename an object over an old object of the same name + */ +static int yaffs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + yaffs_Device *dev; + int retVal = YAFFS_FAIL; + yaffs_Object *target; + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_rename\n")); + dev = yaffs_InodeToObject(old_dir)->myDev; + + yaffs_GrossLock(dev); + + /* Check if the target is an existing directory that is not empty. */ + target = + yaffs_FindObjectByName(yaffs_InodeToObject(new_dir), + new_dentry->d_name.name); + + + + if (target && + target->variantType == YAFFS_OBJECT_TYPE_DIRECTORY && + !ylist_empty(&target->variant.directoryVariant.children)) { + + T(YAFFS_TRACE_OS, (KERN_DEBUG "target is non-empty dir\n")); + + retVal = YAFFS_FAIL; + } else { + + /* Now does unlinking internally using shadowing mechanism */ + T(YAFFS_TRACE_OS, (KERN_DEBUG "calling yaffs_RenameObject\n")); + + retVal = + yaffs_RenameObject(yaffs_InodeToObject(old_dir), + old_dentry->d_name.name, + yaffs_InodeToObject(new_dir), + new_dentry->d_name.name); + + } + yaffs_GrossUnlock(dev); + + if (retVal == YAFFS_OK) { + if(target) { + new_dentry->d_inode->i_nlink--; + mark_inode_dirty(new_dentry->d_inode); + } + + return 0; + } else { + return -ENOTEMPTY; + } + +} + +static int yaffs_setattr(struct dentry *dentry, struct iattr *attr) +{ + struct inode *inode = dentry->d_inode; + int error; + yaffs_Device *dev; + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_setattr of object %d\n", + yaffs_InodeToObject(inode)->objectId)); + + if ((error = inode_change_ok(inode, attr)) == 0) { + + dev = yaffs_InodeToObject(inode)->myDev; + yaffs_GrossLock(dev); + if (yaffs_SetAttributes(yaffs_InodeToObject(inode), attr) == + YAFFS_OK) { + error = 0; + } else { + error = -EPERM; + } + yaffs_GrossUnlock(dev); + if (!error) + error = inode_setattr(inode, attr); + } + return error; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + yaffs_Device *dev = yaffs_DentryToObject(dentry)->myDev; + struct super_block *sb = dentry->d_sb; +#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_statfs(struct super_block *sb, struct kstatfs *buf) +{ + yaffs_Device *dev = yaffs_SuperToDevice(sb); +#else +static int yaffs_statfs(struct super_block *sb, struct statfs *buf) +{ + yaffs_Device *dev = yaffs_SuperToDevice(sb); +#endif + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_statfs\n")); + + yaffs_GrossLock(dev); + + buf->f_type = YAFFS_MAGIC; + buf->f_bsize = sb->s_blocksize; + buf->f_namelen = 255; + + if(dev->nDataBytesPerChunk & (dev->nDataBytesPerChunk - 1)){ + /* Do this if chunk size is not a power of 2 */ + + uint64_t bytesInDev; + uint64_t bytesFree; + + bytesInDev = ((uint64_t)((dev->endBlock - dev->startBlock +1))) * + ((uint64_t)(dev->nChunksPerBlock * dev->nDataBytesPerChunk)); + + do_div(bytesInDev,sb->s_blocksize); /* bytesInDev becomes the number of blocks */ + buf->f_blocks = bytesInDev; + + bytesFree = ((uint64_t)(yaffs_GetNumberOfFreeChunks(dev))) * + ((uint64_t)(dev->nDataBytesPerChunk)); + + do_div(bytesFree,sb->s_blocksize); + + buf->f_bfree = bytesFree; + + } else if (sb->s_blocksize > dev->nDataBytesPerChunk) { + + buf->f_blocks = + (dev->endBlock - dev->startBlock + 1) * + dev->nChunksPerBlock / + (sb->s_blocksize / dev->nDataBytesPerChunk); + buf->f_bfree = + yaffs_GetNumberOfFreeChunks(dev) / + (sb->s_blocksize / dev->nDataBytesPerChunk); + } else { + buf->f_blocks = + (dev->endBlock - dev->startBlock + 1) * + dev->nChunksPerBlock * + (dev->nDataBytesPerChunk / sb->s_blocksize); + + buf->f_bfree = + yaffs_GetNumberOfFreeChunks(dev) * + (dev->nDataBytesPerChunk / sb->s_blocksize); + } + + + buf->f_files = 0; + buf->f_ffree = 0; + buf->f_bavail = buf->f_bfree; + + yaffs_GrossUnlock(dev); + return 0; +} + + +static int yaffs_do_sync_fs(struct super_block *sb) +{ + + yaffs_Device *dev = yaffs_SuperToDevice(sb); + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_do_sync_fs\n")); + + if(sb->s_dirt) { + yaffs_GrossLock(dev); + + if(dev){ + yaffs_FlushEntireDeviceCache(dev); + yaffs_CheckpointSave(dev); + } + + yaffs_GrossUnlock(dev); + + sb->s_dirt = 0; + } + return 0; +} + + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static void yaffs_write_super(struct super_block *sb) +#else +static int yaffs_write_super(struct super_block *sb) +#endif +{ + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_write_super\n")); + yaffs_do_sync_fs(sb); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + return 0; +#endif +} + + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_sync_fs(struct super_block *sb, int wait) +#else +static int yaffs_sync_fs(struct super_block *sb) +#endif +{ + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_sync_fs\n")); + + yaffs_do_sync_fs(sb); + + return 0; + +} + +#ifdef YAFFS_USE_OWN_IGET + +static struct inode * yaffs_iget(struct super_block *sb, unsigned long ino) +{ + struct inode *inode; + yaffs_Object *obj; + yaffs_Device *dev = yaffs_SuperToDevice(sb); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_iget for %lu\n", ino)); + + inode = iget_locked(sb, ino); + if (!inode) + return ERR_PTR(-ENOMEM); + if (!(inode->i_state & I_NEW)) + return inode; + + /* NB This is called as a side effect of other functions, but + * we had to release the lock to prevent deadlocks, so + * need to lock again. + */ + + yaffs_GrossLock(dev); + + obj = yaffs_FindObjectByNumber(dev, inode->i_ino); + + yaffs_FillInodeFromObject(inode, obj); + + yaffs_GrossUnlock(dev); + + unlock_new_inode(inode); + return inode; +} + +#else + +static void yaffs_read_inode(struct inode *inode) +{ + /* NB This is called as a side effect of other functions, but + * we had to release the lock to prevent deadlocks, so + * need to lock again. + */ + + yaffs_Object *obj; + yaffs_Device *dev = yaffs_SuperToDevice(inode->i_sb); + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_read_inode for %d\n", (int)inode->i_ino)); + + yaffs_GrossLock(dev); + + obj = yaffs_FindObjectByNumber(dev, inode->i_ino); + + yaffs_FillInodeFromObject(inode, obj); + + yaffs_GrossUnlock(dev); +} + +#endif + +static YLIST_HEAD(yaffs_dev_list); + +#if 0 // not used +static int yaffs_remount_fs(struct super_block *sb, int *flags, char *data) +{ + yaffs_Device *dev = yaffs_SuperToDevice(sb); + + if( *flags & MS_RDONLY ) { + struct mtd_info *mtd = yaffs_SuperToDevice(sb)->genericDevice; + + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_remount_fs: %s: RO\n", dev->name )); + + yaffs_GrossLock(dev); + + yaffs_FlushEntireDeviceCache(dev); + + yaffs_CheckpointSave(dev); + + if (mtd->sync) + mtd->sync(mtd); + + yaffs_GrossUnlock(dev); + } + else { + T(YAFFS_TRACE_OS, + (KERN_DEBUG "yaffs_remount_fs: %s: RW\n", dev->name )); + } + + return 0; +} +#endif + +static void yaffs_put_super(struct super_block *sb) +{ + yaffs_Device *dev = yaffs_SuperToDevice(sb); + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_put_super\n")); + + yaffs_GrossLock(dev); + + yaffs_FlushEntireDeviceCache(dev); + + yaffs_CheckpointSave(dev); + + if (dev->putSuperFunc) { + dev->putSuperFunc(sb); + } + + yaffs_Deinitialise(dev); + + yaffs_GrossUnlock(dev); + + /* we assume this is protected by lock_kernel() in mount/umount */ + ylist_del(&dev->devList); + + if(dev->spareBuffer){ + YFREE(dev->spareBuffer); + dev->spareBuffer = NULL; + } + + kfree(dev); +} + + +static void yaffs_MTDPutSuper(struct super_block *sb) +{ + + struct mtd_info *mtd = yaffs_SuperToDevice(sb)->genericDevice; + + if (mtd->sync) { + mtd->sync(mtd); + } + + put_mtd_device(mtd); +} + + +static void yaffs_MarkSuperBlockDirty(void *vsb) +{ + struct super_block *sb = (struct super_block *)vsb; + + T(YAFFS_TRACE_OS, (KERN_DEBUG "yaffs_MarkSuperBlockDirty() sb = %p\n",sb)); +// if(sb) +// sb->s_dirt = 1; +} + +typedef struct { + int inband_tags; + int skip_checkpoint_read; + int skip_checkpoint_write; + int no_cache; +} yaffs_options; + +#define MAX_OPT_LEN 20 +static int yaffs_parse_options(yaffs_options *options, const char *options_str) +{ + char cur_opt[MAX_OPT_LEN+1]; + int p; + int error = 0; + + /* Parse through the options which is a comma seperated list */ + + while(options_str && *options_str && !error){ + memset(cur_opt,0,MAX_OPT_LEN+1); + p = 0; + + while(*options_str && *options_str != ','){ + if(p < MAX_OPT_LEN){ + cur_opt[p] = *options_str; + p++; + } + options_str++; + } + + if(!strcmp(cur_opt,"inband-tags")) + options->inband_tags = 1; + else if(!strcmp(cur_opt,"no-cache")) + options->no_cache = 1; + else if(!strcmp(cur_opt,"no-checkpoint-read")) + options->skip_checkpoint_read = 1; + else if(!strcmp(cur_opt,"no-checkpoint-write")) + options->skip_checkpoint_write = 1; + else if(!strcmp(cur_opt,"no-checkpoint")){ + options->skip_checkpoint_read = 1; + options->skip_checkpoint_write = 1; + } else { + printk(KERN_INFO "yaffs: Bad mount option \"%s\"\n",cur_opt); + error = 1; + } + + } + + return error; +} + +static struct super_block *yaffs_internal_read_super(int yaffsVersion, + struct super_block *sb, + void *data, int silent) +{ + int nBlocks; + struct inode *inode = NULL; + struct dentry *root; + yaffs_Device *dev = 0; + char devname_buf[BDEVNAME_SIZE + 1]; + struct mtd_info *mtd; + int err; + char *data_str = (char *)data; + + yaffs_options options; + + sb->s_magic = YAFFS_MAGIC; + sb->s_op = &yaffs_super_ops; + sb->s_flags |= MS_NOATIME; + + if (!sb) + printk(KERN_INFO "yaffs: sb is NULL\n"); + else if (!sb->s_dev) + printk(KERN_INFO "yaffs: sb->s_dev is NULL\n"); + else if (!yaffs_devname(sb, devname_buf)) + printk(KERN_INFO "yaffs: devname is NULL\n"); + else + printk(KERN_INFO "yaffs: dev is %d name is \"%s\"\n", + sb->s_dev, + yaffs_devname(sb, devname_buf)); + + if(!data_str) + data_str = ""; + + printk(KERN_INFO "yaffs: passed flags \"%s\"\n",data_str); + + memset(&options,0,sizeof(options)); + + if(yaffs_parse_options(&options,data_str)){ + /* Option parsing failed */ + return NULL; + } + + + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + T(YAFFS_TRACE_OS, ("yaffs_read_super: Using yaffs%d\n", yaffsVersion)); + T(YAFFS_TRACE_OS, + ("yaffs_read_super: block size %d\n", (int)(sb->s_blocksize))); + +#ifdef CONFIG_YAFFS_DISABLE_WRITE_VERIFY + T(YAFFS_TRACE_OS, + ("yaffs: Write verification disabled. All guarantees " + "null and void\n")); +#endif + + T(YAFFS_TRACE_ALWAYS, ("yaffs: Attempting MTD mount on %u.%u, " + "\"%s\"\n", + MAJOR(sb->s_dev), MINOR(sb->s_dev), + yaffs_devname(sb, devname_buf))); + + /* Check it's an mtd device..... */ + if (MAJOR(sb->s_dev) != MTD_BLOCK_MAJOR) { + return NULL; /* This isn't an mtd device */ + } + /* Get the device */ + mtd = get_mtd_device(NULL, MINOR(sb->s_dev)); + if (!mtd) { + T(YAFFS_TRACE_ALWAYS, + ("yaffs: MTD device #%u doesn't appear to exist\n", + MINOR(sb->s_dev))); + return NULL; + } + /* Check it's NAND */ + if (mtd->type != MTD_NANDFLASH) { + T(YAFFS_TRACE_ALWAYS, + ("yaffs: MTD device is not NAND it's type %d\n", mtd->type)); + return NULL; + } + + T(YAFFS_TRACE_OS, (" erase %p\n", mtd->erase)); + T(YAFFS_TRACE_OS, (" read %p\n", mtd->read)); + T(YAFFS_TRACE_OS, (" write %p\n", mtd->write)); + T(YAFFS_TRACE_OS, (" readoob %p\n", mtd->read_oob)); + T(YAFFS_TRACE_OS, (" writeoob %p\n", mtd->write_oob)); + T(YAFFS_TRACE_OS, (" block_isbad %p\n", mtd->block_isbad)); + T(YAFFS_TRACE_OS, (" block_markbad %p\n", mtd->block_markbad)); + T(YAFFS_TRACE_OS, (" %s %d\n", WRITE_SIZE_STR, WRITE_SIZE(mtd))); + T(YAFFS_TRACE_OS, (" oobsize %d\n", mtd->oobsize)); + T(YAFFS_TRACE_OS, (" erasesize %d\n", mtd->erasesize)); + T(YAFFS_TRACE_OS, (" size %d\n", mtd->size)); + +#ifdef CONFIG_YAFFS_AUTO_YAFFS2 + + if (yaffsVersion == 1 && + WRITE_SIZE(mtd) >= 2048) { + T(YAFFS_TRACE_ALWAYS,("yaffs: auto selecting yaffs2\n")); + yaffsVersion = 2; + } + + /* Added NCB 26/5/2006 for completeness */ + if (yaffsVersion == 2 && + !options.inband_tags && + WRITE_SIZE(mtd) == 512){ + T(YAFFS_TRACE_ALWAYS,("yaffs: auto selecting yaffs1\n")); + yaffsVersion = 1; + } + +#endif + + if (yaffsVersion == 2) { + /* Check for version 2 style functions */ + if (!mtd->erase || + !mtd->block_isbad || + !mtd->block_markbad || + !mtd->read || + !mtd->write || +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) + !mtd->read_oob || !mtd->write_oob) { +#else + !mtd->write_ecc || + !mtd->read_ecc || !mtd->read_oob || !mtd->write_oob) { +#endif + T(YAFFS_TRACE_ALWAYS, + ("yaffs: MTD device does not support required " + "functions\n"));; + return NULL; + } + + if ((WRITE_SIZE(mtd) < YAFFS_MIN_YAFFS2_CHUNK_SIZE || + mtd->oobsize < YAFFS_MIN_YAFFS2_SPARE_SIZE) && + !options.inband_tags) { + T(YAFFS_TRACE_ALWAYS, + ("yaffs: MTD device does not have the " + "right page sizes\n")); + return NULL; + } + } else { + /* Check for V1 style functions */ + if (!mtd->erase || + !mtd->read || + !mtd->write || +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) + !mtd->read_oob || !mtd->write_oob) { +#else + !mtd->write_ecc || + !mtd->read_ecc || !mtd->read_oob || !mtd->write_oob) { +#endif + T(YAFFS_TRACE_ALWAYS, + ("yaffs: MTD device does not support required " + "functions\n"));; + return NULL; + } + + if (WRITE_SIZE(mtd) < YAFFS_BYTES_PER_CHUNK || + mtd->oobsize != YAFFS_BYTES_PER_SPARE) { + T(YAFFS_TRACE_ALWAYS, + ("yaffs: MTD device does not support have the " + "right page sizes\n")); + return NULL; + } + } + + /* OK, so if we got here, we have an MTD that's NAND and looks + * like it has the right capabilities + * Set the yaffs_Device up for mtd + */ + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) + sb->s_fs_info = dev = kmalloc(sizeof(yaffs_Device), GFP_KERNEL); +#else + sb->u.generic_sbp = dev = kmalloc(sizeof(yaffs_Device), GFP_KERNEL); +#endif + if (!dev) { + /* Deep shit could not allocate device structure */ + T(YAFFS_TRACE_ALWAYS, + ("yaffs_read_super: Failed trying to allocate " + "yaffs_Device. \n")); + return NULL; + } + + memset(dev, 0, sizeof(yaffs_Device)); + dev->genericDevice = mtd; + dev->name = mtd->name; + + /* Set up the memory size parameters.... */ + + nBlocks = mtd->size / (YAFFS_CHUNKS_PER_BLOCK * YAFFS_BYTES_PER_CHUNK); + dev->startBlock = 0; + dev->endBlock = nBlocks - 1; + dev->nChunksPerBlock = YAFFS_CHUNKS_PER_BLOCK; + dev->totalBytesPerChunk = YAFFS_BYTES_PER_CHUNK; + dev->nReservedBlocks = 5; + dev->nShortOpCaches = (options.no_cache) ? 0 : 10; + dev->inbandTags = options.inband_tags; + + /* ... and the functions. */ + if (yaffsVersion == 2) { + dev->writeChunkWithTagsToNAND = + nandmtd2_WriteChunkWithTagsToNAND; + dev->readChunkWithTagsFromNAND = + nandmtd2_ReadChunkWithTagsFromNAND; + dev->markNANDBlockBad = nandmtd2_MarkNANDBlockBad; + dev->queryNANDBlock = nandmtd2_QueryNANDBlock; + dev->spareBuffer = YMALLOC(mtd->oobsize); + dev->isYaffs2 = 1; +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) + dev->totalBytesPerChunk = mtd->writesize; + dev->nChunksPerBlock = mtd->erasesize / mtd->writesize; +#else + dev->totalBytesPerChunk = mtd->oobblock; + dev->nChunksPerBlock = mtd->erasesize / mtd->oobblock; +#endif + nBlocks = mtd->size / mtd->erasesize; + + dev->startBlock = 0; + dev->endBlock = nBlocks - 1; + } else { +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) + /* use the MTD interface in yaffs_mtdif1.c */ + dev->writeChunkWithTagsToNAND = + nandmtd1_WriteChunkWithTagsToNAND; + dev->readChunkWithTagsFromNAND = + nandmtd1_ReadChunkWithTagsFromNAND; + dev->markNANDBlockBad = nandmtd1_MarkNANDBlockBad; + dev->queryNANDBlock = nandmtd1_QueryNANDBlock; +#else + dev->writeChunkToNAND = nandmtd_WriteChunkToNAND; + dev->readChunkFromNAND = nandmtd_ReadChunkFromNAND; +#endif + dev->isYaffs2 = 0; + } + /* ... and common functions */ + dev->eraseBlockInNAND = nandmtd_EraseBlockInNAND; + dev->initialiseNAND = nandmtd_InitialiseNAND; + + dev->putSuperFunc = yaffs_MTDPutSuper; + + dev->superBlock = (void *)sb; + dev->markSuperBlockDirty = yaffs_MarkSuperBlockDirty; + + +#ifndef CONFIG_YAFFS_DOES_ECC + dev->useNANDECC = 1; +#endif + +#ifdef CONFIG_YAFFS_DISABLE_WIDE_TNODES + dev->wideTnodesDisabled = 1; +#endif + + dev->skipCheckpointRead = options.skip_checkpoint_read; + dev->skipCheckpointWrite = options.skip_checkpoint_write; + + /* we assume this is protected by lock_kernel() in mount/umount */ + ylist_add_tail(&dev->devList, &yaffs_dev_list); + + init_MUTEX(&dev->grossLock); + + yaffs_GrossLock(dev); + + err = yaffs_GutsInitialise(dev); + + T(YAFFS_TRACE_OS, + ("yaffs_read_super: guts initialised %s\n", + (err == YAFFS_OK) ? "OK" : "FAILED")); + + /* Release lock before yaffs_get_inode() */ + yaffs_GrossUnlock(dev); + + /* Create root inode */ + if (err == YAFFS_OK) + inode = yaffs_get_inode(sb, S_IFDIR | 0755, 0, + yaffs_Root(dev)); + + if (!inode) + return NULL; + + inode->i_op = &yaffs_dir_inode_operations; + inode->i_fop = &yaffs_dir_operations; + + T(YAFFS_TRACE_OS, ("yaffs_read_super: got root inode\n")); + + root = d_alloc_root(inode); + + T(YAFFS_TRACE_OS, ("yaffs_read_super: d_alloc_root done\n")); + + if (!root) { + iput(inode); + return NULL; + } + sb->s_root = root; + + T(YAFFS_TRACE_OS, ("yaffs_read_super: done\n")); + return sb; +} + + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs_internal_read_super_mtd(struct super_block *sb, void *data, + int silent) +{ + return yaffs_internal_read_super(1, sb, data, silent) ? 0 : -EINVAL; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs_read_super(struct file_system_type *fs, + int flags, const char *dev_name, + void *data, struct vfsmount *mnt) +{ + + return get_sb_bdev(fs, flags, dev_name, data, + yaffs_internal_read_super_mtd, mnt); +} +#else +static struct super_block *yaffs_read_super(struct file_system_type *fs, + int flags, const char *dev_name, + void *data) +{ + + return get_sb_bdev(fs, flags, dev_name, data, + yaffs_internal_read_super_mtd); +} +#endif + +static struct file_system_type yaffs_fs_type = { + .owner = THIS_MODULE, + .name = "yaffs", + .get_sb = yaffs_read_super, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, +}; +#else +static struct super_block *yaffs_read_super(struct super_block *sb, void *data, + int silent) +{ + return yaffs_internal_read_super(1, sb, data, silent); +} + +static DECLARE_FSTYPE(yaffs_fs_type, "yaffs", yaffs_read_super, + FS_REQUIRES_DEV); +#endif + + +#ifdef CONFIG_YAFFS_YAFFS2 + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int yaffs2_internal_read_super_mtd(struct super_block *sb, void *data, + int silent) +{ + return yaffs_internal_read_super(2, sb, data, silent) ? 0 : -EINVAL; +} + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) +static int yaffs2_read_super(struct file_system_type *fs, + int flags, const char *dev_name, void *data, + struct vfsmount *mnt) +{ + return get_sb_bdev(fs, flags, dev_name, data, + yaffs2_internal_read_super_mtd, mnt); +} +#else +static struct super_block *yaffs2_read_super(struct file_system_type *fs, + int flags, const char *dev_name, + void *data) +{ + + return get_sb_bdev(fs, flags, dev_name, data, + yaffs2_internal_read_super_mtd); +} +#endif + +static struct file_system_type yaffs2_fs_type = { + .owner = THIS_MODULE, + .name = "yaffs2", + .get_sb = yaffs2_read_super, + .kill_sb = kill_block_super, + .fs_flags = FS_REQUIRES_DEV, +}; +#else +static struct super_block *yaffs2_read_super(struct super_block *sb, + void *data, int silent) +{ + return yaffs_internal_read_super(2, sb, data, silent); +} + +static DECLARE_FSTYPE(yaffs2_fs_type, "yaffs2", yaffs2_read_super, + FS_REQUIRES_DEV); +#endif + +#endif /* CONFIG_YAFFS_YAFFS2 */ + +static struct proc_dir_entry *my_proc_entry; + +static char *yaffs_dump_dev(char *buf, yaffs_Device * dev) +{ + buf += sprintf(buf, "startBlock......... %d\n", dev->startBlock); + buf += sprintf(buf, "endBlock........... %d\n", dev->endBlock); + buf += sprintf(buf, "totalBytesPerChunk. %d\n", dev->totalBytesPerChunk); + buf += sprintf(buf, "nDataBytesPerChunk. %d\n", dev->nDataBytesPerChunk); + buf += sprintf(buf, "chunkGroupBits..... %d\n", dev->chunkGroupBits); + buf += sprintf(buf, "chunkGroupSize..... %d\n", dev->chunkGroupSize); + buf += sprintf(buf, "nErasedBlocks...... %d\n", dev->nErasedBlocks); + buf += sprintf(buf, "nReservedBlocks.... %d\n", dev->nReservedBlocks); + buf += sprintf(buf, "blocksInCheckpoint. %d\n", dev->blocksInCheckpoint); + buf += sprintf(buf, "nTnodesCreated..... %d\n", dev->nTnodesCreated); + buf += sprintf(buf, "nFreeTnodes........ %d\n", dev->nFreeTnodes); + buf += sprintf(buf, "nObjectsCreated.... %d\n", dev->nObjectsCreated); + buf += sprintf(buf, "nFreeObjects....... %d\n", dev->nFreeObjects); + buf += sprintf(buf, "nFreeChunks........ %d\n", dev->nFreeChunks); + buf += sprintf(buf, "nPageWrites........ %d\n", dev->nPageWrites); + buf += sprintf(buf, "nPageReads......... %d\n", dev->nPageReads); + buf += sprintf(buf, "nBlockErasures..... %d\n", dev->nBlockErasures); + buf += sprintf(buf, "nGCCopies.......... %d\n", dev->nGCCopies); + buf += sprintf(buf, "garbageCollections. %d\n", dev->garbageCollections); + buf += sprintf(buf, "passiveGCs......... %d\n", + dev->passiveGarbageCollections); + buf += sprintf(buf, "nRetriedWrites..... %d\n", dev->nRetriedWrites); + buf += sprintf(buf, "nShortOpCaches..... %d\n", dev->nShortOpCaches); + buf += sprintf(buf, "nRetireBlocks...... %d\n", dev->nRetiredBlocks); + buf += sprintf(buf, "eccFixed........... %d\n", dev->eccFixed); + buf += sprintf(buf, "eccUnfixed......... %d\n", dev->eccUnfixed); + buf += sprintf(buf, "tagsEccFixed....... %d\n", dev->tagsEccFixed); + buf += sprintf(buf, "tagsEccUnfixed..... %d\n", dev->tagsEccUnfixed); + buf += sprintf(buf, "cacheHits.......... %d\n", dev->cacheHits); + buf += sprintf(buf, "nDeletedFiles...... %d\n", dev->nDeletedFiles); + buf += sprintf(buf, "nUnlinkedFiles..... %d\n", dev->nUnlinkedFiles); + buf += + sprintf(buf, "nBackgroudDeletions %d\n", dev->nBackgroundDeletions); + buf += sprintf(buf, "useNANDECC......... %d\n", dev->useNANDECC); + buf += sprintf(buf, "isYaffs2........... %d\n", dev->isYaffs2); + buf += sprintf(buf, "inbandTags......... %d\n", dev->inbandTags); + + return buf; +} + +static int yaffs_proc_read(char *page, + char **start, + off_t offset, int count, int *eof, void *data) +{ + struct ylist_head *item; + char *buf = page; + int step = offset; + int n = 0; + + /* Get proc_file_read() to step 'offset' by one on each sucessive call. + * We use 'offset' (*ppos) to indicate where we are in devList. + * This also assumes the user has posted a read buffer large + * enough to hold the complete output; but that's life in /proc. + */ + + *(int *)start = 1; + + /* Print header first */ + if (step == 0) { + buf += sprintf(buf, "YAFFS built:" __DATE__ " " __TIME__ + "\n%s\n%s\n", yaffs_fs_c_version, + yaffs_guts_c_version); + } + + /* hold lock_kernel while traversing yaffs_dev_list */ + lock_kernel(); + + /* Locate and print the Nth entry. Order N-squared but N is small. */ + ylist_for_each(item, &yaffs_dev_list) { + yaffs_Device *dev = ylist_entry(item, yaffs_Device, devList); + if (n < step) { + n++; + continue; + } + buf += sprintf(buf, "\nDevice %d \"%s\"\n", n, dev->name); + buf = yaffs_dump_dev(buf, dev); + break; + } + unlock_kernel(); + + return buf - page < count ? buf - page : count; +} + +/** + * Set the verbosity of the warnings and error messages. + * + * Note that the names can only be a..z or _ with the current code. + */ + +static struct { + char *mask_name; + unsigned mask_bitfield; +} mask_flags[] = { + {"allocate", YAFFS_TRACE_ALLOCATE}, + {"always", YAFFS_TRACE_ALWAYS}, + {"bad_blocks", YAFFS_TRACE_BAD_BLOCKS}, + {"buffers", YAFFS_TRACE_BUFFERS}, + {"bug", YAFFS_TRACE_BUG}, + {"checkpt", YAFFS_TRACE_CHECKPOINT}, + {"deletion", YAFFS_TRACE_DELETION}, + {"erase", YAFFS_TRACE_ERASE}, + {"error", YAFFS_TRACE_ERROR}, + {"gc_detail", YAFFS_TRACE_GC_DETAIL}, + {"gc", YAFFS_TRACE_GC}, + {"mtd", YAFFS_TRACE_MTD}, + {"nandaccess", YAFFS_TRACE_NANDACCESS}, + {"os", YAFFS_TRACE_OS}, + {"scan_debug", YAFFS_TRACE_SCAN_DEBUG}, + {"scan", YAFFS_TRACE_SCAN}, + {"tracing", YAFFS_TRACE_TRACING}, + + {"verify", YAFFS_TRACE_VERIFY}, + {"verify_nand", YAFFS_TRACE_VERIFY_NAND}, + {"verify_full", YAFFS_TRACE_VERIFY_FULL}, + {"verify_all", YAFFS_TRACE_VERIFY_ALL}, + + {"write", YAFFS_TRACE_WRITE}, + {"all", 0xffffffff}, + {"none", 0}, + {NULL, 0}, +}; + +#define MAX_MASK_NAME_LENGTH 40 +static int yaffs_proc_write(struct file *file, const char *buf, + unsigned long count, void *data) +{ + unsigned rg = 0, mask_bitfield; + char *end; + char *mask_name; + const char *x; + char substring[MAX_MASK_NAME_LENGTH+1]; + int i; + int done = 0; + int add, len = 0; + int pos = 0; + + rg = yaffs_traceMask; + + while (!done && (pos < count)) { + done = 1; + while ((pos < count) && isspace(buf[pos])) { + pos++; + } + + switch (buf[pos]) { + case '+': + case '-': + case '=': + add = buf[pos]; + pos++; + break; + + default: + add = ' '; + break; + } + mask_name = NULL; + + mask_bitfield = simple_strtoul(buf + pos, &end, 0); + if (end > buf + pos) { + mask_name = "numeral"; + len = end - (buf + pos); + pos += len; + done = 0; + } else { + for(x = buf + pos, i = 0; + (*x == '_' || (*x >='a' && *x <= 'z')) && + i write_proc = yaffs_proc_write; + my_proc_entry->read_proc = yaffs_proc_read; + my_proc_entry->data = NULL; + } else { + return -ENOMEM; + } + + /* Now add the file system entries */ + + fsinst = fs_to_install; + + while (fsinst->fst && !error) { + error = register_filesystem(fsinst->fst); + if (!error) { + fsinst->installed = 1; + } + fsinst++; + } + + /* Any errors? uninstall */ + if (error) { + fsinst = fs_to_install; + + while (fsinst->fst) { + if (fsinst->installed) { + unregister_filesystem(fsinst->fst); + fsinst->installed = 0; + } + fsinst++; + } + } + + return error; +} + +static void __exit exit_yaffs_fs(void) +{ + + struct file_system_to_install *fsinst; + + T(YAFFS_TRACE_ALWAYS, ("yaffs " __DATE__ " " __TIME__ + " removing. \n")); + + remove_proc_entry("yaffs", YPROC_ROOT); + + fsinst = fs_to_install; + + while (fsinst->fst) { + if (fsinst->installed) { + unregister_filesystem(fsinst->fst); + fsinst->installed = 0; + } + fsinst++; + } + +} + +module_init(init_yaffs_fs) +module_exit(exit_yaffs_fs) + +MODULE_DESCRIPTION("YAFFS2 - a NAND specific flash file system"); +MODULE_AUTHOR("Charles Manning, Aleph One Ltd., 2002-2006"); +MODULE_LICENSE("GPL"); diff --git a/fs/yaffs2/yaffs_getblockinfo.h b/fs/yaffs2/yaffs_getblockinfo.h new file mode 100644 index 0000000..b9742ac --- /dev/null +++ b/fs/yaffs2/yaffs_getblockinfo.h @@ -0,0 +1,34 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_GETBLOCKINFO_H__ +#define __YAFFS_GETBLOCKINFO_H__ + +#include "yaffs_guts.h" + +/* Function to manipulate block info */ +static Y_INLINE yaffs_BlockInfo *yaffs_GetBlockInfo(yaffs_Device * dev, int blk) +{ + if (blk < dev->internalStartBlock || blk > dev->internalEndBlock) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>> yaffs: getBlockInfo block %d is not valid" TENDSTR), + blk)); + YBUG(); + } + return &dev->blockInfo[blk - dev->internalStartBlock]; +} + +#endif diff --git a/fs/yaffs2/yaffs_guts.c b/fs/yaffs2/yaffs_guts.c new file mode 100644 index 0000000..8ea033a --- /dev/null +++ b/fs/yaffs2/yaffs_guts.c @@ -0,0 +1,7676 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +const char *yaffs_guts_c_version = + "$Id: yaffs_guts.c,v 1.71 2009/01/12 00:53:47 charles Exp $"; + +#include "yportenv.h" + +#include "yaffsinterface.h" +#include "yaffs_guts.h" +#include "yaffs_tagsvalidity.h" +#include "yaffs_getblockinfo.h" + +#include "yaffs_tagscompat.h" +#ifndef CONFIG_YAFFS_USE_OWN_SORT +#include "yaffs_qsort.h" +#endif +#include "yaffs_nand.h" + +#include "yaffs_checkptrw.h" + +#include "yaffs_nand.h" +#include "yaffs_packedtags2.h" + + +#ifdef CONFIG_YAFFS_WINCE +void yfsd_LockYAFFS(BOOL fsLockOnly); +void yfsd_UnlockYAFFS(BOOL fsLockOnly); +#endif + +#define YAFFS_PASSIVE_GC_CHUNKS 2 + +#include "yaffs_ecc.h" + + +/* Robustification (if it ever comes about...) */ +static void yaffs_RetireBlock(yaffs_Device * dev, int blockInNAND); +static void yaffs_HandleWriteChunkError(yaffs_Device * dev, int chunkInNAND, int erasedOk); +static void yaffs_HandleWriteChunkOk(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, + const yaffs_ExtendedTags * tags); +static void yaffs_HandleUpdateChunk(yaffs_Device * dev, int chunkInNAND, + const yaffs_ExtendedTags * tags); + +/* Other local prototypes */ +static int yaffs_UnlinkObject( yaffs_Object *obj); +static int yaffs_ObjectHasCachedWriteData(yaffs_Object *obj); + +static void yaffs_HardlinkFixup(yaffs_Device *dev, yaffs_Object *hardList); + +static int yaffs_WriteNewChunkWithTagsToNAND(yaffs_Device * dev, + const __u8 * buffer, + yaffs_ExtendedTags * tags, + int useReserve); +static int yaffs_PutChunkIntoFile(yaffs_Object * in, int chunkInInode, + int chunkInNAND, int inScan); + +static yaffs_Object *yaffs_CreateNewObject(yaffs_Device * dev, int number, + yaffs_ObjectType type); +static void yaffs_AddObjectToDirectory(yaffs_Object * directory, + yaffs_Object * obj); +static int yaffs_UpdateObjectHeader(yaffs_Object * in, const YCHAR * name, + int force, int isShrink, int shadows); +static void yaffs_RemoveObjectFromDirectory(yaffs_Object * obj); +static int yaffs_CheckStructures(void); +static int yaffs_DeleteWorker(yaffs_Object * in, yaffs_Tnode * tn, __u32 level, + int chunkOffset, int *limit); +static int yaffs_DoGenericObjectDeletion(yaffs_Object * in); + +static yaffs_BlockInfo *yaffs_GetBlockInfo(yaffs_Device * dev, int blockNo); + + +static int yaffs_CheckChunkErased(struct yaffs_DeviceStruct *dev, + int chunkInNAND); + +static int yaffs_UnlinkWorker(yaffs_Object * obj); +static void yaffs_DestroyObject(yaffs_Object * obj); + +static int yaffs_TagsMatch(const yaffs_ExtendedTags * tags, int objectId, + int chunkInObject); + +loff_t yaffs_GetFileSize(yaffs_Object * obj); + +static int yaffs_AllocateChunk(yaffs_Device * dev, int useReserve, yaffs_BlockInfo **blockUsedPtr); + +static void yaffs_VerifyFreeChunks(yaffs_Device * dev); + +static void yaffs_CheckObjectDetailsLoaded(yaffs_Object *in); + +static void yaffs_VerifyDirectory(yaffs_Object *directory); +#ifdef YAFFS_PARANOID +static int yaffs_CheckFileSanity(yaffs_Object * in); +#else +#define yaffs_CheckFileSanity(in) +#endif + +static void yaffs_InvalidateWholeChunkCache(yaffs_Object * in); +static void yaffs_InvalidateChunkCache(yaffs_Object * object, int chunkId); + +static void yaffs_InvalidateCheckpoint(yaffs_Device *dev); + +static int yaffs_FindChunkInFile(yaffs_Object * in, int chunkInInode, + yaffs_ExtendedTags * tags); + +static __u32 yaffs_GetChunkGroupBase(yaffs_Device *dev, yaffs_Tnode *tn, unsigned pos); +static yaffs_Tnode *yaffs_FindLevel0Tnode(yaffs_Device * dev, + yaffs_FileStructure * fStruct, + __u32 chunkId); + + +/* Function to calculate chunk and offset */ + +static void yaffs_AddrToChunk(yaffs_Device *dev, loff_t addr, int *chunkOut, __u32 *offsetOut) +{ + int chunk; + __u32 offset; + + chunk = (__u32)(addr >> dev->chunkShift); + + if(dev->chunkDiv == 1) + { + /* easy power of 2 case */ + offset = (__u32)(addr & dev->chunkMask); + } + else + { + /* Non power-of-2 case */ + + loff_t chunkBase; + + chunk /= dev->chunkDiv; + + chunkBase = ((loff_t)chunk) * dev->nDataBytesPerChunk; + offset = (__u32)(addr - chunkBase); + } + + *chunkOut = chunk; + *offsetOut = offset; +} + +/* Function to return the number of shifts for a power of 2 greater than or equal + * to the given number + * Note we don't try to cater for all possible numbers and this does not have to + * be hellishly efficient. + */ + +static __u32 ShiftsGE(__u32 x) +{ + int extraBits; + int nShifts; + + nShifts = extraBits = 0; + + while(x>1){ + if(x & 1) extraBits++; + x>>=1; + nShifts++; + } + + if(extraBits) + nShifts++; + + return nShifts; +} + +/* Function to return the number of shifts to get a 1 in bit 0 + */ + +static __u32 Shifts(__u32 x) +{ + int nShifts; + + nShifts = 0; + + if(!x) return 0; + + while( !(x&1)){ + x>>=1; + nShifts++; + } + + return nShifts; +} + + + +/* + * Temporary buffer manipulations. + */ + +static int yaffs_InitialiseTempBuffers(yaffs_Device *dev) +{ + int i; + __u8 *buf = (__u8 *)1; + + memset(dev->tempBuffer,0,sizeof(dev->tempBuffer)); + + for (i = 0; buf && i < YAFFS_N_TEMP_BUFFERS; i++) { + dev->tempBuffer[i].line = 0; /* not in use */ + dev->tempBuffer[i].buffer = buf = + YMALLOC_DMA(dev->totalBytesPerChunk); + } + + return buf ? YAFFS_OK : YAFFS_FAIL; + +} + +__u8 *yaffs_GetTempBuffer(yaffs_Device * dev, int lineNo) +{ + int i, j; + + dev->tempInUse++; + if(dev->tempInUse > dev->maxTemp) + dev->maxTemp = dev->tempInUse; + + for (i = 0; i < YAFFS_N_TEMP_BUFFERS; i++) { + if (dev->tempBuffer[i].line == 0) { + dev->tempBuffer[i].line = lineNo; + if ((i + 1) > dev->maxTemp) { + dev->maxTemp = i + 1; + for (j = 0; j <= i; j++) + dev->tempBuffer[j].maxLine = + dev->tempBuffer[j].line; + } + + return dev->tempBuffer[i].buffer; + } + } + + T(YAFFS_TRACE_BUFFERS, + (TSTR("Out of temp buffers at line %d, other held by lines:"), + lineNo)); + for (i = 0; i < YAFFS_N_TEMP_BUFFERS; i++) { + T(YAFFS_TRACE_BUFFERS, (TSTR(" %d "), dev->tempBuffer[i].line)); + } + T(YAFFS_TRACE_BUFFERS, (TSTR(" " TENDSTR))); + + /* + * If we got here then we have to allocate an unmanaged one + * This is not good. + */ + + dev->unmanagedTempAllocations++; + return YMALLOC(dev->nDataBytesPerChunk); + +} + +void yaffs_ReleaseTempBuffer(yaffs_Device * dev, __u8 * buffer, + int lineNo) +{ + int i; + + dev->tempInUse--; + + for (i = 0; i < YAFFS_N_TEMP_BUFFERS; i++) { + if (dev->tempBuffer[i].buffer == buffer) { + dev->tempBuffer[i].line = 0; + return; + } + } + + if (buffer) { + /* assume it is an unmanaged one. */ + T(YAFFS_TRACE_BUFFERS, + (TSTR("Releasing unmanaged temp buffer in line %d" TENDSTR), + lineNo)); + YFREE(buffer); + dev->unmanagedTempDeallocations++; + } + +} + +/* + * Determine if we have a managed buffer. + */ +int yaffs_IsManagedTempBuffer(yaffs_Device * dev, const __u8 * buffer) +{ + int i; + for (i = 0; i < YAFFS_N_TEMP_BUFFERS; i++) { + if (dev->tempBuffer[i].buffer == buffer) + return 1; + + } + + for (i = 0; i < dev->nShortOpCaches; i++) { + if( dev->srCache[i].data == buffer ) + return 1; + + } + + if (buffer == dev->checkpointBuffer) + return 1; + + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: unmaged buffer detected.\n" TENDSTR))); + return 0; +} + + + +/* + * Chunk bitmap manipulations + */ + +static Y_INLINE __u8 *yaffs_BlockBits(yaffs_Device * dev, int blk) +{ + if (blk < dev->internalStartBlock || blk > dev->internalEndBlock) { + T(YAFFS_TRACE_ERROR, + (TSTR("**>> yaffs: BlockBits block %d is not valid" TENDSTR), + blk)); + YBUG(); + } + return dev->chunkBits + + (dev->chunkBitmapStride * (blk - dev->internalStartBlock)); +} + +static Y_INLINE void yaffs_VerifyChunkBitId(yaffs_Device *dev, int blk, int chunk) +{ + if(blk < dev->internalStartBlock || blk > dev->internalEndBlock || + chunk < 0 || chunk >= dev->nChunksPerBlock) { + T(YAFFS_TRACE_ERROR, + (TSTR("**>> yaffs: Chunk Id (%d:%d) invalid"TENDSTR),blk,chunk)); + YBUG(); + } +} + +static Y_INLINE void yaffs_ClearChunkBits(yaffs_Device * dev, int blk) +{ + __u8 *blkBits = yaffs_BlockBits(dev, blk); + + memset(blkBits, 0, dev->chunkBitmapStride); +} + +static Y_INLINE void yaffs_ClearChunkBit(yaffs_Device * dev, int blk, int chunk) +{ + __u8 *blkBits = yaffs_BlockBits(dev, blk); + + yaffs_VerifyChunkBitId(dev,blk,chunk); + + blkBits[chunk / 8] &= ~(1 << (chunk & 7)); +} + +static Y_INLINE void yaffs_SetChunkBit(yaffs_Device * dev, int blk, int chunk) +{ + __u8 *blkBits = yaffs_BlockBits(dev, blk); + + yaffs_VerifyChunkBitId(dev,blk,chunk); + + blkBits[chunk / 8] |= (1 << (chunk & 7)); +} + +static Y_INLINE int yaffs_CheckChunkBit(yaffs_Device * dev, int blk, int chunk) +{ + __u8 *blkBits = yaffs_BlockBits(dev, blk); + yaffs_VerifyChunkBitId(dev,blk,chunk); + + return (blkBits[chunk / 8] & (1 << (chunk & 7))) ? 1 : 0; +} + +static Y_INLINE int yaffs_StillSomeChunkBits(yaffs_Device * dev, int blk) +{ + __u8 *blkBits = yaffs_BlockBits(dev, blk); + int i; + for (i = 0; i < dev->chunkBitmapStride; i++) { + if (*blkBits) + return 1; + blkBits++; + } + return 0; +} + +static int yaffs_CountChunkBits(yaffs_Device * dev, int blk) +{ + __u8 *blkBits = yaffs_BlockBits(dev, blk); + int i; + int n = 0; + for (i = 0; i < dev->chunkBitmapStride; i++) { + __u8 x = *blkBits; + while(x){ + if(x & 1) + n++; + x >>=1; + } + + blkBits++; + } + return n; +} + +/* + * Verification code + */ + +static int yaffs_SkipVerification(yaffs_Device *dev) +{ + return !(yaffs_traceMask & (YAFFS_TRACE_VERIFY | YAFFS_TRACE_VERIFY_FULL)); +} + +static int yaffs_SkipFullVerification(yaffs_Device *dev) +{ + return !(yaffs_traceMask & (YAFFS_TRACE_VERIFY_FULL)); +} + +static int yaffs_SkipNANDVerification(yaffs_Device *dev) +{ + return !(yaffs_traceMask & (YAFFS_TRACE_VERIFY_NAND)); +} + +static const char * blockStateName[] = { +"Unknown", +"Needs scanning", +"Scanning", +"Empty", +"Allocating", +"Full", +"Dirty", +"Checkpoint", +"Collecting", +"Dead" +}; + +static void yaffs_VerifyBlock(yaffs_Device *dev,yaffs_BlockInfo *bi,int n) +{ + int actuallyUsed; + int inUse; + + if(yaffs_SkipVerification(dev)) + return; + + /* Report illegal runtime states */ + if(bi->blockState >= YAFFS_NUMBER_OF_BLOCK_STATES) + T(YAFFS_TRACE_VERIFY,(TSTR("Block %d has undefined state %d"TENDSTR),n,bi->blockState)); + + switch(bi->blockState){ + case YAFFS_BLOCK_STATE_UNKNOWN: + case YAFFS_BLOCK_STATE_SCANNING: + case YAFFS_BLOCK_STATE_NEEDS_SCANNING: + T(YAFFS_TRACE_VERIFY,(TSTR("Block %d has bad run-state %s"TENDSTR), + n,blockStateName[bi->blockState])); + } + + /* Check pages in use and soft deletions are legal */ + + actuallyUsed = bi->pagesInUse - bi->softDeletions; + + if(bi->pagesInUse < 0 || bi->pagesInUse > dev->nChunksPerBlock || + bi->softDeletions < 0 || bi->softDeletions > dev->nChunksPerBlock || + actuallyUsed < 0 || actuallyUsed > dev->nChunksPerBlock) + T(YAFFS_TRACE_VERIFY,(TSTR("Block %d has illegal values pagesInUsed %d softDeletions %d"TENDSTR), + n,bi->pagesInUse,bi->softDeletions)); + + + /* Check chunk bitmap legal */ + inUse = yaffs_CountChunkBits(dev,n); + if(inUse != bi->pagesInUse) + T(YAFFS_TRACE_VERIFY,(TSTR("Block %d has inconsistent values pagesInUse %d counted chunk bits %d"TENDSTR), + n,bi->pagesInUse,inUse)); + + /* Check that the sequence number is valid. + * Ten million is legal, but is very unlikely + */ + if(dev->isYaffs2 && + (bi->blockState == YAFFS_BLOCK_STATE_ALLOCATING || bi->blockState == YAFFS_BLOCK_STATE_FULL) && + (bi->sequenceNumber < YAFFS_LOWEST_SEQUENCE_NUMBER || bi->sequenceNumber > 10000000 )) + T(YAFFS_TRACE_VERIFY,(TSTR("Block %d has suspect sequence number of %d"TENDSTR), + n,bi->sequenceNumber)); + +} + +static void yaffs_VerifyCollectedBlock(yaffs_Device *dev,yaffs_BlockInfo *bi,int n) +{ + yaffs_VerifyBlock(dev,bi,n); + + /* After collection the block should be in the erased state */ + /* This will need to change if we do partial gc */ + + if(bi->blockState != YAFFS_BLOCK_STATE_COLLECTING && + bi->blockState != YAFFS_BLOCK_STATE_EMPTY){ + T(YAFFS_TRACE_ERROR,(TSTR("Block %d is in state %d after gc, should be erased"TENDSTR), + n,bi->blockState)); + } +} + +static void yaffs_VerifyBlocks(yaffs_Device *dev) +{ + int i; + int nBlocksPerState[YAFFS_NUMBER_OF_BLOCK_STATES]; + int nIllegalBlockStates = 0; + + + if(yaffs_SkipVerification(dev)) + return; + + memset(nBlocksPerState,0,sizeof(nBlocksPerState)); + + + for(i = dev->internalStartBlock; i <= dev->internalEndBlock; i++){ + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev,i); + yaffs_VerifyBlock(dev,bi,i); + + if(bi->blockState < YAFFS_NUMBER_OF_BLOCK_STATES) + nBlocksPerState[bi->blockState]++; + else + nIllegalBlockStates++; + + } + + T(YAFFS_TRACE_VERIFY,(TSTR(""TENDSTR))); + T(YAFFS_TRACE_VERIFY,(TSTR("Block summary"TENDSTR))); + + T(YAFFS_TRACE_VERIFY,(TSTR("%d blocks have illegal states"TENDSTR),nIllegalBlockStates)); + if(nBlocksPerState[YAFFS_BLOCK_STATE_ALLOCATING] > 1) + T(YAFFS_TRACE_VERIFY,(TSTR("Too many allocating blocks"TENDSTR))); + + for(i = 0; i < YAFFS_NUMBER_OF_BLOCK_STATES; i++) + T(YAFFS_TRACE_VERIFY, + (TSTR("%s %d blocks"TENDSTR), + blockStateName[i],nBlocksPerState[i])); + + if(dev->blocksInCheckpoint != nBlocksPerState[YAFFS_BLOCK_STATE_CHECKPOINT]) + T(YAFFS_TRACE_VERIFY, + (TSTR("Checkpoint block count wrong dev %d count %d"TENDSTR), + dev->blocksInCheckpoint, nBlocksPerState[YAFFS_BLOCK_STATE_CHECKPOINT])); + + if(dev->nErasedBlocks != nBlocksPerState[YAFFS_BLOCK_STATE_EMPTY]) + T(YAFFS_TRACE_VERIFY, + (TSTR("Erased block count wrong dev %d count %d"TENDSTR), + dev->nErasedBlocks, nBlocksPerState[YAFFS_BLOCK_STATE_EMPTY])); + + if(nBlocksPerState[YAFFS_BLOCK_STATE_COLLECTING] > 1) + T(YAFFS_TRACE_VERIFY, + (TSTR("Too many collecting blocks %d (max is 1)"TENDSTR), + nBlocksPerState[YAFFS_BLOCK_STATE_COLLECTING])); + + T(YAFFS_TRACE_VERIFY,(TSTR(""TENDSTR))); + +} + +/* + * Verify the object header. oh must be valid, but obj and tags may be NULL in which + * case those tests will not be performed. + */ +static void yaffs_VerifyObjectHeader(yaffs_Object *obj, yaffs_ObjectHeader *oh, yaffs_ExtendedTags *tags, int parentCheck) +{ + if(obj && yaffs_SkipVerification(obj->myDev)) + return; + + if(!(tags && obj && oh)){ + T(YAFFS_TRACE_VERIFY, + (TSTR("Verifying object header tags %x obj %x oh %x"TENDSTR), + (__u32)tags,(__u32)obj,(__u32)oh)); + return; + } + + if(oh->type <= YAFFS_OBJECT_TYPE_UNKNOWN || + oh->type > YAFFS_OBJECT_TYPE_MAX) + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d header type is illegal value 0x%x"TENDSTR), + tags->objectId, oh->type)); + + if(tags->objectId != obj->objectId) + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d header mismatch objectId %d"TENDSTR), + tags->objectId, obj->objectId)); + + + /* + * Check that the object's parent ids match if parentCheck requested. + * + * Tests do not apply to the root object. + */ + + if(parentCheck && tags->objectId > 1 && !obj->parent) + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d header mismatch parentId %d obj->parent is NULL"TENDSTR), + tags->objectId, oh->parentObjectId)); + + + if(parentCheck && obj->parent && + oh->parentObjectId != obj->parent->objectId && + (oh->parentObjectId != YAFFS_OBJECTID_UNLINKED || + obj->parent->objectId != YAFFS_OBJECTID_DELETED)) + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d header mismatch parentId %d parentObjectId %d"TENDSTR), + tags->objectId, oh->parentObjectId, obj->parent->objectId)); + + + if(tags->objectId > 1 && oh->name[0] == 0) /* Null name */ + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d header name is NULL"TENDSTR), + obj->objectId)); + + if(tags->objectId > 1 && ((__u8)(oh->name[0])) == 0xff) /* Trashed name */ + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d header name is 0xFF"TENDSTR), + obj->objectId)); +} + + + +static int yaffs_VerifyTnodeWorker(yaffs_Object * obj, yaffs_Tnode * tn, + __u32 level, int chunkOffset) +{ + int i; + yaffs_Device *dev = obj->myDev; + int ok = 1; + + if (tn) { + if (level > 0) { + + for (i = 0; i < YAFFS_NTNODES_INTERNAL && ok; i++){ + if (tn->internal[i]) { + ok = yaffs_VerifyTnodeWorker(obj, + tn->internal[i], + level - 1, + (chunkOffset<objectId; + + chunkOffset <<= YAFFS_TNODES_LEVEL0_BITS; + + for(i = 0; i < YAFFS_NTNODES_LEVEL0; i++){ + __u32 theChunk = yaffs_GetChunkGroupBase(dev,tn,i); + + if(theChunk > 0){ + /* T(~0,(TSTR("verifying (%d:%d) %d"TENDSTR),tags.objectId,tags.chunkId,theChunk)); */ + yaffs_ReadChunkWithTagsFromNAND(dev,theChunk,NULL, &tags); + if(tags.objectId != objectId || tags.chunkId != chunkOffset){ + T(~0,(TSTR("Object %d chunkId %d NAND mismatch chunk %d tags (%d:%d)"TENDSTR), + objectId, chunkOffset, theChunk, + tags.objectId, tags.chunkId)); + } + } + chunkOffset++; + } + } + } + + return ok; + +} + + +static void yaffs_VerifyFile(yaffs_Object *obj) +{ + int requiredTallness; + int actualTallness; + __u32 lastChunk; + __u32 x; + __u32 i; + yaffs_Device *dev; + yaffs_ExtendedTags tags; + yaffs_Tnode *tn; + __u32 objectId; + + if(!obj) + return; + + if(yaffs_SkipVerification(obj->myDev)) + return; + + dev = obj->myDev; + objectId = obj->objectId; + + /* Check file size is consistent with tnode depth */ + lastChunk = obj->variant.fileVariant.fileSize / dev->nDataBytesPerChunk + 1; + x = lastChunk >> YAFFS_TNODES_LEVEL0_BITS; + requiredTallness = 0; + while (x> 0) { + x >>= YAFFS_TNODES_INTERNAL_BITS; + requiredTallness++; + } + + actualTallness = obj->variant.fileVariant.topLevel; + + if(requiredTallness > actualTallness ) + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d had tnode tallness %d, needs to be %d"TENDSTR), + obj->objectId,actualTallness, requiredTallness)); + + + /* Check that the chunks in the tnode tree are all correct. + * We do this by scanning through the tnode tree and + * checking the tags for every chunk match. + */ + + if(yaffs_SkipNANDVerification(dev)) + return; + + for(i = 1; i <= lastChunk; i++){ + tn = yaffs_FindLevel0Tnode(dev, &obj->variant.fileVariant,i); + + if (tn) { + __u32 theChunk = yaffs_GetChunkGroupBase(dev,tn,i); + if(theChunk > 0){ + /* T(~0,(TSTR("verifying (%d:%d) %d"TENDSTR),objectId,i,theChunk)); */ + yaffs_ReadChunkWithTagsFromNAND(dev,theChunk,NULL, &tags); + if(tags.objectId != objectId || tags.chunkId != i){ + T(~0,(TSTR("Object %d chunkId %d NAND mismatch chunk %d tags (%d:%d)"TENDSTR), + objectId, i, theChunk, + tags.objectId, tags.chunkId)); + } + } + } + + } + +} + + +static void yaffs_VerifyHardLink(yaffs_Object *obj) +{ + if(obj && yaffs_SkipVerification(obj->myDev)) + return; + + /* Verify sane equivalent object */ +} + +static void yaffs_VerifySymlink(yaffs_Object *obj) +{ + if(obj && yaffs_SkipVerification(obj->myDev)) + return; + + /* Verify symlink string */ +} + +static void yaffs_VerifySpecial(yaffs_Object *obj) +{ + if(obj && yaffs_SkipVerification(obj->myDev)) + return; +} + +static void yaffs_VerifyObject(yaffs_Object *obj) +{ + yaffs_Device *dev; + + __u32 chunkMin; + __u32 chunkMax; + + __u32 chunkIdOk; + __u32 chunkIsLive; + + if(!obj) + return; + + dev = obj->myDev; + + if(yaffs_SkipVerification(dev)) + return; + + /* Check sane object header chunk */ + + chunkMin = dev->internalStartBlock * dev->nChunksPerBlock; + chunkMax = (dev->internalEndBlock+1) * dev->nChunksPerBlock - 1; + + chunkIdOk = (((unsigned)(obj->hdrChunk)) >= chunkMin && ((unsigned)(obj->hdrChunk)) <= chunkMax); + chunkIsLive = chunkIdOk && + yaffs_CheckChunkBit(dev, + obj->hdrChunk / dev->nChunksPerBlock, + obj->hdrChunk % dev->nChunksPerBlock); + if(!obj->fake && + (!chunkIdOk || !chunkIsLive)) { + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d has chunkId %d %s %s"TENDSTR), + obj->objectId,obj->hdrChunk, + chunkIdOk ? "" : ",out of range", + chunkIsLive || !chunkIdOk ? "" : ",marked as deleted")); + } + + if(chunkIdOk && chunkIsLive &&!yaffs_SkipNANDVerification(dev)) { + yaffs_ExtendedTags tags; + yaffs_ObjectHeader *oh; + __u8 *buffer = yaffs_GetTempBuffer(dev,__LINE__); + + oh = (yaffs_ObjectHeader *)buffer; + + yaffs_ReadChunkWithTagsFromNAND(dev, obj->hdrChunk,buffer, &tags); + + yaffs_VerifyObjectHeader(obj,oh,&tags,1); + + yaffs_ReleaseTempBuffer(dev,buffer,__LINE__); + } + + /* Verify it has a parent */ + if(obj && !obj->fake && + (!obj->parent || obj->parent->myDev != dev)){ + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d has parent pointer %p which does not look like an object"TENDSTR), + obj->objectId,obj->parent)); + } + + /* Verify parent is a directory */ + if(obj->parent && obj->parent->variantType != YAFFS_OBJECT_TYPE_DIRECTORY){ + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d's parent is not a directory (type %d)"TENDSTR), + obj->objectId,obj->parent->variantType)); + } + + switch(obj->variantType){ + case YAFFS_OBJECT_TYPE_FILE: + yaffs_VerifyFile(obj); + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + yaffs_VerifySymlink(obj); + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + yaffs_VerifyDirectory(obj); + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + yaffs_VerifyHardLink(obj); + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + yaffs_VerifySpecial(obj); + break; + case YAFFS_OBJECT_TYPE_UNKNOWN: + default: + T(YAFFS_TRACE_VERIFY, + (TSTR("Obj %d has illegaltype %d"TENDSTR), + obj->objectId,obj->variantType)); + break; + } + + +} + +static void yaffs_VerifyObjects(yaffs_Device *dev) +{ + yaffs_Object *obj; + int i; + struct ylist_head *lh; + + if(yaffs_SkipVerification(dev)) + return; + + /* Iterate through the objects in each hash entry */ + + for(i = 0; i < YAFFS_NOBJECT_BUCKETS; i++){ + ylist_for_each(lh, &dev->objectBucket[i].list) { + if (lh) { + obj = ylist_entry(lh, yaffs_Object, hashLink); + yaffs_VerifyObject(obj); + } + } + } + +} + + +/* + * Simple hash function. Needs to have a reasonable spread + */ + +static Y_INLINE int yaffs_HashFunction(int n) +{ + n = abs(n); + return (n % YAFFS_NOBJECT_BUCKETS); +} + +/* + * Access functions to useful fake objects. + * Note that root might have a presence in NAND if permissions are set. + */ + +yaffs_Object *yaffs_Root(yaffs_Device * dev) +{ + return dev->rootDir; +} + +yaffs_Object *yaffs_LostNFound(yaffs_Device * dev) +{ + return dev->lostNFoundDir; +} + + +/* + * Erased NAND checking functions + */ + +int yaffs_CheckFF(__u8 * buffer, int nBytes) +{ + /* Horrible, slow implementation */ + while (nBytes--) { + if (*buffer != 0xFF) + return 0; + buffer++; + } + return 1; +} + +static int yaffs_CheckChunkErased(struct yaffs_DeviceStruct *dev, + int chunkInNAND) +{ + + int retval = YAFFS_OK; + __u8 *data = yaffs_GetTempBuffer(dev, __LINE__); + yaffs_ExtendedTags tags; + int result; + + result = yaffs_ReadChunkWithTagsFromNAND(dev, chunkInNAND, data, &tags); + + if(tags.eccResult > YAFFS_ECC_RESULT_NO_ERROR) + retval = YAFFS_FAIL; + + + if (!yaffs_CheckFF(data, dev->nDataBytesPerChunk) || tags.chunkUsed) { + T(YAFFS_TRACE_NANDACCESS, + (TSTR("Chunk %d not erased" TENDSTR), chunkInNAND)); + retval = YAFFS_FAIL; + } + + yaffs_ReleaseTempBuffer(dev, data, __LINE__); + + return retval; + +} + +static int yaffs_WriteNewChunkWithTagsToNAND(struct yaffs_DeviceStruct *dev, + const __u8 * data, + yaffs_ExtendedTags * tags, + int useReserve) +{ + int attempts = 0; + int writeOk = 0; + int chunk; + + yaffs_InvalidateCheckpoint(dev); + + do { + yaffs_BlockInfo *bi = 0; + int erasedOk = 0; + + chunk = yaffs_AllocateChunk(dev, useReserve, &bi); + if (chunk < 0) { + /* no space */ + break; + } + + /* First check this chunk is erased, if it needs + * checking. The checking policy (unless forced + * always on) is as follows: + * + * Check the first page we try to write in a block. + * If the check passes then we don't need to check any + * more. If the check fails, we check again... + * If the block has been erased, we don't need to check. + * + * However, if the block has been prioritised for gc, + * then we think there might be something odd about + * this block and stop using it. + * + * Rationale: We should only ever see chunks that have + * not been erased if there was a partially written + * chunk due to power loss. This checking policy should + * catch that case with very few checks and thus save a + * lot of checks that are most likely not needed. + */ + if (bi->gcPrioritise) { + yaffs_DeleteChunk(dev, chunk, 1, __LINE__); + /* try another chunk */ + continue; + } + + /* let's give it a try */ + attempts++; + +#ifdef CONFIG_YAFFS_ALWAYS_CHECK_CHUNK_ERASED + bi->skipErasedCheck = 0; +#endif + if (!bi->skipErasedCheck) { + erasedOk = yaffs_CheckChunkErased(dev, chunk); + if (erasedOk != YAFFS_OK) { + T(YAFFS_TRACE_ERROR, + (TSTR ("**>> yaffs chunk %d was not erased" + TENDSTR), chunk)); + + /* try another chunk */ + continue; + } + bi->skipErasedCheck = 1; + } + + writeOk = yaffs_WriteChunkWithTagsToNAND(dev, chunk, + data, tags); + if (writeOk != YAFFS_OK) { + yaffs_HandleWriteChunkError(dev, chunk, erasedOk); + /* try another chunk */ + continue; + } + + /* Copy the data into the robustification buffer */ + yaffs_HandleWriteChunkOk(dev, chunk, data, tags); + + } while (writeOk != YAFFS_OK && + (yaffs_wr_attempts <= 0 || attempts <= yaffs_wr_attempts)); + + if(!writeOk) + chunk = -1; + + if (attempts > 1) { + T(YAFFS_TRACE_ERROR, + (TSTR("**>> yaffs write required %d attempts" TENDSTR), + attempts)); + + dev->nRetriedWrites += (attempts - 1); + } + + return chunk; +} + +/* + * Block retiring for handling a broken block. + */ + +static void yaffs_RetireBlock(yaffs_Device * dev, int blockInNAND) +{ + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev, blockInNAND); + + yaffs_InvalidateCheckpoint(dev); + + yaffs_MarkBlockBad(dev, blockInNAND); + + bi->blockState = YAFFS_BLOCK_STATE_DEAD; + bi->gcPrioritise = 0; + bi->needsRetiring = 0; + + dev->nRetiredBlocks++; +} + +/* + * Functions for robustisizing TODO + * + */ + +static void yaffs_HandleWriteChunkOk(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, + const yaffs_ExtendedTags * tags) +{ +} + +static void yaffs_HandleUpdateChunk(yaffs_Device * dev, int chunkInNAND, + const yaffs_ExtendedTags * tags) +{ +} + +void yaffs_HandleChunkError(yaffs_Device *dev, yaffs_BlockInfo *bi) +{ + if(!bi->gcPrioritise){ + bi->gcPrioritise = 1; + dev->hasPendingPrioritisedGCs = 1; + bi->chunkErrorStrikes ++; + + if(bi->chunkErrorStrikes > 3){ + bi->needsRetiring = 1; /* Too many stikes, so retire this */ + T(YAFFS_TRACE_ALWAYS, (TSTR("yaffs: Block struck out" TENDSTR))); + + } + + } +} + +static void yaffs_HandleWriteChunkError(yaffs_Device * dev, int chunkInNAND, int erasedOk) +{ + + int blockInNAND = chunkInNAND / dev->nChunksPerBlock; + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev, blockInNAND); + + yaffs_HandleChunkError(dev,bi); + + + if(erasedOk ) { + /* Was an actual write failure, so mark the block for retirement */ + bi->needsRetiring = 1; + T(YAFFS_TRACE_ERROR | YAFFS_TRACE_BAD_BLOCKS, + (TSTR("**>> Block %d needs retiring" TENDSTR), blockInNAND)); + + + } + + /* Delete the chunk */ + yaffs_DeleteChunk(dev, chunkInNAND, 1, __LINE__); +} + + +/*---------------- Name handling functions ------------*/ + +static __u16 yaffs_CalcNameSum(const YCHAR * name) +{ + __u16 sum = 0; + __u16 i = 1; + + const YUCHAR *bname = (const YUCHAR *) name; + if (bname) { + while ((*bname) && (i < (YAFFS_MAX_NAME_LENGTH/2))) { + +#ifdef CONFIG_YAFFS_CASE_INSENSITIVE + sum += yaffs_toupper(*bname) * i; +#else + sum += (*bname) * i; +#endif + i++; + bname++; + } + } + + return sum; +} + +static void yaffs_SetObjectName(yaffs_Object * obj, const YCHAR * name) +{ +#ifdef CONFIG_YAFFS_SHORT_NAMES_IN_RAM + memset(obj->shortName,0,sizeof (YCHAR) * (YAFFS_SHORT_NAME_LENGTH+1)); + if (name && yaffs_strlen(name) <= YAFFS_SHORT_NAME_LENGTH) { + yaffs_strcpy(obj->shortName, name); + } else { + obj->shortName[0] = _Y('\0'); + } +#endif + obj->sum = yaffs_CalcNameSum(name); +} + +/*-------------------- TNODES ------------------- + + * List of spare tnodes + * The list is hooked together using the first pointer + * in the tnode. + */ + +/* yaffs_CreateTnodes creates a bunch more tnodes and + * adds them to the tnode free list. + * Don't use this function directly + */ + +static int yaffs_CreateTnodes(yaffs_Device * dev, int nTnodes) +{ + int i; + int tnodeSize; + yaffs_Tnode *newTnodes; + __u8 *mem; + yaffs_Tnode *curr; + yaffs_Tnode *next; + yaffs_TnodeList *tnl; + + if (nTnodes < 1) + return YAFFS_OK; + + /* Calculate the tnode size in bytes for variable width tnode support. + * Must be a multiple of 32-bits */ + tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8; + + if(tnodeSize < sizeof(yaffs_Tnode)) + tnodeSize = sizeof(yaffs_Tnode); + + + /* make these things */ + + newTnodes = YMALLOC(nTnodes * tnodeSize); + mem = (__u8 *)newTnodes; + + if (!newTnodes) { + T(YAFFS_TRACE_ERROR, + (TSTR("yaffs: Could not allocate Tnodes" TENDSTR))); + return YAFFS_FAIL; + } + + /* Hook them into the free list */ +#if 0 + for (i = 0; i < nTnodes - 1; i++) { + newTnodes[i].internal[0] = &newTnodes[i + 1]; +#ifdef CONFIG_YAFFS_TNODE_LIST_DEBUG + newTnodes[i].internal[YAFFS_NTNODES_INTERNAL] = (void *)1; +#endif + } + + newTnodes[nTnodes - 1].internal[0] = dev->freeTnodes; +#ifdef CONFIG_YAFFS_TNODE_LIST_DEBUG + newTnodes[nTnodes - 1].internal[YAFFS_NTNODES_INTERNAL] = (void *)1; +#endif + dev->freeTnodes = newTnodes; +#else + /* New hookup for wide tnodes */ + for(i = 0; i < nTnodes -1; i++) { + curr = (yaffs_Tnode *) &mem[i * tnodeSize]; + next = (yaffs_Tnode *) &mem[(i+1) * tnodeSize]; + curr->internal[0] = next; + } + + curr = (yaffs_Tnode *) &mem[(nTnodes - 1) * tnodeSize]; + curr->internal[0] = dev->freeTnodes; + dev->freeTnodes = (yaffs_Tnode *)mem; + +#endif + + + dev->nFreeTnodes += nTnodes; + dev->nTnodesCreated += nTnodes; + + /* Now add this bunch of tnodes to a list for freeing up. + * NB If we can't add this to the management list it isn't fatal + * but it just means we can't free this bunch of tnodes later. + */ + + tnl = YMALLOC(sizeof(yaffs_TnodeList)); + if (!tnl) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("yaffs: Could not add tnodes to management list" TENDSTR))); + return YAFFS_FAIL; + + } else { + tnl->tnodes = newTnodes; + tnl->next = dev->allocatedTnodeList; + dev->allocatedTnodeList = tnl; + } + + T(YAFFS_TRACE_ALLOCATE, (TSTR("yaffs: Tnodes added" TENDSTR))); + + return YAFFS_OK; +} + +/* GetTnode gets us a clean tnode. Tries to make allocate more if we run out */ + +static yaffs_Tnode *yaffs_GetTnodeRaw(yaffs_Device * dev) +{ + yaffs_Tnode *tn = NULL; + + /* If there are none left make more */ + if (!dev->freeTnodes) { + yaffs_CreateTnodes(dev, YAFFS_ALLOCATION_NTNODES); + } + + if (dev->freeTnodes) { + tn = dev->freeTnodes; +#ifdef CONFIG_YAFFS_TNODE_LIST_DEBUG + if (tn->internal[YAFFS_NTNODES_INTERNAL] != (void *)1) { + /* Hoosterman, this thing looks like it isn't in the list */ + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: Tnode list bug 1" TENDSTR))); + } +#endif + dev->freeTnodes = dev->freeTnodes->internal[0]; + dev->nFreeTnodes--; + } + + dev->nCheckpointBlocksRequired = 0; /* force recalculation*/ + + return tn; +} + +static yaffs_Tnode *yaffs_GetTnode(yaffs_Device * dev) +{ + yaffs_Tnode *tn = yaffs_GetTnodeRaw(dev); + int tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8; + + if(tnodeSize < sizeof(yaffs_Tnode)) + tnodeSize = sizeof(yaffs_Tnode); + + if(tn) + memset(tn, 0, tnodeSize); + + return tn; +} + +/* FreeTnode frees up a tnode and puts it back on the free list */ +static void yaffs_FreeTnode(yaffs_Device * dev, yaffs_Tnode * tn) +{ + if (tn) { +#ifdef CONFIG_YAFFS_TNODE_LIST_DEBUG + if (tn->internal[YAFFS_NTNODES_INTERNAL] != 0) { + /* Hoosterman, this thing looks like it is already in the list */ + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: Tnode list bug 2" TENDSTR))); + } + tn->internal[YAFFS_NTNODES_INTERNAL] = (void *)1; +#endif + tn->internal[0] = dev->freeTnodes; + dev->freeTnodes = tn; + dev->nFreeTnodes++; + } + dev->nCheckpointBlocksRequired = 0; /* force recalculation*/ + +} + +static void yaffs_DeinitialiseTnodes(yaffs_Device * dev) +{ + /* Free the list of allocated tnodes */ + yaffs_TnodeList *tmp; + + while (dev->allocatedTnodeList) { + tmp = dev->allocatedTnodeList->next; + + YFREE(dev->allocatedTnodeList->tnodes); + YFREE(dev->allocatedTnodeList); + dev->allocatedTnodeList = tmp; + + } + + dev->freeTnodes = NULL; + dev->nFreeTnodes = 0; +} + +static void yaffs_InitialiseTnodes(yaffs_Device * dev) +{ + dev->allocatedTnodeList = NULL; + dev->freeTnodes = NULL; + dev->nFreeTnodes = 0; + dev->nTnodesCreated = 0; + +} + + +void yaffs_PutLevel0Tnode(yaffs_Device *dev, yaffs_Tnode *tn, unsigned pos, unsigned val) +{ + __u32 *map = (__u32 *)tn; + __u32 bitInMap; + __u32 bitInWord; + __u32 wordInMap; + __u32 mask; + + pos &= YAFFS_TNODES_LEVEL0_MASK; + val >>= dev->chunkGroupBits; + + bitInMap = pos * dev->tnodeWidth; + wordInMap = bitInMap /32; + bitInWord = bitInMap & (32 -1); + + mask = dev->tnodeMask << bitInWord; + + map[wordInMap] &= ~mask; + map[wordInMap] |= (mask & (val << bitInWord)); + + if(dev->tnodeWidth > (32-bitInWord)) { + bitInWord = (32 - bitInWord); + wordInMap++;; + mask = dev->tnodeMask >> (/*dev->tnodeWidth -*/ bitInWord); + map[wordInMap] &= ~mask; + map[wordInMap] |= (mask & (val >> bitInWord)); + } +} + +static __u32 yaffs_GetChunkGroupBase(yaffs_Device *dev, yaffs_Tnode *tn, unsigned pos) +{ + __u32 *map = (__u32 *)tn; + __u32 bitInMap; + __u32 bitInWord; + __u32 wordInMap; + __u32 val; + + pos &= YAFFS_TNODES_LEVEL0_MASK; + + bitInMap = pos * dev->tnodeWidth; + wordInMap = bitInMap /32; + bitInWord = bitInMap & (32 -1); + + val = map[wordInMap] >> bitInWord; + + if(dev->tnodeWidth > (32-bitInWord)) { + bitInWord = (32 - bitInWord); + wordInMap++;; + val |= (map[wordInMap] << bitInWord); + } + + val &= dev->tnodeMask; + val <<= dev->chunkGroupBits; + + return val; +} + +/* ------------------- End of individual tnode manipulation -----------------*/ + +/* ---------Functions to manipulate the look-up tree (made up of tnodes) ------ + * The look up tree is represented by the top tnode and the number of topLevel + * in the tree. 0 means only the level 0 tnode is in the tree. + */ + +/* FindLevel0Tnode finds the level 0 tnode, if one exists. */ +static yaffs_Tnode *yaffs_FindLevel0Tnode(yaffs_Device * dev, + yaffs_FileStructure * fStruct, + __u32 chunkId) +{ + + yaffs_Tnode *tn = fStruct->top; + __u32 i; + int requiredTallness; + int level = fStruct->topLevel; + + /* Check sane level and chunk Id */ + if (level < 0 || level > YAFFS_TNODES_MAX_LEVEL) { + return NULL; + } + + if (chunkId > YAFFS_MAX_CHUNK_ID) { + return NULL; + } + + /* First check we're tall enough (ie enough topLevel) */ + + i = chunkId >> YAFFS_TNODES_LEVEL0_BITS; + requiredTallness = 0; + while (i) { + i >>= YAFFS_TNODES_INTERNAL_BITS; + requiredTallness++; + } + + if (requiredTallness > fStruct->topLevel) { + /* Not tall enough, so we can't find it, return NULL. */ + return NULL; + } + + /* Traverse down to level 0 */ + while (level > 0 && tn) { + tn = tn-> + internal[(chunkId >> + ( YAFFS_TNODES_LEVEL0_BITS + + (level - 1) * + YAFFS_TNODES_INTERNAL_BITS) + ) & + YAFFS_TNODES_INTERNAL_MASK]; + level--; + + } + + return tn; +} + +/* AddOrFindLevel0Tnode finds the level 0 tnode if it exists, otherwise first expands the tree. + * This happens in two steps: + * 1. If the tree isn't tall enough, then make it taller. + * 2. Scan down the tree towards the level 0 tnode adding tnodes if required. + * + * Used when modifying the tree. + * + * If the tn argument is NULL, then a fresh tnode will be added otherwise the specified tn will + * be plugged into the ttree. + */ + +static yaffs_Tnode *yaffs_AddOrFindLevel0Tnode(yaffs_Device * dev, + yaffs_FileStructure * fStruct, + __u32 chunkId, + yaffs_Tnode *passedTn) +{ + + int requiredTallness; + int i; + int l; + yaffs_Tnode *tn; + + __u32 x; + + + /* Check sane level and page Id */ + if (fStruct->topLevel < 0 || fStruct->topLevel > YAFFS_TNODES_MAX_LEVEL) { + return NULL; + } + + if (chunkId > YAFFS_MAX_CHUNK_ID) { + return NULL; + } + + /* First check we're tall enough (ie enough topLevel) */ + + x = chunkId >> YAFFS_TNODES_LEVEL0_BITS; + requiredTallness = 0; + while (x) { + x >>= YAFFS_TNODES_INTERNAL_BITS; + requiredTallness++; + } + + + if (requiredTallness > fStruct->topLevel) { + /* Not tall enough,gotta make the tree taller */ + for (i = fStruct->topLevel; i < requiredTallness; i++) { + + tn = yaffs_GetTnode(dev); + + if (tn) { + tn->internal[0] = fStruct->top; + fStruct->top = tn; + } else { + T(YAFFS_TRACE_ERROR, + (TSTR("yaffs: no more tnodes" TENDSTR))); + } + } + + fStruct->topLevel = requiredTallness; + } + + /* Traverse down to level 0, adding anything we need */ + + l = fStruct->topLevel; + tn = fStruct->top; + + if(l > 0) { + while (l > 0 && tn) { + x = (chunkId >> + ( YAFFS_TNODES_LEVEL0_BITS + + (l - 1) * YAFFS_TNODES_INTERNAL_BITS)) & + YAFFS_TNODES_INTERNAL_MASK; + + + if((l>1) && !tn->internal[x]){ + /* Add missing non-level-zero tnode */ + tn->internal[x] = yaffs_GetTnode(dev); + + } else if(l == 1) { + /* Looking from level 1 at level 0 */ + if (passedTn) { + /* If we already have one, then release it.*/ + if(tn->internal[x]) + yaffs_FreeTnode(dev,tn->internal[x]); + tn->internal[x] = passedTn; + + } else if(!tn->internal[x]) { + /* Don't have one, none passed in */ + tn->internal[x] = yaffs_GetTnode(dev); + } + } + + tn = tn->internal[x]; + l--; + } + } else { + /* top is level 0 */ + if(passedTn) { + memcpy(tn,passedTn,(dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8); + yaffs_FreeTnode(dev,passedTn); + } + } + + return tn; +} + +static int yaffs_FindChunkInGroup(yaffs_Device * dev, int theChunk, + yaffs_ExtendedTags * tags, int objectId, + int chunkInInode) +{ + int j; + + for (j = 0; theChunk && j < dev->chunkGroupSize; j++) { + if (yaffs_CheckChunkBit + (dev, theChunk / dev->nChunksPerBlock, + theChunk % dev->nChunksPerBlock)) { + yaffs_ReadChunkWithTagsFromNAND(dev, theChunk, NULL, + tags); + if (yaffs_TagsMatch(tags, objectId, chunkInInode)) { + /* found it; */ + return theChunk; + + } + } + theChunk++; + } + return -1; +} + + +/* DeleteWorker scans backwards through the tnode tree and deletes all the + * chunks and tnodes in the file + * Returns 1 if the tree was deleted. + * Returns 0 if it stopped early due to hitting the limit and the delete is incomplete. + */ + +static int yaffs_DeleteWorker(yaffs_Object * in, yaffs_Tnode * tn, __u32 level, + int chunkOffset, int *limit) +{ + int i; + int chunkInInode; + int theChunk; + yaffs_ExtendedTags tags; + int foundChunk; + yaffs_Device *dev = in->myDev; + + int allDone = 1; + + if (tn) { + if (level > 0) { + + for (i = YAFFS_NTNODES_INTERNAL - 1; allDone && i >= 0; + i--) { + if (tn->internal[i]) { + if (limit && (*limit) < 0) { + allDone = 0; + } else { + allDone = + yaffs_DeleteWorker(in, + tn-> + internal + [i], + level - + 1, + (chunkOffset + << + YAFFS_TNODES_INTERNAL_BITS) + + i, + limit); + } + if (allDone) { + yaffs_FreeTnode(dev, + tn-> + internal[i]); + tn->internal[i] = NULL; + } + } + + } + return (allDone) ? 1 : 0; + } else if (level == 0) { + int hitLimit = 0; + + for (i = YAFFS_NTNODES_LEVEL0 - 1; i >= 0 && !hitLimit; + i--) { + theChunk = yaffs_GetChunkGroupBase(dev,tn,i); + if (theChunk) { + + chunkInInode = + (chunkOffset << + YAFFS_TNODES_LEVEL0_BITS) + i; + + foundChunk = + yaffs_FindChunkInGroup(dev, + theChunk, + &tags, + in->objectId, + chunkInInode); + + if (foundChunk > 0) { + yaffs_DeleteChunk(dev, + foundChunk, 1, + __LINE__); + in->nDataChunks--; + if (limit) { + *limit = *limit - 1; + if (*limit <= 0) { + hitLimit = 1; + } + } + + } + + yaffs_PutLevel0Tnode(dev,tn,i,0); + } + + } + return (i < 0) ? 1 : 0; + + } + + } + + return 1; + +} + +static void yaffs_SoftDeleteChunk(yaffs_Device * dev, int chunk) +{ + + yaffs_BlockInfo *theBlock; + + T(YAFFS_TRACE_DELETION, (TSTR("soft delete chunk %d" TENDSTR), chunk)); + + theBlock = yaffs_GetBlockInfo(dev, chunk / dev->nChunksPerBlock); + if (theBlock) { + theBlock->softDeletions++; + dev->nFreeChunks++; + } +} + +/* SoftDeleteWorker scans backwards through the tnode tree and soft deletes all the chunks in the file. + * All soft deleting does is increment the block's softdelete count and pulls the chunk out + * of the tnode. + * Thus, essentially this is the same as DeleteWorker except that the chunks are soft deleted. + */ + +static int yaffs_SoftDeleteWorker(yaffs_Object * in, yaffs_Tnode * tn, + __u32 level, int chunkOffset) +{ + int i; + int theChunk; + int allDone = 1; + yaffs_Device *dev = in->myDev; + + if (tn) { + if (level > 0) { + + for (i = YAFFS_NTNODES_INTERNAL - 1; allDone && i >= 0; + i--) { + if (tn->internal[i]) { + allDone = + yaffs_SoftDeleteWorker(in, + tn-> + internal[i], + level - 1, + (chunkOffset + << + YAFFS_TNODES_INTERNAL_BITS) + + i); + if (allDone) { + yaffs_FreeTnode(dev, + tn-> + internal[i]); + tn->internal[i] = NULL; + } else { + /* Hoosterman... how could this happen? */ + } + } + } + return (allDone) ? 1 : 0; + } else if (level == 0) { + + for (i = YAFFS_NTNODES_LEVEL0 - 1; i >= 0; i--) { + theChunk = yaffs_GetChunkGroupBase(dev,tn,i); + if (theChunk) { + /* Note this does not find the real chunk, only the chunk group. + * We make an assumption that a chunk group is not larger than + * a block. + */ + yaffs_SoftDeleteChunk(dev, theChunk); + yaffs_PutLevel0Tnode(dev,tn,i,0); + } + + } + return 1; + + } + + } + + return 1; + +} + +static void yaffs_SoftDeleteFile(yaffs_Object * obj) +{ + if (obj->deleted && + obj->variantType == YAFFS_OBJECT_TYPE_FILE && !obj->softDeleted) { + if (obj->nDataChunks <= 0) { + /* Empty file with no duplicate object headers, just delete it immediately */ + yaffs_FreeTnode(obj->myDev, + obj->variant.fileVariant.top); + obj->variant.fileVariant.top = NULL; + T(YAFFS_TRACE_TRACING, + (TSTR("yaffs: Deleting empty file %d" TENDSTR), + obj->objectId)); + yaffs_DoGenericObjectDeletion(obj); + } else { + yaffs_SoftDeleteWorker(obj, + obj->variant.fileVariant.top, + obj->variant.fileVariant. + topLevel, 0); + obj->softDeleted = 1; + } + } +} + +/* Pruning removes any part of the file structure tree that is beyond the + * bounds of the file (ie that does not point to chunks). + * + * A file should only get pruned when its size is reduced. + * + * Before pruning, the chunks must be pulled from the tree and the + * level 0 tnode entries must be zeroed out. + * Could also use this for file deletion, but that's probably better handled + * by a special case. + */ + +static yaffs_Tnode *yaffs_PruneWorker(yaffs_Device * dev, yaffs_Tnode * tn, + __u32 level, int del0) +{ + int i; + int hasData; + + if (tn) { + hasData = 0; + + for (i = 0; i < YAFFS_NTNODES_INTERNAL; i++) { + if (tn->internal[i] && level > 0) { + tn->internal[i] = + yaffs_PruneWorker(dev, tn->internal[i], + level - 1, + (i == 0) ? del0 : 1); + } + + if (tn->internal[i]) { + hasData++; + } + } + + if (hasData == 0 && del0) { + /* Free and return NULL */ + + yaffs_FreeTnode(dev, tn); + tn = NULL; + } + + } + + return tn; + +} + +static int yaffs_PruneFileStructure(yaffs_Device * dev, + yaffs_FileStructure * fStruct) +{ + int i; + int hasData; + int done = 0; + yaffs_Tnode *tn; + + if (fStruct->topLevel > 0) { + fStruct->top = + yaffs_PruneWorker(dev, fStruct->top, fStruct->topLevel, 0); + + /* Now we have a tree with all the non-zero branches NULL but the height + * is the same as it was. + * Let's see if we can trim internal tnodes to shorten the tree. + * We can do this if only the 0th element in the tnode is in use + * (ie all the non-zero are NULL) + */ + + while (fStruct->topLevel && !done) { + tn = fStruct->top; + + hasData = 0; + for (i = 1; i < YAFFS_NTNODES_INTERNAL; i++) { + if (tn->internal[i]) { + hasData++; + } + } + + if (!hasData) { + fStruct->top = tn->internal[0]; + fStruct->topLevel--; + yaffs_FreeTnode(dev, tn); + } else { + done = 1; + } + } + } + + return YAFFS_OK; +} + +/*-------------------- End of File Structure functions.-------------------*/ + +/* yaffs_CreateFreeObjects creates a bunch more objects and + * adds them to the object free list. + */ +static int yaffs_CreateFreeObjects(yaffs_Device * dev, int nObjects) +{ + int i; + yaffs_Object *newObjects; + yaffs_ObjectList *list; + + if (nObjects < 1) + return YAFFS_OK; + + /* make these things */ + newObjects = YMALLOC(nObjects * sizeof(yaffs_Object)); + list = YMALLOC(sizeof(yaffs_ObjectList)); + + if (!newObjects || !list) { + if(newObjects) + YFREE(newObjects); + if(list) + YFREE(list); + T(YAFFS_TRACE_ALLOCATE, + (TSTR("yaffs: Could not allocate more objects" TENDSTR))); + return YAFFS_FAIL; + } + + /* Hook them into the free list */ + for (i = 0; i < nObjects - 1; i++) { + newObjects[i].siblings.next = + (struct ylist_head *)(&newObjects[i + 1]); + } + + newObjects[nObjects - 1].siblings.next = (void *)dev->freeObjects; + dev->freeObjects = newObjects; + dev->nFreeObjects += nObjects; + dev->nObjectsCreated += nObjects; + + /* Now add this bunch of Objects to a list for freeing up. */ + + list->objects = newObjects; + list->next = dev->allocatedObjectList; + dev->allocatedObjectList = list; + + return YAFFS_OK; +} + + +/* AllocateEmptyObject gets us a clean Object. Tries to make allocate more if we run out */ +static yaffs_Object *yaffs_AllocateEmptyObject(yaffs_Device * dev) +{ + yaffs_Object *tn = NULL; + +#ifdef VALGRIND_TEST + tn = YMALLOC(sizeof(yaffs_Object)); +#else + /* If there are none left make more */ + if (!dev->freeObjects) { + yaffs_CreateFreeObjects(dev, YAFFS_ALLOCATION_NOBJECTS); + } + + if (dev->freeObjects) { + tn = dev->freeObjects; + dev->freeObjects = + (yaffs_Object *) (dev->freeObjects->siblings.next); + dev->nFreeObjects--; + } +#endif + if(tn){ + /* Now sweeten it up... */ + + memset(tn, 0, sizeof(yaffs_Object)); + tn->myDev = dev; + tn->hdrChunk = 0; + tn->variantType = YAFFS_OBJECT_TYPE_UNKNOWN; + YINIT_LIST_HEAD(&(tn->hardLinks)); + YINIT_LIST_HEAD(&(tn->hashLink)); + YINIT_LIST_HEAD(&tn->siblings); + + + /* Now make the directory sane */ + if(dev->rootDir){ + tn->parent = dev->rootDir; + ylist_add(&(tn->siblings),&dev->rootDir->variant.directoryVariant.children); + } + + /* Add it to the lost and found directory. + * NB Can't put root or lostNFound in lostNFound so + * check if lostNFound exists first + */ + if (dev->lostNFoundDir) { + yaffs_AddObjectToDirectory(dev->lostNFoundDir, tn); + } + } + + dev->nCheckpointBlocksRequired = 0; /* force recalculation*/ + + return tn; +} + +static yaffs_Object *yaffs_CreateFakeDirectory(yaffs_Device * dev, int number, + __u32 mode) +{ + + yaffs_Object *obj = + yaffs_CreateNewObject(dev, number, YAFFS_OBJECT_TYPE_DIRECTORY); + if (obj) { + obj->fake = 1; /* it is fake so it might have no NAND presence... */ + obj->renameAllowed = 0; /* ... and we're not allowed to rename it... */ + obj->unlinkAllowed = 0; /* ... or unlink it */ + obj->deleted = 0; + obj->unlinked = 0; + obj->yst_mode = mode; + obj->myDev = dev; + obj->hdrChunk = 0; /* Not a valid chunk. */ + } + + return obj; + +} + +static void yaffs_UnhashObject(yaffs_Object * tn) +{ + int bucket; + yaffs_Device *dev = tn->myDev; + + /* If it is still linked into the bucket list, free from the list */ + if (!ylist_empty(&tn->hashLink)) { + ylist_del_init(&tn->hashLink); + bucket = yaffs_HashFunction(tn->objectId); + dev->objectBucket[bucket].count--; + } + +} + +/* FreeObject frees up a Object and puts it back on the free list */ +static void yaffs_FreeObject(yaffs_Object * tn) +{ + + yaffs_Device *dev = tn->myDev; + + + if(tn->parent) + YBUG(); + if(!ylist_empty(&tn->siblings)) + YBUG(); + + +#ifdef __KERNEL__ + if (tn->myInode) { + /* We're still hooked up to a cached inode. + * Don't delete now, but mark for later deletion + */ + tn->deferedFree = 1; + return; + } +#endif + + yaffs_UnhashObject(tn); + +#ifdef VALGRIND_TEST + YFREE(tn); +#else + /* Link into the free list. */ + tn->siblings.next = (struct ylist_head *)(dev->freeObjects); + dev->freeObjects = tn; + dev->nFreeObjects++; +#endif + dev->nCheckpointBlocksRequired = 0; /* force recalculation*/ + +} + +#ifdef __KERNEL__ + +void yaffs_HandleDeferedFree(yaffs_Object * obj) +{ + if (obj->deferedFree) { + yaffs_FreeObject(obj); + } +} + +#endif + +static void yaffs_DeinitialiseObjects(yaffs_Device * dev) +{ + /* Free the list of allocated Objects */ + + yaffs_ObjectList *tmp; + + while (dev->allocatedObjectList) { + tmp = dev->allocatedObjectList->next; + YFREE(dev->allocatedObjectList->objects); + YFREE(dev->allocatedObjectList); + + dev->allocatedObjectList = tmp; + } + + dev->freeObjects = NULL; + dev->nFreeObjects = 0; +} + +static void yaffs_InitialiseObjects(yaffs_Device * dev) +{ + int i; + + dev->allocatedObjectList = NULL; + dev->freeObjects = NULL; + dev->nFreeObjects = 0; + + for (i = 0; i < YAFFS_NOBJECT_BUCKETS; i++) { + YINIT_LIST_HEAD(&dev->objectBucket[i].list); + dev->objectBucket[i].count = 0; + } + +} + +static int yaffs_FindNiceObjectBucket(yaffs_Device * dev) +{ + static int x = 0; + int i; + int l = 999; + int lowest = 999999; + + /* First let's see if we can find one that's empty. */ + + for (i = 0; i < 10 && lowest > 0; i++) { + x++; + x %= YAFFS_NOBJECT_BUCKETS; + if (dev->objectBucket[x].count < lowest) { + lowest = dev->objectBucket[x].count; + l = x; + } + + } + + /* If we didn't find an empty list, then try + * looking a bit further for a short one + */ + + for (i = 0; i < 10 && lowest > 3; i++) { + x++; + x %= YAFFS_NOBJECT_BUCKETS; + if (dev->objectBucket[x].count < lowest) { + lowest = dev->objectBucket[x].count; + l = x; + } + + } + + return l; +} + +static int yaffs_CreateNewObjectNumber(yaffs_Device * dev) +{ + int bucket = yaffs_FindNiceObjectBucket(dev); + + /* Now find an object value that has not already been taken + * by scanning the list. + */ + + int found = 0; + struct ylist_head *i; + + __u32 n = (__u32) bucket; + + /* yaffs_CheckObjectHashSanity(); */ + + while (!found) { + found = 1; + n += YAFFS_NOBJECT_BUCKETS; + if (1 || dev->objectBucket[bucket].count > 0) { + ylist_for_each(i, &dev->objectBucket[bucket].list) { + /* If there is already one in the list */ + if (i + && ylist_entry(i, yaffs_Object, + hashLink)->objectId == n) { + found = 0; + } + } + } + } + + + return n; +} + +static void yaffs_HashObject(yaffs_Object * in) +{ + int bucket = yaffs_HashFunction(in->objectId); + yaffs_Device *dev = in->myDev; + + ylist_add(&in->hashLink, &dev->objectBucket[bucket].list); + dev->objectBucket[bucket].count++; + +} + +yaffs_Object *yaffs_FindObjectByNumber(yaffs_Device * dev, __u32 number) +{ + int bucket = yaffs_HashFunction(number); + struct ylist_head *i; + yaffs_Object *in; + + ylist_for_each(i, &dev->objectBucket[bucket].list) { + /* Look if it is in the list */ + if (i) { + in = ylist_entry(i, yaffs_Object, hashLink); + if (in->objectId == number) { +#ifdef __KERNEL__ + /* Don't tell the VFS about this one if it is defered free */ + if (in->deferedFree) + return NULL; +#endif + + return in; + } + } + } + + return NULL; +} + +yaffs_Object *yaffs_CreateNewObject(yaffs_Device * dev, int number, + yaffs_ObjectType type) +{ + + yaffs_Object *theObject; + yaffs_Tnode *tn = NULL; + + if (number < 0) { + number = yaffs_CreateNewObjectNumber(dev); + } + + theObject = yaffs_AllocateEmptyObject(dev); + if(!theObject) + return NULL; + + if(type == YAFFS_OBJECT_TYPE_FILE){ + tn = yaffs_GetTnode(dev); + if(!tn){ + yaffs_FreeObject(theObject); + return NULL; + } + } + + + + if (theObject) { + theObject->fake = 0; + theObject->renameAllowed = 1; + theObject->unlinkAllowed = 1; + theObject->objectId = number; + yaffs_HashObject(theObject); + theObject->variantType = type; +#ifdef CONFIG_YAFFS_WINCE + yfsd_WinFileTimeNow(theObject->win_atime); + theObject->win_ctime[0] = theObject->win_mtime[0] = + theObject->win_atime[0]; + theObject->win_ctime[1] = theObject->win_mtime[1] = + theObject->win_atime[1]; + +#else + + theObject->yst_atime = theObject->yst_mtime = + theObject->yst_ctime = Y_CURRENT_TIME; +#endif + +#if 0 + theObject->sum_prev = 12345; + theObject->sum_trailer = 6789; +#endif + + switch (type) { + case YAFFS_OBJECT_TYPE_FILE: + theObject->variant.fileVariant.fileSize = 0; + theObject->variant.fileVariant.scannedFileSize = 0; + theObject->variant.fileVariant.shrinkSize = 0xFFFFFFFF; /* max __u32 */ + theObject->variant.fileVariant.topLevel = 0; + theObject->variant.fileVariant.top = tn; + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + YINIT_LIST_HEAD(&theObject->variant.directoryVariant. + children); + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + case YAFFS_OBJECT_TYPE_HARDLINK: + case YAFFS_OBJECT_TYPE_SPECIAL: + /* No action required */ + break; + case YAFFS_OBJECT_TYPE_UNKNOWN: + /* todo this should not happen */ + break; + } + } + + return theObject; +} + +static yaffs_Object *yaffs_FindOrCreateObjectByNumber(yaffs_Device * dev, + int number, + yaffs_ObjectType type) +{ + yaffs_Object *theObject = NULL; + + if (number > 0) { + theObject = yaffs_FindObjectByNumber(dev, number); + } + + if (!theObject) { + theObject = yaffs_CreateNewObject(dev, number, type); + } + + return theObject; + +} + + +static YCHAR *yaffs_CloneString(const YCHAR * str) +{ + YCHAR *newStr = NULL; + + if (str && *str) { + newStr = YMALLOC((yaffs_strlen(str) + 1) * sizeof(YCHAR)); + if(newStr) + yaffs_strcpy(newStr, str); + } + + return newStr; + +} + +/* + * Mknod (create) a new object. + * equivalentObject only has meaning for a hard link; + * aliasString only has meaning for a sumlink. + * rdev only has meaning for devices (a subset of special objects) + */ + +static yaffs_Object *yaffs_MknodObject(yaffs_ObjectType type, + yaffs_Object * parent, + const YCHAR * name, + __u32 mode, + __u32 uid, + __u32 gid, + yaffs_Object * equivalentObject, + const YCHAR * aliasString, __u32 rdev) +{ + yaffs_Object *in; + YCHAR *str = NULL; + + yaffs_Device *dev = parent->myDev; + + /* Check if the entry exists. If it does then fail the call since we don't want a dup.*/ + if (yaffs_FindObjectByName(parent, name)) { + return NULL; + } + + in = yaffs_CreateNewObject(dev, -1, type); + + if(!in) + return YAFFS_FAIL; + + if(type == YAFFS_OBJECT_TYPE_SYMLINK){ + str = yaffs_CloneString(aliasString); + if(!str){ + yaffs_FreeObject(in); + return NULL; + } + } + + + + if (in) { + in->hdrChunk = 0; + in->valid = 1; + in->variantType = type; + + in->yst_mode = mode; + +#ifdef CONFIG_YAFFS_WINCE + yfsd_WinFileTimeNow(in->win_atime); + in->win_ctime[0] = in->win_mtime[0] = in->win_atime[0]; + in->win_ctime[1] = in->win_mtime[1] = in->win_atime[1]; + +#else + in->yst_atime = in->yst_mtime = in->yst_ctime = Y_CURRENT_TIME; + + in->yst_rdev = rdev; + in->yst_uid = uid; + in->yst_gid = gid; +#endif + in->nDataChunks = 0; + + yaffs_SetObjectName(in, name); + in->dirty = 1; + + yaffs_AddObjectToDirectory(parent, in); + + in->myDev = parent->myDev; + + switch (type) { + case YAFFS_OBJECT_TYPE_SYMLINK: + in->variant.symLinkVariant.alias = str; + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + in->variant.hardLinkVariant.equivalentObject = + equivalentObject; + in->variant.hardLinkVariant.equivalentObjectId = + equivalentObject->objectId; + ylist_add(&in->hardLinks, &equivalentObject->hardLinks); + break; + case YAFFS_OBJECT_TYPE_FILE: + case YAFFS_OBJECT_TYPE_DIRECTORY: + case YAFFS_OBJECT_TYPE_SPECIAL: + case YAFFS_OBJECT_TYPE_UNKNOWN: + /* do nothing */ + break; + } + + if (yaffs_UpdateObjectHeader(in, name, 0, 0, 0) < 0) { + /* Could not create the object header, fail the creation */ + yaffs_DestroyObject(in); + in = NULL; + } + + } + + return in; +} + +yaffs_Object *yaffs_MknodFile(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid) +{ + return yaffs_MknodObject(YAFFS_OBJECT_TYPE_FILE, parent, name, mode, + uid, gid, NULL, NULL, 0); +} + +yaffs_Object *yaffs_MknodDirectory(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid) +{ + return yaffs_MknodObject(YAFFS_OBJECT_TYPE_DIRECTORY, parent, name, + mode, uid, gid, NULL, NULL, 0); +} + +yaffs_Object *yaffs_MknodSpecial(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid, __u32 rdev) +{ + return yaffs_MknodObject(YAFFS_OBJECT_TYPE_SPECIAL, parent, name, mode, + uid, gid, NULL, NULL, rdev); +} + +yaffs_Object *yaffs_MknodSymLink(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid, + const YCHAR * alias) +{ + return yaffs_MknodObject(YAFFS_OBJECT_TYPE_SYMLINK, parent, name, mode, + uid, gid, NULL, alias, 0); +} + +/* yaffs_Link returns the object id of the equivalent object.*/ +yaffs_Object *yaffs_Link(yaffs_Object * parent, const YCHAR * name, + yaffs_Object * equivalentObject) +{ + /* Get the real object in case we were fed a hard link as an equivalent object */ + equivalentObject = yaffs_GetEquivalentObject(equivalentObject); + + if (yaffs_MknodObject + (YAFFS_OBJECT_TYPE_HARDLINK, parent, name, 0, 0, 0, + equivalentObject, NULL, 0)) { + return equivalentObject; + } else { + return NULL; + } + +} + +static int yaffs_ChangeObjectName(yaffs_Object * obj, yaffs_Object * newDir, + const YCHAR * newName, int force, int shadows) +{ + int unlinkOp; + int deleteOp; + + yaffs_Object *existingTarget; + + if (newDir == NULL) { + newDir = obj->parent; /* use the old directory */ + } + + if (newDir->variantType != YAFFS_OBJECT_TYPE_DIRECTORY) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: yaffs_ChangeObjectName: newDir is not a directory" + TENDSTR))); + YBUG(); + } + + /* TODO: Do we need this different handling for YAFFS2 and YAFFS1?? */ + if (obj->myDev->isYaffs2) { + unlinkOp = (newDir == obj->myDev->unlinkedDir); + } else { + unlinkOp = (newDir == obj->myDev->unlinkedDir + && obj->variantType == YAFFS_OBJECT_TYPE_FILE); + } + + deleteOp = (newDir == obj->myDev->deletedDir); + + existingTarget = yaffs_FindObjectByName(newDir, newName); + + /* If the object is a file going into the unlinked directory, + * then it is OK to just stuff it in since duplicate names are allowed. + * else only proceed if the new name does not exist and if we're putting + * it into a directory. + */ + if ((unlinkOp || + deleteOp || + force || + (shadows > 0) || + !existingTarget) && + newDir->variantType == YAFFS_OBJECT_TYPE_DIRECTORY) { + yaffs_SetObjectName(obj, newName); + obj->dirty = 1; + + yaffs_AddObjectToDirectory(newDir, obj); + + if (unlinkOp) + obj->unlinked = 1; + + /* If it is a deletion then we mark it as a shrink for gc purposes. */ + if (yaffs_UpdateObjectHeader(obj, newName, 0, deleteOp, shadows)>= 0) + return YAFFS_OK; + } + + return YAFFS_FAIL; +} + +int yaffs_RenameObject(yaffs_Object * oldDir, const YCHAR * oldName, + yaffs_Object * newDir, const YCHAR * newName) +{ + yaffs_Object *obj=NULL; + yaffs_Object *existingTarget=NULL; + int force = 0; + + + if(!oldDir || oldDir->variantType != YAFFS_OBJECT_TYPE_DIRECTORY) + YBUG(); + if(!newDir || newDir->variantType != YAFFS_OBJECT_TYPE_DIRECTORY) + YBUG(); + +#ifdef CONFIG_YAFFS_CASE_INSENSITIVE + /* Special case for case insemsitive systems (eg. WinCE). + * While look-up is case insensitive, the name isn't. + * Therefore we might want to change x.txt to X.txt + */ + if (oldDir == newDir && yaffs_strcmp(oldName, newName) == 0) { + force = 1; + } +#endif + + else if (yaffs_strlen(newName) > YAFFS_MAX_NAME_LENGTH) + /* ENAMETOOLONG */ + return YAFFS_FAIL; + + obj = yaffs_FindObjectByName(oldDir, oldName); + + if (obj && obj->renameAllowed) { + + /* Now do the handling for an existing target, if there is one */ + + existingTarget = yaffs_FindObjectByName(newDir, newName); + if (existingTarget && + existingTarget->variantType == YAFFS_OBJECT_TYPE_DIRECTORY && + !ylist_empty(&existingTarget->variant.directoryVariant.children)) { + /* There is a target that is a non-empty directory, so we fail */ + return YAFFS_FAIL; /* EEXIST or ENOTEMPTY */ + } else if (existingTarget && existingTarget != obj) { + /* Nuke the target first, using shadowing, + * but only if it isn't the same object + */ + yaffs_ChangeObjectName(obj, newDir, newName, force, + existingTarget->objectId); + yaffs_UnlinkObject(existingTarget); + } + + return yaffs_ChangeObjectName(obj, newDir, newName, 1, 0); + } + return YAFFS_FAIL; +} + +/*------------------------- Block Management and Page Allocation ----------------*/ + +static int yaffs_InitialiseBlocks(yaffs_Device * dev) +{ + int nBlocks = dev->internalEndBlock - dev->internalStartBlock + 1; + + dev->blockInfo = NULL; + dev->chunkBits = NULL; + + dev->allocationBlock = -1; /* force it to get a new one */ + + /* If the first allocation strategy fails, thry the alternate one */ + dev->blockInfo = YMALLOC(nBlocks * sizeof(yaffs_BlockInfo)); + if(!dev->blockInfo){ + dev->blockInfo = YMALLOC_ALT(nBlocks * sizeof(yaffs_BlockInfo)); + dev->blockInfoAlt = 1; + } + else + dev->blockInfoAlt = 0; + + if(dev->blockInfo){ + + /* Set up dynamic blockinfo stuff. */ + dev->chunkBitmapStride = (dev->nChunksPerBlock + 7) / 8; /* round up bytes */ + dev->chunkBits = YMALLOC(dev->chunkBitmapStride * nBlocks); + if(!dev->chunkBits){ + dev->chunkBits = YMALLOC_ALT(dev->chunkBitmapStride * nBlocks); + dev->chunkBitsAlt = 1; + } + else + dev->chunkBitsAlt = 0; + } + + if (dev->blockInfo && dev->chunkBits) { + memset(dev->blockInfo, 0, nBlocks * sizeof(yaffs_BlockInfo)); + memset(dev->chunkBits, 0, dev->chunkBitmapStride * nBlocks); + return YAFFS_OK; + } + + return YAFFS_FAIL; + +} + +static void yaffs_DeinitialiseBlocks(yaffs_Device * dev) +{ + if(dev->blockInfoAlt && dev->blockInfo) + YFREE_ALT(dev->blockInfo); + else if(dev->blockInfo) + YFREE(dev->blockInfo); + + dev->blockInfoAlt = 0; + + dev->blockInfo = NULL; + + if(dev->chunkBitsAlt && dev->chunkBits) + YFREE_ALT(dev->chunkBits); + else if(dev->chunkBits) + YFREE(dev->chunkBits); + dev->chunkBitsAlt = 0; + dev->chunkBits = NULL; +} + +static int yaffs_BlockNotDisqualifiedFromGC(yaffs_Device * dev, + yaffs_BlockInfo * bi) +{ + int i; + __u32 seq; + yaffs_BlockInfo *b; + + if (!dev->isYaffs2) + return 1; /* disqualification only applies to yaffs2. */ + + if (!bi->hasShrinkHeader) + return 1; /* can gc */ + + /* Find the oldest dirty sequence number if we don't know it and save it + * so we don't have to keep recomputing it. + */ + if (!dev->oldestDirtySequence) { + seq = dev->sequenceNumber; + + for (i = dev->internalStartBlock; i <= dev->internalEndBlock; + i++) { + b = yaffs_GetBlockInfo(dev, i); + if (b->blockState == YAFFS_BLOCK_STATE_FULL && + (b->pagesInUse - b->softDeletions) < + dev->nChunksPerBlock && b->sequenceNumber < seq) { + seq = b->sequenceNumber; + } + } + dev->oldestDirtySequence = seq; + } + + /* Can't do gc of this block if there are any blocks older than this one that have + * discarded pages. + */ + return (bi->sequenceNumber <= dev->oldestDirtySequence); + +} + +/* FindDiretiestBlock is used to select the dirtiest block (or close enough) + * for garbage collection. + */ + +static int yaffs_FindBlockForGarbageCollection(yaffs_Device * dev, + int aggressive) +{ + + int b = dev->currentDirtyChecker; + + int i; + int iterations; + int dirtiest = -1; + int pagesInUse = 0; + int prioritised=0; + yaffs_BlockInfo *bi; + int pendingPrioritisedExist = 0; + + /* First let's see if we need to grab a prioritised block */ + if(dev->hasPendingPrioritisedGCs){ + for(i = dev->internalStartBlock; i < dev->internalEndBlock && !prioritised; i++){ + + bi = yaffs_GetBlockInfo(dev, i); + //yaffs_VerifyBlock(dev,bi,i); + + if(bi->gcPrioritise) { + pendingPrioritisedExist = 1; + if(bi->blockState == YAFFS_BLOCK_STATE_FULL && + yaffs_BlockNotDisqualifiedFromGC(dev, bi)){ + pagesInUse = (bi->pagesInUse - bi->softDeletions); + dirtiest = i; + prioritised = 1; + aggressive = 1; /* Fool the non-aggressive skip logiv below */ + } + } + } + + if(!pendingPrioritisedExist) /* None found, so we can clear this */ + dev->hasPendingPrioritisedGCs = 0; + } + + /* If we're doing aggressive GC then we are happy to take a less-dirty block, and + * search harder. + * else (we're doing a leasurely gc), then we only bother to do this if the + * block has only a few pages in use. + */ + + dev->nonAggressiveSkip--; + + if (!aggressive && (dev->nonAggressiveSkip > 0)) { + return -1; + } + + if(!prioritised) + pagesInUse = + (aggressive) ? dev->nChunksPerBlock : YAFFS_PASSIVE_GC_CHUNKS + 1; + + if (aggressive) { + iterations = + dev->internalEndBlock - dev->internalStartBlock + 1; + } else { + iterations = + dev->internalEndBlock - dev->internalStartBlock + 1; + iterations = iterations / 16; + if (iterations > 200) { + iterations = 200; + } + } + + for (i = 0; i <= iterations && pagesInUse > 0 && !prioritised; i++) { + b++; + if (b < dev->internalStartBlock || b > dev->internalEndBlock) { + b = dev->internalStartBlock; + } + + if (b < dev->internalStartBlock || b > dev->internalEndBlock) { + T(YAFFS_TRACE_ERROR, + (TSTR("**>> Block %d is not valid" TENDSTR), b)); + YBUG(); + } + + bi = yaffs_GetBlockInfo(dev, b); + +#if 0 + if (bi->blockState == YAFFS_BLOCK_STATE_CHECKPOINT) { + dirtiest = b; + pagesInUse = 0; + } + else +#endif + + if (bi->blockState == YAFFS_BLOCK_STATE_FULL && + (bi->pagesInUse - bi->softDeletions) < pagesInUse && + yaffs_BlockNotDisqualifiedFromGC(dev, bi)) { + dirtiest = b; + pagesInUse = (bi->pagesInUse - bi->softDeletions); + } + } + + dev->currentDirtyChecker = b; + + if (dirtiest > 0) { + T(YAFFS_TRACE_GC, + (TSTR("GC Selected block %d with %d free, prioritised:%d" TENDSTR), dirtiest, + dev->nChunksPerBlock - pagesInUse,prioritised)); + } + + dev->oldestDirtySequence = 0; + + if (dirtiest > 0) { + dev->nonAggressiveSkip = 4; + } + + return dirtiest; +} + +static void yaffs_BlockBecameDirty(yaffs_Device * dev, int blockNo) +{ + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev, blockNo); + + int erasedOk = 0; + + /* If the block is still healthy erase it and mark as clean. + * If the block has had a data failure, then retire it. + */ + + T(YAFFS_TRACE_GC | YAFFS_TRACE_ERASE, + (TSTR("yaffs_BlockBecameDirty block %d state %d %s"TENDSTR), + blockNo, bi->blockState, (bi->needsRetiring) ? "needs retiring" : "")); + + bi->blockState = YAFFS_BLOCK_STATE_DIRTY; + + if (!bi->needsRetiring) { + yaffs_InvalidateCheckpoint(dev); + erasedOk = yaffs_EraseBlockInNAND(dev, blockNo); + if (!erasedOk) { + dev->nErasureFailures++; + T(YAFFS_TRACE_ERROR | YAFFS_TRACE_BAD_BLOCKS, + (TSTR("**>> Erasure failed %d" TENDSTR), blockNo)); + } + } + + if (erasedOk && + ((yaffs_traceMask & YAFFS_TRACE_ERASE) || !yaffs_SkipVerification(dev))) { + int i; + for (i = 0; i < dev->nChunksPerBlock; i++) { + if (!yaffs_CheckChunkErased + (dev, blockNo * dev->nChunksPerBlock + i)) { + T(YAFFS_TRACE_ERROR, + (TSTR + (">>Block %d erasure supposedly OK, but chunk %d not erased" + TENDSTR), blockNo, i)); + } + } + } + + if (erasedOk) { + /* Clean it up... */ + bi->blockState = YAFFS_BLOCK_STATE_EMPTY; + dev->nErasedBlocks++; + bi->pagesInUse = 0; + bi->softDeletions = 0; + bi->hasShrinkHeader = 0; + bi->skipErasedCheck = 1; /* This is clean, so no need to check */ + bi->gcPrioritise = 0; + yaffs_ClearChunkBits(dev, blockNo); + + T(YAFFS_TRACE_ERASE, + (TSTR("Erased block %d" TENDSTR), blockNo)); + } else { + dev->nFreeChunks -= dev->nChunksPerBlock; /* We lost a block of free space */ + + yaffs_RetireBlock(dev, blockNo); + T(YAFFS_TRACE_ERROR | YAFFS_TRACE_BAD_BLOCKS, + (TSTR("**>> Block %d retired" TENDSTR), blockNo)); + } +} + +static int yaffs_FindBlockForAllocation(yaffs_Device * dev) +{ + int i; + + yaffs_BlockInfo *bi; + + if (dev->nErasedBlocks < 1) { + /* Hoosterman we've got a problem. + * Can't get space to gc + */ + T(YAFFS_TRACE_ERROR, + (TSTR("yaffs tragedy: no more erased blocks" TENDSTR))); + + return -1; + } + + /* Find an empty block. */ + + for (i = dev->internalStartBlock; i <= dev->internalEndBlock; i++) { + dev->allocationBlockFinder++; + if (dev->allocationBlockFinder < dev->internalStartBlock + || dev->allocationBlockFinder > dev->internalEndBlock) { + dev->allocationBlockFinder = dev->internalStartBlock; + } + + bi = yaffs_GetBlockInfo(dev, dev->allocationBlockFinder); + + if (bi->blockState == YAFFS_BLOCK_STATE_EMPTY) { + bi->blockState = YAFFS_BLOCK_STATE_ALLOCATING; + dev->sequenceNumber++; + bi->sequenceNumber = dev->sequenceNumber; + dev->nErasedBlocks--; + T(YAFFS_TRACE_ALLOCATE, + (TSTR("Allocated block %d, seq %d, %d left" TENDSTR), + dev->allocationBlockFinder, dev->sequenceNumber, + dev->nErasedBlocks)); + return dev->allocationBlockFinder; + } + } + + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("yaffs tragedy: no more erased blocks, but there should have been %d" + TENDSTR), dev->nErasedBlocks)); + + return -1; +} + + + +static int yaffs_CalcCheckpointBlocksRequired(yaffs_Device *dev) +{ + if(!dev->nCheckpointBlocksRequired && + dev->isYaffs2){ + /* Not a valid value so recalculate */ + int nBytes = 0; + int nBlocks; + int devBlocks = (dev->endBlock - dev->startBlock + 1); + int tnodeSize; + + tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8; + + if(tnodeSize < sizeof(yaffs_Tnode)) + tnodeSize = sizeof(yaffs_Tnode); + + nBytes += sizeof(yaffs_CheckpointValidity); + nBytes += sizeof(yaffs_CheckpointDevice); + nBytes += devBlocks * sizeof(yaffs_BlockInfo); + nBytes += devBlocks * dev->chunkBitmapStride; + nBytes += (sizeof(yaffs_CheckpointObject) + sizeof(__u32)) * (dev->nObjectsCreated - dev->nFreeObjects); + nBytes += (tnodeSize + sizeof(__u32)) * (dev->nTnodesCreated - dev->nFreeTnodes); + nBytes += sizeof(yaffs_CheckpointValidity); + nBytes += sizeof(__u32); /* checksum*/ + + /* Round up and add 2 blocks to allow for some bad blocks, so add 3 */ + + nBlocks = (nBytes/(dev->nDataBytesPerChunk * dev->nChunksPerBlock)) + 3; + + dev->nCheckpointBlocksRequired = nBlocks; + } + + return dev->nCheckpointBlocksRequired; +} + +// Check if there's space to allocate... +// Thinks.... do we need top make this ths same as yaffs_GetFreeChunks()? +static int yaffs_CheckSpaceForAllocation(yaffs_Device * dev) +{ + int reservedChunks; + int reservedBlocks = dev->nReservedBlocks; + int checkpointBlocks; + + if(dev->isYaffs2){ + checkpointBlocks = yaffs_CalcCheckpointBlocksRequired(dev) - + dev->blocksInCheckpoint; + if(checkpointBlocks < 0) + checkpointBlocks = 0; + } else { + checkpointBlocks =0; + } + + reservedChunks = ((reservedBlocks + checkpointBlocks) * dev->nChunksPerBlock); + + return (dev->nFreeChunks > reservedChunks); +} + +static int yaffs_AllocateChunk(yaffs_Device * dev, int useReserve, yaffs_BlockInfo **blockUsedPtr) +{ + int retVal; + yaffs_BlockInfo *bi; + + if (dev->allocationBlock < 0) { + /* Get next block to allocate off */ + dev->allocationBlock = yaffs_FindBlockForAllocation(dev); + dev->allocationPage = 0; + } + + if (!useReserve && !yaffs_CheckSpaceForAllocation(dev)) { + /* Not enough space to allocate unless we're allowed to use the reserve. */ + return -1; + } + + if (dev->nErasedBlocks < dev->nReservedBlocks + && dev->allocationPage == 0) { + T(YAFFS_TRACE_ALLOCATE, (TSTR("Allocating reserve" TENDSTR))); + } + + /* Next page please.... */ + if (dev->allocationBlock >= 0) { + bi = yaffs_GetBlockInfo(dev, dev->allocationBlock); + + retVal = (dev->allocationBlock * dev->nChunksPerBlock) + + dev->allocationPage; + bi->pagesInUse++; + yaffs_SetChunkBit(dev, dev->allocationBlock, + dev->allocationPage); + + dev->allocationPage++; + + dev->nFreeChunks--; + + /* If the block is full set the state to full */ + if (dev->allocationPage >= dev->nChunksPerBlock) { + bi->blockState = YAFFS_BLOCK_STATE_FULL; + dev->allocationBlock = -1; + } + + if(blockUsedPtr) + *blockUsedPtr = bi; + + return retVal; + } + + T(YAFFS_TRACE_ERROR, + (TSTR("!!!!!!!!! Allocator out !!!!!!!!!!!!!!!!!" TENDSTR))); + + return -1; +} + +static int yaffs_GetErasedChunks(yaffs_Device * dev) +{ + int n; + + n = dev->nErasedBlocks * dev->nChunksPerBlock; + + if (dev->allocationBlock > 0) { + n += (dev->nChunksPerBlock - dev->allocationPage); + } + + return n; + +} + +static int yaffs_GarbageCollectBlock(yaffs_Device * dev, int block, int wholeBlock) +{ + int oldChunk; + int newChunk; + int markNAND; + int retVal = YAFFS_OK; + int cleanups = 0; + int i; + int isCheckpointBlock; + int matchingChunk; + int maxCopies; + + int chunksBefore = yaffs_GetErasedChunks(dev); + int chunksAfter; + + yaffs_ExtendedTags tags; + + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev, block); + + yaffs_Object *object; + + isCheckpointBlock = (bi->blockState == YAFFS_BLOCK_STATE_CHECKPOINT); + + bi->blockState = YAFFS_BLOCK_STATE_COLLECTING; + + T(YAFFS_TRACE_TRACING, + (TSTR("Collecting block %d, in use %d, shrink %d, wholeBlock %d" TENDSTR), + block, + bi->pagesInUse, + bi->hasShrinkHeader, + wholeBlock)); + + /*yaffs_VerifyFreeChunks(dev); */ + + bi->hasShrinkHeader = 0; /* clear the flag so that the block can erase */ + + /* Take off the number of soft deleted entries because + * they're going to get really deleted during GC. + */ + dev->nFreeChunks -= bi->softDeletions; + + dev->isDoingGC = 1; + + if (isCheckpointBlock || + !yaffs_StillSomeChunkBits(dev, block)) { + T(YAFFS_TRACE_TRACING, + (TSTR + ("Collecting block %d that has no chunks in use" TENDSTR), + block)); + yaffs_BlockBecameDirty(dev, block); + } else { + + __u8 *buffer = yaffs_GetTempBuffer(dev, __LINE__); + + yaffs_VerifyBlock(dev,bi,block); + + maxCopies = (wholeBlock) ? dev->nChunksPerBlock : 10; + oldChunk = block * dev->nChunksPerBlock + dev->gcChunk; + + for ( /* init already done */; + retVal == YAFFS_OK && + dev->gcChunk < dev->nChunksPerBlock && + (bi->blockState == YAFFS_BLOCK_STATE_COLLECTING)&& + maxCopies > 0; + dev->gcChunk++, oldChunk++) { + if (yaffs_CheckChunkBit(dev, block, dev->gcChunk)) { + + /* This page is in use and might need to be copied off */ + + maxCopies--; + + markNAND = 1; + + yaffs_InitialiseTags(&tags); + + yaffs_ReadChunkWithTagsFromNAND(dev, oldChunk, + buffer, &tags); + + object = + yaffs_FindObjectByNumber(dev, + tags.objectId); + + T(YAFFS_TRACE_GC_DETAIL, + (TSTR + ("Collecting chunk in block %d, %d %d %d " TENDSTR), + dev->gcChunk, tags.objectId, tags.chunkId, + tags.byteCount)); + + if(object && !yaffs_SkipVerification(dev)){ + if(tags.chunkId == 0) + matchingChunk = object->hdrChunk; + else if(object->softDeleted) + matchingChunk = oldChunk; /* Defeat the test */ + else + matchingChunk = yaffs_FindChunkInFile(object,tags.chunkId,NULL); + + if(oldChunk != matchingChunk) + T(YAFFS_TRACE_ERROR, + (TSTR("gc: page in gc mismatch: %d %d %d %d"TENDSTR), + oldChunk,matchingChunk,tags.objectId, tags.chunkId)); + + } + + if (!object) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("page %d in gc has no object: %d %d %d " + TENDSTR), oldChunk, + tags.objectId, tags.chunkId, tags.byteCount)); + } + + if (object && + object->deleted && + object->softDeleted && + tags.chunkId != 0) { + /* Data chunk in a soft deleted file, throw it away + * It's a soft deleted data chunk, + * No need to copy this, just forget about it and + * fix up the object. + */ + + object->nDataChunks--; + + if (object->nDataChunks <= 0) { + /* remeber to clean up the object */ + dev->gcCleanupList[cleanups] = + tags.objectId; + cleanups++; + } + markNAND = 0; + } else if (0 + /* Todo object && object->deleted && object->nDataChunks == 0 */ + ) { + /* Deleted object header with no data chunks. + * Can be discarded and the file deleted. + */ + object->hdrChunk = 0; + yaffs_FreeTnode(object->myDev, + object->variant. + fileVariant.top); + object->variant.fileVariant.top = NULL; + yaffs_DoGenericObjectDeletion(object); + + } else if (object) { + /* It's either a data chunk in a live file or + * an ObjectHeader, so we're interested in it. + * NB Need to keep the ObjectHeaders of deleted files + * until the whole file has been deleted off + */ + tags.serialNumber++; + + dev->nGCCopies++; + + if (tags.chunkId == 0) { + /* It is an object Id, + * We need to nuke the shrinkheader flags first + * We no longer want the shrinkHeader flag since its work is done + * and if it is left in place it will mess up scanning. + * Also, clear out any shadowing stuff + */ + + yaffs_ObjectHeader *oh; + oh = (yaffs_ObjectHeader *)buffer; + oh->isShrink = 0; + tags.extraShadows = 0; + tags.extraIsShrinkHeader = 0; + + yaffs_VerifyObjectHeader(object,oh,&tags,1); + } + + newChunk = + yaffs_WriteNewChunkWithTagsToNAND(dev, buffer, &tags, 1); + + if (newChunk < 0) { + retVal = YAFFS_FAIL; + } else { + + /* Ok, now fix up the Tnodes etc. */ + + if (tags.chunkId == 0) { + /* It's a header */ + object->hdrChunk = newChunk; + object->serial = tags.serialNumber; + } else { + /* It's a data chunk */ + yaffs_PutChunkIntoFile + (object, + tags.chunkId, + newChunk, 0); + } + } + } + + if(retVal == YAFFS_OK) + yaffs_DeleteChunk(dev, oldChunk, markNAND, __LINE__); + + } + } + + yaffs_ReleaseTempBuffer(dev, buffer, __LINE__); + + + + /* Do any required cleanups */ + for (i = 0; i < cleanups; i++) { + /* Time to delete the file too */ + object = + yaffs_FindObjectByNumber(dev, + dev->gcCleanupList[i]); + if (object) { + yaffs_FreeTnode(dev, + object->variant.fileVariant. + top); + object->variant.fileVariant.top = NULL; + T(YAFFS_TRACE_GC, + (TSTR + ("yaffs: About to finally delete object %d" + TENDSTR), object->objectId)); + yaffs_DoGenericObjectDeletion(object); + object->myDev->nDeletedFiles--; + } + + } + + } + + yaffs_VerifyCollectedBlock(dev,bi,block); + + if (chunksBefore >= (chunksAfter = yaffs_GetErasedChunks(dev))) { + T(YAFFS_TRACE_GC, + (TSTR + ("gc did not increase free chunks before %d after %d" + TENDSTR), chunksBefore, chunksAfter)); + } + + /* If the gc completed then clear the current gcBlock so that we find another. */ + if(bi->blockState != YAFFS_BLOCK_STATE_COLLECTING){ + dev->gcBlock = -1; + dev->gcChunk = 0; + } + + dev->isDoingGC = 0; + + return retVal; +} + +/* New garbage collector + * If we're very low on erased blocks then we do aggressive garbage collection + * otherwise we do "leasurely" garbage collection. + * Aggressive gc looks further (whole array) and will accept less dirty blocks. + * Passive gc only inspects smaller areas and will only accept more dirty blocks. + * + * The idea is to help clear out space in a more spread-out manner. + * Dunno if it really does anything useful. + */ +static int yaffs_CheckGarbageCollection(yaffs_Device * dev) +{ + int block; + int aggressive; + int gcOk = YAFFS_OK; + int maxTries = 0; + + int checkpointBlockAdjust; + + if (dev->isDoingGC) { + /* Bail out so we don't get recursive gc */ + return YAFFS_OK; + } + + /* This loop should pass the first time. + * We'll only see looping here if the erase of the collected block fails. + */ + + do { + maxTries++; + + checkpointBlockAdjust = yaffs_CalcCheckpointBlocksRequired(dev) - dev->blocksInCheckpoint; + if(checkpointBlockAdjust < 0) + checkpointBlockAdjust = 0; + + if (dev->nErasedBlocks < (dev->nReservedBlocks + checkpointBlockAdjust + 2)) { + /* We need a block soon...*/ + aggressive = 1; + } else { + /* We're in no hurry */ + aggressive = 0; + } + + if(dev->gcBlock <= 0){ + dev->gcBlock = yaffs_FindBlockForGarbageCollection(dev, aggressive); + dev->gcChunk = 0; + } + + block = dev->gcBlock; + + if (block > 0) { + dev->garbageCollections++; + if (!aggressive) { + dev->passiveGarbageCollections++; + } + + T(YAFFS_TRACE_GC, + (TSTR + ("yaffs: GC erasedBlocks %d aggressive %d" TENDSTR), + dev->nErasedBlocks, aggressive)); + + gcOk = yaffs_GarbageCollectBlock(dev,block,aggressive); + } + + if (dev->nErasedBlocks < (dev->nReservedBlocks) && block > 0) { + T(YAFFS_TRACE_GC, + (TSTR + ("yaffs: GC !!!no reclaim!!! erasedBlocks %d after try %d block %d" + TENDSTR), dev->nErasedBlocks, maxTries, block)); + } + } while ((dev->nErasedBlocks < dev->nReservedBlocks) && + (block > 0) && + (maxTries < 2)); + + return aggressive ? gcOk : YAFFS_OK; +} + +/*------------------------- TAGS --------------------------------*/ + +static int yaffs_TagsMatch(const yaffs_ExtendedTags * tags, int objectId, + int chunkInObject) +{ + return (tags->chunkId == chunkInObject && + tags->objectId == objectId && !tags->chunkDeleted) ? 1 : 0; + +} + + +/*-------------------- Data file manipulation -----------------*/ + +static int yaffs_FindChunkInFile(yaffs_Object * in, int chunkInInode, + yaffs_ExtendedTags * tags) +{ + /*Get the Tnode, then get the level 0 offset chunk offset */ + yaffs_Tnode *tn; + int theChunk = -1; + yaffs_ExtendedTags localTags; + int retVal = -1; + + yaffs_Device *dev = in->myDev; + + if (!tags) { + /* Passed a NULL, so use our own tags space */ + tags = &localTags; + } + + tn = yaffs_FindLevel0Tnode(dev, &in->variant.fileVariant, chunkInInode); + + if (tn) { + theChunk = yaffs_GetChunkGroupBase(dev,tn,chunkInInode); + + retVal = + yaffs_FindChunkInGroup(dev, theChunk, tags, in->objectId, + chunkInInode); + } + return retVal; +} + +static int yaffs_FindAndDeleteChunkInFile(yaffs_Object * in, int chunkInInode, + yaffs_ExtendedTags * tags) +{ + /* Get the Tnode, then get the level 0 offset chunk offset */ + yaffs_Tnode *tn; + int theChunk = -1; + yaffs_ExtendedTags localTags; + + yaffs_Device *dev = in->myDev; + int retVal = -1; + + if (!tags) { + /* Passed a NULL, so use our own tags space */ + tags = &localTags; + } + + tn = yaffs_FindLevel0Tnode(dev, &in->variant.fileVariant, chunkInInode); + + if (tn) { + + theChunk = yaffs_GetChunkGroupBase(dev,tn,chunkInInode); + + retVal = + yaffs_FindChunkInGroup(dev, theChunk, tags, in->objectId, + chunkInInode); + + /* Delete the entry in the filestructure (if found) */ + if (retVal != -1) { + yaffs_PutLevel0Tnode(dev,tn,chunkInInode,0); + } + } else { + /*T(("No level 0 found for %d\n", chunkInInode)); */ + } + + if (retVal == -1) { + /* T(("Could not find %d to delete\n",chunkInInode)); */ + } + return retVal; +} + +#ifdef YAFFS_PARANOID + +static int yaffs_CheckFileSanity(yaffs_Object * in) +{ + int chunk; + int nChunks; + int fSize; + int failed = 0; + int objId; + yaffs_Tnode *tn; + yaffs_Tags localTags; + yaffs_Tags *tags = &localTags; + int theChunk; + int chunkDeleted; + + if (in->variantType != YAFFS_OBJECT_TYPE_FILE) { + /* T(("Object not a file\n")); */ + return YAFFS_FAIL; + } + + objId = in->objectId; + fSize = in->variant.fileVariant.fileSize; + nChunks = + (fSize + in->myDev->nDataBytesPerChunk - 1) / in->myDev->nDataBytesPerChunk; + + for (chunk = 1; chunk <= nChunks; chunk++) { + tn = yaffs_FindLevel0Tnode(in->myDev, &in->variant.fileVariant, + chunk); + + if (tn) { + + theChunk = yaffs_GetChunkGroupBase(dev,tn,chunk); + + if (yaffs_CheckChunkBits + (dev, theChunk / dev->nChunksPerBlock, + theChunk % dev->nChunksPerBlock)) { + + yaffs_ReadChunkTagsFromNAND(in->myDev, theChunk, + tags, + &chunkDeleted); + if (yaffs_TagsMatch + (tags, in->objectId, chunk, chunkDeleted)) { + /* found it; */ + + } + } else { + + failed = 1; + } + + } else { + /* T(("No level 0 found for %d\n", chunk)); */ + } + } + + return failed ? YAFFS_FAIL : YAFFS_OK; +} + +#endif + +static int yaffs_PutChunkIntoFile(yaffs_Object * in, int chunkInInode, + int chunkInNAND, int inScan) +{ + /* NB inScan is zero unless scanning. + * For forward scanning, inScan is > 0; + * for backward scanning inScan is < 0 + */ + + yaffs_Tnode *tn; + yaffs_Device *dev = in->myDev; + int existingChunk; + yaffs_ExtendedTags existingTags; + yaffs_ExtendedTags newTags; + unsigned existingSerial, newSerial; + + if (in->variantType != YAFFS_OBJECT_TYPE_FILE) { + /* Just ignore an attempt at putting a chunk into a non-file during scanning + * If it is not during Scanning then something went wrong! + */ + if (!inScan) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("yaffs tragedy:attempt to put data chunk into a non-file" + TENDSTR))); + YBUG(); + } + + yaffs_DeleteChunk(dev, chunkInNAND, 1, __LINE__); + return YAFFS_OK; + } + + tn = yaffs_AddOrFindLevel0Tnode(dev, + &in->variant.fileVariant, + chunkInInode, + NULL); + if (!tn) { + return YAFFS_FAIL; + } + + existingChunk = yaffs_GetChunkGroupBase(dev,tn,chunkInInode); + + if (inScan != 0) { + /* If we're scanning then we need to test for duplicates + * NB This does not need to be efficient since it should only ever + * happen when the power fails during a write, then only one + * chunk should ever be affected. + * + * Correction for YAFFS2: This could happen quite a lot and we need to think about efficiency! TODO + * Update: For backward scanning we don't need to re-read tags so this is quite cheap. + */ + + if (existingChunk > 0) { + /* NB Right now existing chunk will not be real chunkId if the device >= 32MB + * thus we have to do a FindChunkInFile to get the real chunk id. + * + * We have a duplicate now we need to decide which one to use: + * + * Backwards scanning YAFFS2: The old one is what we use, dump the new one. + * Forward scanning YAFFS2: The new one is what we use, dump the old one. + * YAFFS1: Get both sets of tags and compare serial numbers. + */ + + if (inScan > 0) { + /* Only do this for forward scanning */ + yaffs_ReadChunkWithTagsFromNAND(dev, + chunkInNAND, + NULL, &newTags); + + /* Do a proper find */ + existingChunk = + yaffs_FindChunkInFile(in, chunkInInode, + &existingTags); + } + + if (existingChunk <= 0) { + /*Hoosterman - how did this happen? */ + + T(YAFFS_TRACE_ERROR, + (TSTR + ("yaffs tragedy: existing chunk < 0 in scan" + TENDSTR))); + + } + + /* NB The deleted flags should be false, otherwise the chunks will + * not be loaded during a scan + */ + + if(inScan > 0) { + newSerial = newTags.serialNumber; + existingSerial = existingTags.serialNumber; + } + + if ((inScan > 0) && + (in->myDev->isYaffs2 || + existingChunk <= 0 || + ((existingSerial + 1) & 3) == newSerial)) { + /* Forward scanning. + * Use new + * Delete the old one and drop through to update the tnode + */ + yaffs_DeleteChunk(dev, existingChunk, 1, + __LINE__); + } else { + /* Backward scanning or we want to use the existing one + * Use existing. + * Delete the new one and return early so that the tnode isn't changed + */ + yaffs_DeleteChunk(dev, chunkInNAND, 1, + __LINE__); + return YAFFS_OK; + } + } + + } + + if (existingChunk == 0) { + in->nDataChunks++; + } + + yaffs_PutLevel0Tnode(dev,tn,chunkInInode,chunkInNAND); + + return YAFFS_OK; +} + +static int yaffs_ReadChunkDataFromObject(yaffs_Object * in, int chunkInInode, + __u8 * buffer) +{ + int chunkInNAND = yaffs_FindChunkInFile(in, chunkInInode, NULL); + + if (chunkInNAND >= 0) { + return yaffs_ReadChunkWithTagsFromNAND(in->myDev, chunkInNAND, + buffer,NULL); + } else { + T(YAFFS_TRACE_NANDACCESS, + (TSTR("Chunk %d not found zero instead" TENDSTR), + chunkInNAND)); + /* get sane (zero) data if you read a hole */ + memset(buffer, 0, in->myDev->nDataBytesPerChunk); + return 0; + } + +} + +void yaffs_DeleteChunk(yaffs_Device * dev, int chunkId, int markNAND, int lyn) +{ + int block; + int page; + yaffs_ExtendedTags tags; + yaffs_BlockInfo *bi; + + if (chunkId <= 0) + return; + + + dev->nDeletions++; + block = chunkId / dev->nChunksPerBlock; + page = chunkId % dev->nChunksPerBlock; + + + if(!yaffs_CheckChunkBit(dev,block,page)) + T(YAFFS_TRACE_VERIFY, + (TSTR("Deleting invalid chunk %d"TENDSTR), + chunkId)); + + bi = yaffs_GetBlockInfo(dev, block); + + T(YAFFS_TRACE_DELETION, + (TSTR("line %d delete of chunk %d" TENDSTR), lyn, chunkId)); + + if (markNAND && + bi->blockState != YAFFS_BLOCK_STATE_COLLECTING && !dev->isYaffs2) { + + yaffs_InitialiseTags(&tags); + + tags.chunkDeleted = 1; + + yaffs_WriteChunkWithTagsToNAND(dev, chunkId, NULL, &tags); + yaffs_HandleUpdateChunk(dev, chunkId, &tags); + } else { + dev->nUnmarkedDeletions++; + } + + /* Pull out of the management area. + * If the whole block became dirty, this will kick off an erasure. + */ + if (bi->blockState == YAFFS_BLOCK_STATE_ALLOCATING || + bi->blockState == YAFFS_BLOCK_STATE_FULL || + bi->blockState == YAFFS_BLOCK_STATE_NEEDS_SCANNING || + bi->blockState == YAFFS_BLOCK_STATE_COLLECTING) { + dev->nFreeChunks++; + + yaffs_ClearChunkBit(dev, block, page); + + bi->pagesInUse--; + + if (bi->pagesInUse == 0 && + !bi->hasShrinkHeader && + bi->blockState != YAFFS_BLOCK_STATE_ALLOCATING && + bi->blockState != YAFFS_BLOCK_STATE_NEEDS_SCANNING) { + yaffs_BlockBecameDirty(dev, block); + } + + } else { + /* T(("Bad news deleting chunk %d\n",chunkId)); */ + } + +} + +static int yaffs_WriteChunkDataToObject(yaffs_Object * in, int chunkInInode, + const __u8 * buffer, int nBytes, + int useReserve) +{ + /* Find old chunk Need to do this to get serial number + * Write new one and patch into tree. + * Invalidate old tags. + */ + + int prevChunkId; + yaffs_ExtendedTags prevTags; + + int newChunkId; + yaffs_ExtendedTags newTags; + + yaffs_Device *dev = in->myDev; + + yaffs_CheckGarbageCollection(dev); + + /* Get the previous chunk at this location in the file if it exists */ + prevChunkId = yaffs_FindChunkInFile(in, chunkInInode, &prevTags); + + /* Set up new tags */ + yaffs_InitialiseTags(&newTags); + + newTags.chunkId = chunkInInode; + newTags.objectId = in->objectId; + newTags.serialNumber = + (prevChunkId >= 0) ? prevTags.serialNumber + 1 : 1; + newTags.byteCount = nBytes; + + if(nBytes < 1 || nBytes > dev->totalBytesPerChunk){ + T(YAFFS_TRACE_ERROR, + (TSTR("Writing %d bytes to chunk!!!!!!!!!" TENDSTR), nBytes)); + while(1){} + } + + + + newChunkId = + yaffs_WriteNewChunkWithTagsToNAND(dev, buffer, &newTags, + useReserve); + + if (newChunkId >= 0) { + yaffs_PutChunkIntoFile(in, chunkInInode, newChunkId, 0); + + if (prevChunkId >= 0) { + yaffs_DeleteChunk(dev, prevChunkId, 1, __LINE__); + + } + + yaffs_CheckFileSanity(in); + } + return newChunkId; + +} + +/* UpdateObjectHeader updates the header on NAND for an object. + * If name is not NULL, then that new name is used. + */ +int yaffs_UpdateObjectHeader(yaffs_Object * in, const YCHAR * name, int force, + int isShrink, int shadows) +{ + + yaffs_BlockInfo *bi; + + yaffs_Device *dev = in->myDev; + + int prevChunkId; + int retVal = 0; + int result = 0; + + int newChunkId; + yaffs_ExtendedTags newTags; + yaffs_ExtendedTags oldTags; + + __u8 *buffer = NULL; + YCHAR oldName[YAFFS_MAX_NAME_LENGTH + 1]; + + yaffs_ObjectHeader *oh = NULL; + + yaffs_strcpy(oldName,_Y("silly old name")); + + + if (!in->fake || + in == dev->rootDir || /* The rootDir should also be saved */ + force) { + + yaffs_CheckGarbageCollection(dev); + yaffs_CheckObjectDetailsLoaded(in); + + buffer = yaffs_GetTempBuffer(in->myDev, __LINE__); + oh = (yaffs_ObjectHeader *) buffer; + + prevChunkId = in->hdrChunk; + + if (prevChunkId > 0) { + result = yaffs_ReadChunkWithTagsFromNAND(dev, prevChunkId, + buffer, &oldTags); + + yaffs_VerifyObjectHeader(in,oh,&oldTags,0); + + memcpy(oldName, oh->name, sizeof(oh->name)); + } + + memset(buffer, 0xFF, dev->nDataBytesPerChunk); + + oh->type = in->variantType; + oh->yst_mode = in->yst_mode; + oh->shadowsObject = oh->inbandShadowsObject = shadows; + +#ifdef CONFIG_YAFFS_WINCE + oh->win_atime[0] = in->win_atime[0]; + oh->win_ctime[0] = in->win_ctime[0]; + oh->win_mtime[0] = in->win_mtime[0]; + oh->win_atime[1] = in->win_atime[1]; + oh->win_ctime[1] = in->win_ctime[1]; + oh->win_mtime[1] = in->win_mtime[1]; +#else + oh->yst_uid = in->yst_uid; + oh->yst_gid = in->yst_gid; + oh->yst_atime = in->yst_atime; + oh->yst_mtime = in->yst_mtime; + oh->yst_ctime = in->yst_ctime; + oh->yst_rdev = in->yst_rdev; +#endif + if (in->parent) { + oh->parentObjectId = in->parent->objectId; + } else { + oh->parentObjectId = 0; + } + + if (name && *name) { + memset(oh->name, 0, sizeof(oh->name)); + yaffs_strncpy(oh->name, name, YAFFS_MAX_NAME_LENGTH); + } else if (prevChunkId>=0) { + memcpy(oh->name, oldName, sizeof(oh->name)); + } else { + memset(oh->name, 0, sizeof(oh->name)); + } + + oh->isShrink = isShrink; + + switch (in->variantType) { + case YAFFS_OBJECT_TYPE_UNKNOWN: + /* Should not happen */ + break; + case YAFFS_OBJECT_TYPE_FILE: + oh->fileSize = + (oh->parentObjectId == YAFFS_OBJECTID_DELETED + || oh->parentObjectId == + YAFFS_OBJECTID_UNLINKED) ? 0 : in->variant. + fileVariant.fileSize; + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + oh->equivalentObjectId = + in->variant.hardLinkVariant.equivalentObjectId; + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + /* Do nothing */ + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + /* Do nothing */ + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + yaffs_strncpy(oh->alias, + in->variant.symLinkVariant.alias, + YAFFS_MAX_ALIAS_LENGTH); + oh->alias[YAFFS_MAX_ALIAS_LENGTH] = 0; + break; + } + + /* Tags */ + yaffs_InitialiseTags(&newTags); + in->serial++; + newTags.chunkId = 0; + newTags.objectId = in->objectId; + newTags.serialNumber = in->serial; + + /* Add extra info for file header */ + + newTags.extraHeaderInfoAvailable = 1; + newTags.extraParentObjectId = oh->parentObjectId; + newTags.extraFileLength = oh->fileSize; + newTags.extraIsShrinkHeader = oh->isShrink; + newTags.extraEquivalentObjectId = oh->equivalentObjectId; + newTags.extraShadows = (oh->shadowsObject > 0) ? 1 : 0; + newTags.extraObjectType = in->variantType; + + yaffs_VerifyObjectHeader(in,oh,&newTags,1); + + /* Create new chunk in NAND */ + newChunkId = + yaffs_WriteNewChunkWithTagsToNAND(dev, buffer, &newTags, + (prevChunkId >= 0) ? 1 : 0); + + if (newChunkId >= 0) { + + in->hdrChunk = newChunkId; + + if (prevChunkId >= 0) { + yaffs_DeleteChunk(dev, prevChunkId, 1, + __LINE__); + } + + if(!yaffs_ObjectHasCachedWriteData(in)) + in->dirty = 0; + + /* If this was a shrink, then mark the block that the chunk lives on */ + if (isShrink) { + bi = yaffs_GetBlockInfo(in->myDev, + newChunkId /in->myDev-> nChunksPerBlock); + bi->hasShrinkHeader = 1; + } + + } + + retVal = newChunkId; + + } + + if (buffer) + yaffs_ReleaseTempBuffer(dev, buffer, __LINE__); + + return retVal; +} + +/*------------------------ Short Operations Cache ---------------------------------------- + * In many situations where there is no high level buffering (eg WinCE) a lot of + * reads might be short sequential reads, and a lot of writes may be short + * sequential writes. eg. scanning/writing a jpeg file. + * In these cases, a short read/write cache can provide a huge perfomance benefit + * with dumb-as-a-rock code. + * In Linux, the page cache provides read buffering aand the short op cache provides write + * buffering. + * + * There are a limited number (~10) of cache chunks per device so that we don't + * need a very intelligent search. + */ + +static int yaffs_ObjectHasCachedWriteData(yaffs_Object *obj) +{ + yaffs_Device *dev = obj->myDev; + int i; + yaffs_ChunkCache *cache; + int nCaches = obj->myDev->nShortOpCaches; + + for(i = 0; i < nCaches; i++){ + cache = &dev->srCache[i]; + if (cache->object == obj && + cache->dirty) + return 1; + } + + return 0; +} + + +static void yaffs_FlushFilesChunkCache(yaffs_Object * obj) +{ + yaffs_Device *dev = obj->myDev; + int lowest = -99; /* Stop compiler whining. */ + int i; + yaffs_ChunkCache *cache; + int chunkWritten = 0; + int nCaches = obj->myDev->nShortOpCaches; + + if (nCaches > 0) { + do { + cache = NULL; + + /* Find the dirty cache for this object with the lowest chunk id. */ + for (i = 0; i < nCaches; i++) { + if (dev->srCache[i].object == obj && + dev->srCache[i].dirty) { + if (!cache + || dev->srCache[i].chunkId < + lowest) { + cache = &dev->srCache[i]; + lowest = cache->chunkId; + } + } + } + + if (cache && !cache->locked) { + /* Write it out and free it up */ + + chunkWritten = + yaffs_WriteChunkDataToObject(cache->object, + cache->chunkId, + cache->data, + cache->nBytes, + 1); + cache->dirty = 0; + cache->object = NULL; + } + + } while (cache && chunkWritten > 0); + + if (cache) { + /* Hoosterman, disk full while writing cache out. */ + T(YAFFS_TRACE_ERROR, + (TSTR("yaffs tragedy: no space during cache write" TENDSTR))); + + } + } + +} + +/*yaffs_FlushEntireDeviceCache(dev) + * + * + */ + +void yaffs_FlushEntireDeviceCache(yaffs_Device *dev) +{ + yaffs_Object *obj; + int nCaches = dev->nShortOpCaches; + int i; + + /* Find a dirty object in the cache and flush it... + * until there are no further dirty objects. + */ + do { + obj = NULL; + for( i = 0; i < nCaches && !obj; i++) { + if (dev->srCache[i].object && + dev->srCache[i].dirty) + obj = dev->srCache[i].object; + + } + if(obj) + yaffs_FlushFilesChunkCache(obj); + + } while(obj); + +} + + +/* Grab us a cache chunk for use. + * First look for an empty one. + * Then look for the least recently used non-dirty one. + * Then look for the least recently used dirty one...., flush and look again. + */ +static yaffs_ChunkCache *yaffs_GrabChunkCacheWorker(yaffs_Device * dev) +{ + int i; + + if (dev->nShortOpCaches > 0) { + for (i = 0; i < dev->nShortOpCaches; i++) { + if (!dev->srCache[i].object) + return &dev->srCache[i]; + } + } + + return NULL; +} + +static yaffs_ChunkCache *yaffs_GrabChunkCache(yaffs_Device * dev) +{ + yaffs_ChunkCache *cache; + yaffs_Object *theObj; + int usage; + int i; + int pushout; + + if (dev->nShortOpCaches > 0) { + /* Try find a non-dirty one... */ + + cache = yaffs_GrabChunkCacheWorker(dev); + + if (!cache) { + /* They were all dirty, find the last recently used object and flush + * its cache, then find again. + * NB what's here is not very accurate, we actually flush the object + * the last recently used page. + */ + + /* With locking we can't assume we can use entry zero */ + + theObj = NULL; + usage = -1; + cache = NULL; + pushout = -1; + + for (i = 0; i < dev->nShortOpCaches; i++) { + if (dev->srCache[i].object && + !dev->srCache[i].locked && + (dev->srCache[i].lastUse < usage || !cache)) + { + usage = dev->srCache[i].lastUse; + theObj = dev->srCache[i].object; + cache = &dev->srCache[i]; + pushout = i; + } + } + + if (!cache || cache->dirty) { + /* Flush and try again */ + yaffs_FlushFilesChunkCache(theObj); + cache = yaffs_GrabChunkCacheWorker(dev); + } + + } + return cache; + } else + return NULL; + +} + +/* Find a cached chunk */ +static yaffs_ChunkCache *yaffs_FindChunkCache(const yaffs_Object * obj, + int chunkId) +{ + yaffs_Device *dev = obj->myDev; + int i; + if (dev->nShortOpCaches > 0) { + for (i = 0; i < dev->nShortOpCaches; i++) { + if (dev->srCache[i].object == obj && + dev->srCache[i].chunkId == chunkId) { + dev->cacheHits++; + + return &dev->srCache[i]; + } + } + } + return NULL; +} + +/* Mark the chunk for the least recently used algorithym */ +static void yaffs_UseChunkCache(yaffs_Device * dev, yaffs_ChunkCache * cache, + int isAWrite) +{ + + if (dev->nShortOpCaches > 0) { + if (dev->srLastUse < 0 || dev->srLastUse > 100000000) { + /* Reset the cache usages */ + int i; + for (i = 1; i < dev->nShortOpCaches; i++) { + dev->srCache[i].lastUse = 0; + } + dev->srLastUse = 0; + } + + dev->srLastUse++; + + cache->lastUse = dev->srLastUse; + + if (isAWrite) { + cache->dirty = 1; + } + } +} + +/* Invalidate a single cache page. + * Do this when a whole page gets written, + * ie the short cache for this page is no longer valid. + */ +static void yaffs_InvalidateChunkCache(yaffs_Object * object, int chunkId) +{ + if (object->myDev->nShortOpCaches > 0) { + yaffs_ChunkCache *cache = yaffs_FindChunkCache(object, chunkId); + + if (cache) { + cache->object = NULL; + } + } +} + +/* Invalidate all the cache pages associated with this object + * Do this whenever ther file is deleted or resized. + */ +static void yaffs_InvalidateWholeChunkCache(yaffs_Object * in) +{ + int i; + yaffs_Device *dev = in->myDev; + + if (dev->nShortOpCaches > 0) { + /* Invalidate it. */ + for (i = 0; i < dev->nShortOpCaches; i++) { + if (dev->srCache[i].object == in) { + dev->srCache[i].object = NULL; + } + } + } +} + +/*--------------------- Checkpointing --------------------*/ + + +static int yaffs_WriteCheckpointValidityMarker(yaffs_Device *dev,int head) +{ + yaffs_CheckpointValidity cp; + + memset(&cp,0,sizeof(cp)); + + cp.structType = sizeof(cp); + cp.magic = YAFFS_MAGIC; + cp.version = YAFFS_CHECKPOINT_VERSION; + cp.head = (head) ? 1 : 0; + + return (yaffs_CheckpointWrite(dev,&cp,sizeof(cp)) == sizeof(cp))? + 1 : 0; +} + +static int yaffs_ReadCheckpointValidityMarker(yaffs_Device *dev, int head) +{ + yaffs_CheckpointValidity cp; + int ok; + + ok = (yaffs_CheckpointRead(dev,&cp,sizeof(cp)) == sizeof(cp)); + + if(ok) + ok = (cp.structType == sizeof(cp)) && + (cp.magic == YAFFS_MAGIC) && + (cp.version == YAFFS_CHECKPOINT_VERSION) && + (cp.head == ((head) ? 1 : 0)); + return ok ? 1 : 0; +} + +static void yaffs_DeviceToCheckpointDevice(yaffs_CheckpointDevice *cp, + yaffs_Device *dev) +{ + cp->nErasedBlocks = dev->nErasedBlocks; + cp->allocationBlock = dev->allocationBlock; + cp->allocationPage = dev->allocationPage; + cp->nFreeChunks = dev->nFreeChunks; + + cp->nDeletedFiles = dev->nDeletedFiles; + cp->nUnlinkedFiles = dev->nUnlinkedFiles; + cp->nBackgroundDeletions = dev->nBackgroundDeletions; + cp->sequenceNumber = dev->sequenceNumber; + cp->oldestDirtySequence = dev->oldestDirtySequence; + +} + +static void yaffs_CheckpointDeviceToDevice(yaffs_Device *dev, + yaffs_CheckpointDevice *cp) +{ + dev->nErasedBlocks = cp->nErasedBlocks; + dev->allocationBlock = cp->allocationBlock; + dev->allocationPage = cp->allocationPage; + dev->nFreeChunks = cp->nFreeChunks; + + dev->nDeletedFiles = cp->nDeletedFiles; + dev->nUnlinkedFiles = cp->nUnlinkedFiles; + dev->nBackgroundDeletions = cp->nBackgroundDeletions; + dev->sequenceNumber = cp->sequenceNumber; + dev->oldestDirtySequence = cp->oldestDirtySequence; +} + + +static int yaffs_WriteCheckpointDevice(yaffs_Device *dev) +{ + yaffs_CheckpointDevice cp; + __u32 nBytes; + __u32 nBlocks = (dev->internalEndBlock - dev->internalStartBlock + 1); + + int ok; + + /* Write device runtime values*/ + yaffs_DeviceToCheckpointDevice(&cp,dev); + cp.structType = sizeof(cp); + + ok = (yaffs_CheckpointWrite(dev,&cp,sizeof(cp)) == sizeof(cp)); + + /* Write block info */ + if(ok) { + nBytes = nBlocks * sizeof(yaffs_BlockInfo); + ok = (yaffs_CheckpointWrite(dev,dev->blockInfo,nBytes) == nBytes); + } + + /* Write chunk bits */ + if(ok) { + nBytes = nBlocks * dev->chunkBitmapStride; + ok = (yaffs_CheckpointWrite(dev,dev->chunkBits,nBytes) == nBytes); + } + return ok ? 1 : 0; + +} + +static int yaffs_ReadCheckpointDevice(yaffs_Device *dev) +{ + yaffs_CheckpointDevice cp; + __u32 nBytes; + __u32 nBlocks = (dev->internalEndBlock - dev->internalStartBlock + 1); + + int ok; + + ok = (yaffs_CheckpointRead(dev,&cp,sizeof(cp)) == sizeof(cp)); + if(!ok) + return 0; + + if(cp.structType != sizeof(cp)) + return 0; + + + yaffs_CheckpointDeviceToDevice(dev,&cp); + + nBytes = nBlocks * sizeof(yaffs_BlockInfo); + + ok = (yaffs_CheckpointRead(dev,dev->blockInfo,nBytes) == nBytes); + + if(!ok) + return 0; + nBytes = nBlocks * dev->chunkBitmapStride; + + ok = (yaffs_CheckpointRead(dev,dev->chunkBits,nBytes) == nBytes); + + return ok ? 1 : 0; +} + +static void yaffs_ObjectToCheckpointObject(yaffs_CheckpointObject *cp, + yaffs_Object *obj) +{ + + cp->objectId = obj->objectId; + cp->parentId = (obj->parent) ? obj->parent->objectId : 0; + cp->hdrChunk = obj->hdrChunk; + cp->variantType = obj->variantType; + cp->deleted = obj->deleted; + cp->softDeleted = obj->softDeleted; + cp->unlinked = obj->unlinked; + cp->fake = obj->fake; + cp->renameAllowed = obj->renameAllowed; + cp->unlinkAllowed = obj->unlinkAllowed; + cp->serial = obj->serial; + cp->nDataChunks = obj->nDataChunks; + + if(obj->variantType == YAFFS_OBJECT_TYPE_FILE) + cp->fileSizeOrEquivalentObjectId = obj->variant.fileVariant.fileSize; + else if(obj->variantType == YAFFS_OBJECT_TYPE_HARDLINK) + cp->fileSizeOrEquivalentObjectId = obj->variant.hardLinkVariant.equivalentObjectId; +} + +static void yaffs_CheckpointObjectToObject( yaffs_Object *obj,yaffs_CheckpointObject *cp) +{ + + yaffs_Object *parent; + + obj->objectId = cp->objectId; + + if(cp->parentId) + parent = yaffs_FindOrCreateObjectByNumber( + obj->myDev, + cp->parentId, + YAFFS_OBJECT_TYPE_DIRECTORY); + else + parent = NULL; + + if(parent) + yaffs_AddObjectToDirectory(parent, obj); + + obj->hdrChunk = cp->hdrChunk; + obj->variantType = cp->variantType; + obj->deleted = cp->deleted; + obj->softDeleted = cp->softDeleted; + obj->unlinked = cp->unlinked; + obj->fake = cp->fake; + obj->renameAllowed = cp->renameAllowed; + obj->unlinkAllowed = cp->unlinkAllowed; + obj->serial = cp->serial; + obj->nDataChunks = cp->nDataChunks; + + if(obj->variantType == YAFFS_OBJECT_TYPE_FILE) + obj->variant.fileVariant.fileSize = cp->fileSizeOrEquivalentObjectId; + else if(obj->variantType == YAFFS_OBJECT_TYPE_HARDLINK) + obj->variant.hardLinkVariant.equivalentObjectId = cp->fileSizeOrEquivalentObjectId; + + if(obj->hdrChunk > 0) + obj->lazyLoaded = 1; +} + + + +static int yaffs_CheckpointTnodeWorker(yaffs_Object * in, yaffs_Tnode * tn, + __u32 level, int chunkOffset) +{ + int i; + yaffs_Device *dev = in->myDev; + int ok = 1; + int tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8; + + if(tnodeSize < sizeof(yaffs_Tnode)) + tnodeSize = sizeof(yaffs_Tnode); + + + if (tn) { + if (level > 0) { + + for (i = 0; i < YAFFS_NTNODES_INTERNAL && ok; i++){ + if (tn->internal[i]) { + ok = yaffs_CheckpointTnodeWorker(in, + tn->internal[i], + level - 1, + (chunkOffset<variantType == YAFFS_OBJECT_TYPE_FILE){ + ok = yaffs_CheckpointTnodeWorker(obj, + obj->variant.fileVariant.top, + obj->variant.fileVariant.topLevel, + 0); + if(ok) + ok = (yaffs_CheckpointWrite(obj->myDev,&endMarker,sizeof(endMarker)) == + sizeof(endMarker)); + } + + return ok ? 1 : 0; +} + +static int yaffs_ReadCheckpointTnodes(yaffs_Object *obj) +{ + __u32 baseChunk; + int ok = 1; + yaffs_Device *dev = obj->myDev; + yaffs_FileStructure *fileStructPtr = &obj->variant.fileVariant; + yaffs_Tnode *tn; + int nread = 0; + int tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8; + + if(tnodeSize < sizeof(yaffs_Tnode)) + tnodeSize = sizeof(yaffs_Tnode); + + ok = (yaffs_CheckpointRead(dev,&baseChunk,sizeof(baseChunk)) == sizeof(baseChunk)); + + while(ok && (~baseChunk)){ + nread++; + /* Read level 0 tnode */ + + + /* printf("read tnode at %d\n",baseChunk); */ + tn = yaffs_GetTnodeRaw(dev); + if(tn) + ok = (yaffs_CheckpointRead(dev,tn,tnodeSize) == tnodeSize); + else + ok = 0; + + if(tn && ok){ + ok = yaffs_AddOrFindLevel0Tnode(dev, + fileStructPtr, + baseChunk, + tn) ? 1 : 0; + + } + + if(ok) + ok = (yaffs_CheckpointRead(dev,&baseChunk,sizeof(baseChunk)) == sizeof(baseChunk)); + + } + + T(YAFFS_TRACE_CHECKPOINT,( + TSTR("Checkpoint read tnodes %d records, last %d. ok %d" TENDSTR), + nread,baseChunk,ok)); + + return ok ? 1 : 0; +} + + +static int yaffs_WriteCheckpointObjects(yaffs_Device *dev) +{ + yaffs_Object *obj; + yaffs_CheckpointObject cp; + int i; + int ok = 1; + struct ylist_head *lh; + + + /* Iterate through the objects in each hash entry, + * dumping them to the checkpointing stream. + */ + + for(i = 0; ok && i < YAFFS_NOBJECT_BUCKETS; i++){ + ylist_for_each(lh, &dev->objectBucket[i].list) { + if (lh) { + obj = ylist_entry(lh, yaffs_Object, hashLink); + if (!obj->deferedFree) { + yaffs_ObjectToCheckpointObject(&cp,obj); + cp.structType = sizeof(cp); + + T(YAFFS_TRACE_CHECKPOINT,( + TSTR("Checkpoint write object %d parent %d type %d chunk %d obj addr %x" TENDSTR), + cp.objectId,cp.parentId,cp.variantType,cp.hdrChunk,(unsigned) obj)); + + ok = (yaffs_CheckpointWrite(dev,&cp,sizeof(cp)) == sizeof(cp)); + + if(ok && obj->variantType == YAFFS_OBJECT_TYPE_FILE){ + ok = yaffs_WriteCheckpointTnodes(obj); + } + } + } + } + } + + /* Dump end of list */ + memset(&cp,0xFF,sizeof(yaffs_CheckpointObject)); + cp.structType = sizeof(cp); + + if(ok) + ok = (yaffs_CheckpointWrite(dev,&cp,sizeof(cp)) == sizeof(cp)); + + return ok ? 1 : 0; +} + +static int yaffs_ReadCheckpointObjects(yaffs_Device *dev) +{ + yaffs_Object *obj; + yaffs_CheckpointObject cp; + int ok = 1; + int done = 0; + yaffs_Object *hardList = NULL; + + while(ok && !done) { + ok = (yaffs_CheckpointRead(dev,&cp,sizeof(cp)) == sizeof(cp)); + if(cp.structType != sizeof(cp)) { + T(YAFFS_TRACE_CHECKPOINT,(TSTR("struct size %d instead of %d ok %d"TENDSTR), + cp.structType,sizeof(cp),ok)); + ok = 0; + } + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("Checkpoint read object %d parent %d type %d chunk %d " TENDSTR), + cp.objectId,cp.parentId,cp.variantType,cp.hdrChunk)); + + if(ok && cp.objectId == ~0) + done = 1; + else if(ok){ + obj = yaffs_FindOrCreateObjectByNumber(dev,cp.objectId, cp.variantType); + if(obj) { + yaffs_CheckpointObjectToObject(obj,&cp); + if(obj->variantType == YAFFS_OBJECT_TYPE_FILE) { + ok = yaffs_ReadCheckpointTnodes(obj); + } else if(obj->variantType == YAFFS_OBJECT_TYPE_HARDLINK) { + obj->hardLinks.next = + (struct ylist_head *) + hardList; + hardList = obj; + } + + } + } + } + + if(ok) + yaffs_HardlinkFixup(dev,hardList); + + return ok ? 1 : 0; +} + +static int yaffs_WriteCheckpointSum(yaffs_Device *dev) +{ + __u32 checkpointSum; + int ok; + + yaffs_GetCheckpointSum(dev,&checkpointSum); + + ok = (yaffs_CheckpointWrite(dev,&checkpointSum,sizeof(checkpointSum)) == sizeof(checkpointSum)); + + if(!ok) + return 0; + + return 1; +} + +static int yaffs_ReadCheckpointSum(yaffs_Device *dev) +{ + __u32 checkpointSum0; + __u32 checkpointSum1; + int ok; + + yaffs_GetCheckpointSum(dev,&checkpointSum0); + + ok = (yaffs_CheckpointRead(dev,&checkpointSum1,sizeof(checkpointSum1)) == sizeof(checkpointSum1)); + + if(!ok) + return 0; + + if(checkpointSum0 != checkpointSum1) + return 0; + + return 1; +} + + +static int yaffs_WriteCheckpointData(yaffs_Device *dev) +{ + + int ok = 1; + + if(dev->skipCheckpointWrite || !dev->isYaffs2){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("skipping checkpoint write" TENDSTR))); + ok = 0; + } + + if(ok) + ok = yaffs_CheckpointOpen(dev,1); + + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("write checkpoint validity" TENDSTR))); + ok = yaffs_WriteCheckpointValidityMarker(dev,1); + } + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("write checkpoint device" TENDSTR))); + ok = yaffs_WriteCheckpointDevice(dev); + } + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("write checkpoint objects" TENDSTR))); + ok = yaffs_WriteCheckpointObjects(dev); + } + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("write checkpoint validity" TENDSTR))); + ok = yaffs_WriteCheckpointValidityMarker(dev,0); + } + + if(ok){ + ok = yaffs_WriteCheckpointSum(dev); + } + + + if(!yaffs_CheckpointClose(dev)) + ok = 0; + + if(ok) + dev->isCheckpointed = 1; + else + dev->isCheckpointed = 0; + + return dev->isCheckpointed; +} + +static int yaffs_ReadCheckpointData(yaffs_Device *dev) +{ + int ok = 1; + + if(dev->skipCheckpointRead || !dev->isYaffs2){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("skipping checkpoint read" TENDSTR))); + ok = 0; + } + + if(ok) + ok = yaffs_CheckpointOpen(dev,0); /* open for read */ + + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("read checkpoint validity" TENDSTR))); + ok = yaffs_ReadCheckpointValidityMarker(dev,1); + } + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("read checkpoint device" TENDSTR))); + ok = yaffs_ReadCheckpointDevice(dev); + } + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("read checkpoint objects" TENDSTR))); + ok = yaffs_ReadCheckpointObjects(dev); + } + if(ok){ + T(YAFFS_TRACE_CHECKPOINT,(TSTR("read checkpoint validity" TENDSTR))); + ok = yaffs_ReadCheckpointValidityMarker(dev,0); + } + + if(ok){ + ok = yaffs_ReadCheckpointSum(dev); + T(YAFFS_TRACE_CHECKPOINT,(TSTR("read checkpoint checksum %d" TENDSTR),ok)); + } + + if(!yaffs_CheckpointClose(dev)) + ok = 0; + + if(ok) + dev->isCheckpointed = 1; + else + dev->isCheckpointed = 0; + + return ok ? 1 : 0; + +} + +static void yaffs_InvalidateCheckpoint(yaffs_Device *dev) +{ + if(dev->isCheckpointed || + dev->blocksInCheckpoint > 0){ + dev->isCheckpointed = 0; + yaffs_CheckpointInvalidateStream(dev); + if(dev->superBlock && dev->markSuperBlockDirty) + dev->markSuperBlockDirty(dev->superBlock); + } +} + + +int yaffs_CheckpointSave(yaffs_Device *dev) +{ + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("save entry: isCheckpointed %d"TENDSTR),dev->isCheckpointed)); + + yaffs_VerifyObjects(dev); + yaffs_VerifyBlocks(dev); + yaffs_VerifyFreeChunks(dev); + + if(!dev->isCheckpointed) { + yaffs_InvalidateCheckpoint(dev); + yaffs_WriteCheckpointData(dev); + } + + T(YAFFS_TRACE_ALWAYS,(TSTR("save exit: isCheckpointed %d"TENDSTR),dev->isCheckpointed)); + + return dev->isCheckpointed; +} + +int yaffs_CheckpointRestore(yaffs_Device *dev) +{ + int retval; + T(YAFFS_TRACE_CHECKPOINT,(TSTR("restore entry: isCheckpointed %d"TENDSTR),dev->isCheckpointed)); + + retval = yaffs_ReadCheckpointData(dev); + + if(dev->isCheckpointed){ + yaffs_VerifyObjects(dev); + yaffs_VerifyBlocks(dev); + yaffs_VerifyFreeChunks(dev); + } + + T(YAFFS_TRACE_CHECKPOINT,(TSTR("restore exit: isCheckpointed %d"TENDSTR),dev->isCheckpointed)); + + return retval; +} + +/*--------------------- File read/write ------------------------ + * Read and write have very similar structures. + * In general the read/write has three parts to it + * An incomplete chunk to start with (if the read/write is not chunk-aligned) + * Some complete chunks + * An incomplete chunk to end off with + * + * Curve-balls: the first chunk might also be the last chunk. + */ + +int yaffs_ReadDataFromFile(yaffs_Object * in, __u8 * buffer, loff_t offset, + int nBytes) +{ + + int chunk; + __u32 start; + int nToCopy; + int n = nBytes; + int nDone = 0; + yaffs_ChunkCache *cache; + + yaffs_Device *dev; + + dev = in->myDev; + + while (n > 0) { + //chunk = offset / dev->nDataBytesPerChunk + 1; + //start = offset % dev->nDataBytesPerChunk; + yaffs_AddrToChunk(dev,offset,&chunk,&start); + chunk++; + + /* OK now check for the curveball where the start and end are in + * the same chunk. + */ + if ((start + n) < dev->nDataBytesPerChunk) { + nToCopy = n; + } else { + nToCopy = dev->nDataBytesPerChunk - start; + } + + cache = yaffs_FindChunkCache(in, chunk); + + /* If the chunk is already in the cache or it is less than a whole chunk + * or we're using inband tags then use the cache (if there is caching) + * else bypass the cache. + */ + if (cache || nToCopy != dev->nDataBytesPerChunk || dev->inbandTags) { + if (dev->nShortOpCaches > 0) { + + /* If we can't find the data in the cache, then load it up. */ + + if (!cache) { + cache = yaffs_GrabChunkCache(in->myDev); + cache->object = in; + cache->chunkId = chunk; + cache->dirty = 0; + cache->locked = 0; + yaffs_ReadChunkDataFromObject(in, chunk, + cache-> + data); + cache->nBytes = 0; + } + + yaffs_UseChunkCache(dev, cache, 0); + + cache->locked = 1; + +#ifdef CONFIG_YAFFS_WINCE + yfsd_UnlockYAFFS(TRUE); +#endif + memcpy(buffer, &cache->data[start], nToCopy); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_LockYAFFS(TRUE); +#endif + cache->locked = 0; + } else { + /* Read into the local buffer then copy..*/ + + __u8 *localBuffer = + yaffs_GetTempBuffer(dev, __LINE__); + yaffs_ReadChunkDataFromObject(in, chunk, + localBuffer); +#ifdef CONFIG_YAFFS_WINCE + yfsd_UnlockYAFFS(TRUE); +#endif + memcpy(buffer, &localBuffer[start], nToCopy); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_LockYAFFS(TRUE); +#endif + yaffs_ReleaseTempBuffer(dev, localBuffer, + __LINE__); + } + + } else { +#ifdef CONFIG_YAFFS_WINCE + __u8 *localBuffer = yaffs_GetTempBuffer(dev, __LINE__); + + /* Under WinCE can't do direct transfer. Need to use a local buffer. + * This is because we otherwise screw up WinCE's memory mapper + */ + yaffs_ReadChunkDataFromObject(in, chunk, localBuffer); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_UnlockYAFFS(TRUE); +#endif + memcpy(buffer, localBuffer, dev->nDataBytesPerChunk); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_LockYAFFS(TRUE); + yaffs_ReleaseTempBuffer(dev, localBuffer, __LINE__); +#endif + +#else + /* A full chunk. Read directly into the supplied buffer. */ + yaffs_ReadChunkDataFromObject(in, chunk, buffer); +#endif + } + + n -= nToCopy; + offset += nToCopy; + buffer += nToCopy; + nDone += nToCopy; + + } + + return nDone; +} + +int yaffs_WriteDataToFile(yaffs_Object * in, const __u8 * buffer, loff_t offset, + int nBytes, int writeThrough) +{ + + int chunk; + __u32 start; + int nToCopy; + int n = nBytes; + int nDone = 0; + int nToWriteBack; + int startOfWrite = offset; + int chunkWritten = 0; + __u32 nBytesRead; + __u32 chunkStart; + + yaffs_Device *dev; + + dev = in->myDev; + + while (n > 0 && chunkWritten >= 0) { + //chunk = offset / dev->nDataBytesPerChunk + 1; + //start = offset % dev->nDataBytesPerChunk; + yaffs_AddrToChunk(dev,offset,&chunk,&start); + + if(chunk * dev->nDataBytesPerChunk + start != offset || + start >= dev->nDataBytesPerChunk){ + T(YAFFS_TRACE_ERROR,(TSTR("AddrToChunk of offset %d gives chunk %d start %d"TENDSTR), + (int)offset, chunk,start)); + } + chunk++; + + /* OK now check for the curveball where the start and end are in + * the same chunk. + */ + + if ((start + n) < dev->nDataBytesPerChunk) { + nToCopy = n; + + /* Now folks, to calculate how many bytes to write back.... + * If we're overwriting and not writing to then end of file then + * we need to write back as much as was there before. + */ + + chunkStart = ((chunk - 1) * dev->nDataBytesPerChunk); + + if(chunkStart > in->variant.fileVariant.fileSize) + nBytesRead = 0; /* Past end of file */ + else + nBytesRead = in->variant.fileVariant.fileSize - chunkStart; + + if (nBytesRead > dev->nDataBytesPerChunk) { + nBytesRead = dev->nDataBytesPerChunk; + } + + nToWriteBack = + (nBytesRead > + (start + n)) ? nBytesRead : (start + n); + + if(nToWriteBack < 0 || nToWriteBack > dev->nDataBytesPerChunk) + YBUG(); + + } else { + nToCopy = dev->nDataBytesPerChunk - start; + nToWriteBack = dev->nDataBytesPerChunk; + } + + if (nToCopy != dev->nDataBytesPerChunk || dev->inbandTags) { + /* An incomplete start or end chunk (or maybe both start and end chunk), + * or we're using inband tags, so we want to use the cache buffers. + */ + if (dev->nShortOpCaches > 0) { + yaffs_ChunkCache *cache; + /* If we can't find the data in the cache, then load the cache */ + cache = yaffs_FindChunkCache(in, chunk); + + if (!cache + && yaffs_CheckSpaceForAllocation(in-> + myDev)) { + cache = yaffs_GrabChunkCache(in->myDev); + cache->object = in; + cache->chunkId = chunk; + cache->dirty = 0; + cache->locked = 0; + yaffs_ReadChunkDataFromObject(in, chunk, + cache-> + data); + } + else if(cache && + !cache->dirty && + !yaffs_CheckSpaceForAllocation(in->myDev)){ + /* Drop the cache if it was a read cache item and + * no space check has been made for it. + */ + cache = NULL; + } + + if (cache) { + yaffs_UseChunkCache(dev, cache, 1); + cache->locked = 1; +#ifdef CONFIG_YAFFS_WINCE + yfsd_UnlockYAFFS(TRUE); +#endif + + memcpy(&cache->data[start], buffer, + nToCopy); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_LockYAFFS(TRUE); +#endif + cache->locked = 0; + cache->nBytes = nToWriteBack; + + if (writeThrough) { + chunkWritten = + yaffs_WriteChunkDataToObject + (cache->object, + cache->chunkId, + cache->data, cache->nBytes, + 1); + cache->dirty = 0; + } + + } else { + chunkWritten = -1; /* fail the write */ + } + } else { + /* An incomplete start or end chunk (or maybe both start and end chunk) + * Read into the local buffer then copy, then copy over and write back. + */ + + __u8 *localBuffer = + yaffs_GetTempBuffer(dev, __LINE__); + + yaffs_ReadChunkDataFromObject(in, chunk, + localBuffer); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_UnlockYAFFS(TRUE); +#endif + + memcpy(&localBuffer[start], buffer, nToCopy); + +#ifdef CONFIG_YAFFS_WINCE + yfsd_LockYAFFS(TRUE); +#endif + chunkWritten = + yaffs_WriteChunkDataToObject(in, chunk, + localBuffer, + nToWriteBack, + 0); + + yaffs_ReleaseTempBuffer(dev, localBuffer, + __LINE__); + + } + + } else { + /* A full chunk. Write directly from the supplied buffer. */ + +#ifdef CONFIG_YAFFS_WINCE + /* Under WinCE can't do direct transfer. Need to use a local buffer. + * This is because we otherwise screw up WinCE's memory mapper + */ + __u8 *localBuffer = yaffs_GetTempBuffer(dev, __LINE__); +#ifdef CONFIG_YAFFS_WINCE + yfsd_UnlockYAFFS(TRUE); +#endif + memcpy(localBuffer, buffer, dev->nDataBytesPerChunk); +#ifdef CONFIG_YAFFS_WINCE + yfsd_LockYAFFS(TRUE); +#endif + chunkWritten = + yaffs_WriteChunkDataToObject(in, chunk, localBuffer, + dev->nDataBytesPerChunk, + 0); + yaffs_ReleaseTempBuffer(dev, localBuffer, __LINE__); +#else + + chunkWritten = + yaffs_WriteChunkDataToObject(in, chunk, buffer, + dev->nDataBytesPerChunk, + 0); +#endif + /* Since we've overwritten the cached data, we better invalidate it. */ + yaffs_InvalidateChunkCache(in, chunk); + } + + if (chunkWritten >= 0) { + n -= nToCopy; + offset += nToCopy; + buffer += nToCopy; + nDone += nToCopy; + } + + } + + /* Update file object */ + + if ((startOfWrite + nDone) > in->variant.fileVariant.fileSize) { + in->variant.fileVariant.fileSize = (startOfWrite + nDone); + } + + in->dirty = 1; + + return nDone; +} + + +/* ---------------------- File resizing stuff ------------------ */ + +static void yaffs_PruneResizedChunks(yaffs_Object * in, int newSize) +{ + + yaffs_Device *dev = in->myDev; + int oldFileSize = in->variant.fileVariant.fileSize; + + int lastDel = 1 + (oldFileSize - 1) / dev->nDataBytesPerChunk; + + int startDel = 1 + (newSize + dev->nDataBytesPerChunk - 1) / + dev->nDataBytesPerChunk; + int i; + int chunkId; + + /* Delete backwards so that we don't end up with holes if + * power is lost part-way through the operation. + */ + for (i = lastDel; i >= startDel; i--) { + /* NB this could be optimised somewhat, + * eg. could retrieve the tags and write them without + * using yaffs_DeleteChunk + */ + + chunkId = yaffs_FindAndDeleteChunkInFile(in, i, NULL); + if (chunkId > 0) { + if (chunkId < + (dev->internalStartBlock * dev->nChunksPerBlock) + || chunkId >= + ((dev->internalEndBlock + + 1) * dev->nChunksPerBlock)) { + T(YAFFS_TRACE_ALWAYS, + (TSTR("Found daft chunkId %d for %d" TENDSTR), + chunkId, i)); + } else { + in->nDataChunks--; + yaffs_DeleteChunk(dev, chunkId, 1, __LINE__); + } + } + } + +} + +int yaffs_ResizeFile(yaffs_Object * in, loff_t newSize) +{ + + int oldFileSize = in->variant.fileVariant.fileSize; + __u32 newSizeOfPartialChunk; + int newFullChunks; + + yaffs_Device *dev = in->myDev; + + yaffs_AddrToChunk(dev, newSize, &newFullChunks, &newSizeOfPartialChunk); + + yaffs_FlushFilesChunkCache(in); + yaffs_InvalidateWholeChunkCache(in); + + yaffs_CheckGarbageCollection(dev); + + if (in->variantType != YAFFS_OBJECT_TYPE_FILE) { + return YAFFS_FAIL; + } + + if (newSize == oldFileSize) { + return YAFFS_OK; + } + + if (newSize < oldFileSize) { + + yaffs_PruneResizedChunks(in, newSize); + + if (newSizeOfPartialChunk != 0) { + int lastChunk = 1 + newFullChunks; + + __u8 *localBuffer = yaffs_GetTempBuffer(dev, __LINE__); + + /* Got to read and rewrite the last chunk with its new size and zero pad */ + yaffs_ReadChunkDataFromObject(in, lastChunk, + localBuffer); + + memset(localBuffer + newSizeOfPartialChunk, 0, + dev->nDataBytesPerChunk - newSizeOfPartialChunk); + + yaffs_WriteChunkDataToObject(in, lastChunk, localBuffer, + newSizeOfPartialChunk, 1); + + yaffs_ReleaseTempBuffer(dev, localBuffer, __LINE__); + } + + in->variant.fileVariant.fileSize = newSize; + + yaffs_PruneFileStructure(dev, &in->variant.fileVariant); + } else { + /* newsSize > oldFileSize */ + in->variant.fileVariant.fileSize = newSize; + } + + + + /* Write a new object header. + * show we've shrunk the file, if need be + * Do this only if the file is not in the deleted directories. + */ + if (in->parent && + in->parent->objectId != YAFFS_OBJECTID_UNLINKED && + in->parent->objectId != YAFFS_OBJECTID_DELETED) { + yaffs_UpdateObjectHeader(in, NULL, 0, + (newSize < oldFileSize) ? 1 : 0, 0); + } + + return YAFFS_OK; +} + +loff_t yaffs_GetFileSize(yaffs_Object * obj) +{ + obj = yaffs_GetEquivalentObject(obj); + + switch (obj->variantType) { + case YAFFS_OBJECT_TYPE_FILE: + return obj->variant.fileVariant.fileSize; + case YAFFS_OBJECT_TYPE_SYMLINK: + return yaffs_strlen(obj->variant.symLinkVariant.alias); + default: + return 0; + } +} + + + +int yaffs_FlushFile(yaffs_Object * in, int updateTime) +{ + int retVal; + if (in->dirty) { + yaffs_FlushFilesChunkCache(in); + if (updateTime) { +#ifdef CONFIG_YAFFS_WINCE + yfsd_WinFileTimeNow(in->win_mtime); +#else + + in->yst_mtime = Y_CURRENT_TIME; + +#endif + } + + retVal = + (yaffs_UpdateObjectHeader(in, NULL, 0, 0, 0) >= + 0) ? YAFFS_OK : YAFFS_FAIL; + } else { + retVal = YAFFS_OK; + } + + return retVal; + +} + +static int yaffs_DoGenericObjectDeletion(yaffs_Object * in) +{ + + /* First off, invalidate the file's data in the cache, without flushing. */ + yaffs_InvalidateWholeChunkCache(in); + + if (in->myDev->isYaffs2 && (in->parent != in->myDev->deletedDir)) { + /* Move to the unlinked directory so we have a record that it was deleted. */ + yaffs_ChangeObjectName(in, in->myDev->deletedDir,_Y("deleted"), 0, 0); + + } + + yaffs_RemoveObjectFromDirectory(in); + yaffs_DeleteChunk(in->myDev, in->hdrChunk, 1, __LINE__); + in->hdrChunk = 0; + + yaffs_FreeObject(in); + return YAFFS_OK; + +} + +/* yaffs_DeleteFile deletes the whole file data + * and the inode associated with the file. + * It does not delete the links associated with the file. + */ + +static int yaffs_UnlinkFile(yaffs_Object * in) +{ + + int retVal; + int immediateDeletion = 0; + +#ifdef __KERNEL__ + if (!in->myInode) { + immediateDeletion = 1; + } +#else + if (in->inUse <= 0) { + immediateDeletion = 1; + } +#endif + + if (immediateDeletion) { + retVal = + yaffs_ChangeObjectName(in, in->myDev->deletedDir, + _Y("deleted"), 0, 0); + T(YAFFS_TRACE_TRACING, + (TSTR("yaffs: immediate deletion of file %d" TENDSTR), + in->objectId)); + in->deleted = 1; + in->myDev->nDeletedFiles++; + if (1 || in->myDev->isYaffs2) { + yaffs_ResizeFile(in, 0); + } + yaffs_SoftDeleteFile(in); + } else { + retVal = + yaffs_ChangeObjectName(in, in->myDev->unlinkedDir, + _Y("unlinked"), 0, 0); + } + + + return retVal; +} + +int yaffs_DeleteFile(yaffs_Object * in) +{ + int retVal = YAFFS_OK; + int deleted = in->deleted; + + yaffs_ResizeFile(in,0); + + if (in->nDataChunks > 0) { + /* Use soft deletion if there is data in the file. + * That won't be the case if it has been resized to zero. + */ + if (!in->unlinked) { + retVal = yaffs_UnlinkFile(in); + } + if (retVal == YAFFS_OK && in->unlinked && !in->deleted) { + in->deleted = deleted = 1; + in->myDev->nDeletedFiles++; + yaffs_SoftDeleteFile(in); + } + return deleted ? YAFFS_OK : YAFFS_FAIL; + } else { + /* The file has no data chunks so we toss it immediately */ + yaffs_FreeTnode(in->myDev, in->variant.fileVariant.top); + in->variant.fileVariant.top = NULL; + yaffs_DoGenericObjectDeletion(in); + + return YAFFS_OK; + } +} + +static int yaffs_DeleteDirectory(yaffs_Object * in) +{ + /* First check that the directory is empty. */ + if (ylist_empty(&in->variant.directoryVariant.children)) { + return yaffs_DoGenericObjectDeletion(in); + } + + return YAFFS_FAIL; + +} + +static int yaffs_DeleteSymLink(yaffs_Object * in) +{ + YFREE(in->variant.symLinkVariant.alias); + + return yaffs_DoGenericObjectDeletion(in); +} + +static int yaffs_DeleteHardLink(yaffs_Object * in) +{ + /* remove this hardlink from the list assocaited with the equivalent + * object + */ + ylist_del_init(&in->hardLinks); + return yaffs_DoGenericObjectDeletion(in); +} + +static void yaffs_DestroyObject(yaffs_Object * obj) +{ + switch (obj->variantType) { + case YAFFS_OBJECT_TYPE_FILE: + yaffs_DeleteFile(obj); + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + yaffs_DeleteDirectory(obj); + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + yaffs_DeleteSymLink(obj); + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + yaffs_DeleteHardLink(obj); + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + yaffs_DoGenericObjectDeletion(obj); + break; + case YAFFS_OBJECT_TYPE_UNKNOWN: + break; /* should not happen. */ + } +} + +static int yaffs_UnlinkWorker(yaffs_Object * obj) +{ + + if (obj->variantType == YAFFS_OBJECT_TYPE_HARDLINK) { + return yaffs_DeleteHardLink(obj); + } else if (!ylist_empty(&obj->hardLinks)) { + /* Curve ball: We're unlinking an object that has a hardlink. + * + * This problem arises because we are not strictly following + * The Linux link/inode model. + * + * We can't really delete the object. + * Instead, we do the following: + * - Select a hardlink. + * - Unhook it from the hard links + * - Unhook it from its parent directory (so that the rename can work) + * - Rename the object to the hardlink's name. + * - Delete the hardlink + */ + + yaffs_Object *hl; + int retVal; + YCHAR name[YAFFS_MAX_NAME_LENGTH + 1]; + + hl = ylist_entry(obj->hardLinks.next, yaffs_Object, hardLinks); + + ylist_del_init(&hl->hardLinks); + ylist_del_init(&hl->siblings); + + yaffs_GetObjectName(hl, name, YAFFS_MAX_NAME_LENGTH + 1); + + retVal = yaffs_ChangeObjectName(obj, hl->parent, name, 0, 0); + + if (retVal == YAFFS_OK) { + retVal = yaffs_DoGenericObjectDeletion(hl); + } + return retVal; + + } else { + switch (obj->variantType) { + case YAFFS_OBJECT_TYPE_FILE: + return yaffs_UnlinkFile(obj); + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + return yaffs_DeleteDirectory(obj); + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + return yaffs_DeleteSymLink(obj); + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + return yaffs_DoGenericObjectDeletion(obj); + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + case YAFFS_OBJECT_TYPE_UNKNOWN: + default: + return YAFFS_FAIL; + } + } +} + + +static int yaffs_UnlinkObject( yaffs_Object *obj) +{ + + if (obj && obj->unlinkAllowed) { + return yaffs_UnlinkWorker(obj); + } + + return YAFFS_FAIL; + +} +int yaffs_Unlink(yaffs_Object * dir, const YCHAR * name) +{ + yaffs_Object *obj; + + obj = yaffs_FindObjectByName(dir, name); + return yaffs_UnlinkObject(obj); +} + +/*----------------------- Initialisation Scanning ---------------------- */ + +static void yaffs_HandleShadowedObject(yaffs_Device * dev, int objId, + int backwardScanning) +{ + yaffs_Object *obj; + + if (!backwardScanning) { + /* Handle YAFFS1 forward scanning case + * For YAFFS1 we always do the deletion + */ + + } else { + /* Handle YAFFS2 case (backward scanning) + * If the shadowed object exists then ignore. + */ + if (yaffs_FindObjectByNumber(dev, objId)) { + return; + } + } + + /* Let's create it (if it does not exist) assuming it is a file so that it can do shrinking etc. + * We put it in unlinked dir to be cleaned up after the scanning + */ + obj = + yaffs_FindOrCreateObjectByNumber(dev, objId, + YAFFS_OBJECT_TYPE_FILE); + yaffs_AddObjectToDirectory(dev->unlinkedDir, obj); + obj->variant.fileVariant.shrinkSize = 0; + obj->valid = 1; /* So that we don't read any other info for this file */ + +} + +typedef struct { + int seq; + int block; +} yaffs_BlockIndex; + + +static void yaffs_HardlinkFixup(yaffs_Device *dev, yaffs_Object *hardList) +{ + yaffs_Object *hl; + yaffs_Object *in; + + while (hardList) { + hl = hardList; + hardList = (yaffs_Object *) (hardList->hardLinks.next); + + in = yaffs_FindObjectByNumber(dev, + hl->variant.hardLinkVariant. + equivalentObjectId); + + if (in) { + /* Add the hardlink pointers */ + hl->variant.hardLinkVariant.equivalentObject = in; + ylist_add(&hl->hardLinks, &in->hardLinks); + } else { + /* Todo Need to report/handle this better. + * Got a problem... hardlink to a non-existant object + */ + hl->variant.hardLinkVariant.equivalentObject = NULL; + YINIT_LIST_HEAD(&hl->hardLinks); + + } + + } + +} + + + + + +static int ybicmp(const void *a, const void *b){ + register int aseq = ((yaffs_BlockIndex *)a)->seq; + register int bseq = ((yaffs_BlockIndex *)b)->seq; + register int ablock = ((yaffs_BlockIndex *)a)->block; + register int bblock = ((yaffs_BlockIndex *)b)->block; + if( aseq == bseq ) + return ablock - bblock; + else + return aseq - bseq; + +} + + +struct yaffs_ShadowFixerStruct { + int objectId; + int shadowedId; + struct yaffs_ShadowFixerStruct *next; +}; + + +static void yaffs_StripDeletedObjects(yaffs_Device *dev) +{ + /* + * Sort out state of unlinked and deleted objects after scanning. + */ + struct ylist_head *i; + struct ylist_head *n; + yaffs_Object *l; + + /* Soft delete all the unlinked files */ + ylist_for_each_safe(i, n, + &dev->unlinkedDir->variant.directoryVariant.children) { + if (i) { + l = ylist_entry(i, yaffs_Object, siblings); + yaffs_DestroyObject(l); + } + } + + ylist_for_each_safe(i, n, + &dev->deletedDir->variant.directoryVariant.children) { + if (i) { + l = ylist_entry(i, yaffs_Object, siblings); + yaffs_DestroyObject(l); + } + } + +} + +static int yaffs_Scan(yaffs_Device * dev) +{ + yaffs_ExtendedTags tags; + int blk; + int blockIterator; + int startIterator; + int endIterator; + int result; + + int chunk; + int c; + int deleted; + yaffs_BlockState state; + yaffs_Object *hardList = NULL; + yaffs_BlockInfo *bi; + __u32 sequenceNumber; + yaffs_ObjectHeader *oh; + yaffs_Object *in; + yaffs_Object *parent; + + int alloc_failed = 0; + + struct yaffs_ShadowFixerStruct *shadowFixerList = NULL; + + + __u8 *chunkData; + + + + T(YAFFS_TRACE_SCAN, + (TSTR("yaffs_Scan starts intstartblk %d intendblk %d..." TENDSTR), + dev->internalStartBlock, dev->internalEndBlock)); + + chunkData = yaffs_GetTempBuffer(dev, __LINE__); + + dev->sequenceNumber = YAFFS_LOWEST_SEQUENCE_NUMBER; + + /* Scan all the blocks to determine their state */ + for (blk = dev->internalStartBlock; blk <= dev->internalEndBlock; blk++) { + bi = yaffs_GetBlockInfo(dev, blk); + yaffs_ClearChunkBits(dev, blk); + bi->pagesInUse = 0; + bi->softDeletions = 0; + + yaffs_QueryInitialBlockState(dev, blk, &state, &sequenceNumber); + + bi->blockState = state; + bi->sequenceNumber = sequenceNumber; + + T(YAFFS_TRACE_SCAN_DEBUG, + (TSTR("Block scanning block %d state %d seq %d" TENDSTR), blk, + state, sequenceNumber)); + + if (state == YAFFS_BLOCK_STATE_DEAD) { + T(YAFFS_TRACE_BAD_BLOCKS, + (TSTR("block %d is bad" TENDSTR), blk)); + } else if (state == YAFFS_BLOCK_STATE_EMPTY) { + T(YAFFS_TRACE_SCAN_DEBUG, + (TSTR("Block empty " TENDSTR))); + dev->nErasedBlocks++; + dev->nFreeChunks += dev->nChunksPerBlock; + } + } + + startIterator = dev->internalStartBlock; + endIterator = dev->internalEndBlock; + + /* For each block.... */ + for (blockIterator = startIterator; !alloc_failed && blockIterator <= endIterator; + blockIterator++) { + + YYIELD(); + + YYIELD(); + + blk = blockIterator; + + bi = yaffs_GetBlockInfo(dev, blk); + state = bi->blockState; + + deleted = 0; + + /* For each chunk in each block that needs scanning....*/ + for (c = 0; !alloc_failed && c < dev->nChunksPerBlock && + state == YAFFS_BLOCK_STATE_NEEDS_SCANNING; c++) { + /* Read the tags and decide what to do */ + chunk = blk * dev->nChunksPerBlock + c; + + result = yaffs_ReadChunkWithTagsFromNAND(dev, chunk, NULL, + &tags); + + /* Let's have a good look at this chunk... */ + + if (tags.eccResult == YAFFS_ECC_RESULT_UNFIXED || tags.chunkDeleted) { + /* YAFFS1 only... + * A deleted chunk + */ + deleted++; + dev->nFreeChunks++; + /*T((" %d %d deleted\n",blk,c)); */ + } else if (!tags.chunkUsed) { + /* An unassigned chunk in the block + * This means that either the block is empty or + * this is the one being allocated from + */ + + if (c == 0) { + /* We're looking at the first chunk in the block so the block is unused */ + state = YAFFS_BLOCK_STATE_EMPTY; + dev->nErasedBlocks++; + } else { + /* this is the block being allocated from */ + T(YAFFS_TRACE_SCAN, + (TSTR + (" Allocating from %d %d" TENDSTR), + blk, c)); + state = YAFFS_BLOCK_STATE_ALLOCATING; + dev->allocationBlock = blk; + dev->allocationPage = c; + dev->allocationBlockFinder = blk; + /* Set it to here to encourage the allocator to go forth from here. */ + + } + + dev->nFreeChunks += (dev->nChunksPerBlock - c); + } else if (tags.chunkId > 0) { + /* chunkId > 0 so it is a data chunk... */ + unsigned int endpos; + + yaffs_SetChunkBit(dev, blk, c); + bi->pagesInUse++; + + in = yaffs_FindOrCreateObjectByNumber(dev, + tags. + objectId, + YAFFS_OBJECT_TYPE_FILE); + /* PutChunkIntoFile checks for a clash (two data chunks with + * the same chunkId). + */ + + if(!in) + alloc_failed = 1; + + if(in){ + if(!yaffs_PutChunkIntoFile(in, tags.chunkId, chunk,1)) + alloc_failed = 1; + } + + endpos = + (tags.chunkId - 1) * dev->nDataBytesPerChunk + + tags.byteCount; + if (in && + in->variantType == YAFFS_OBJECT_TYPE_FILE + && in->variant.fileVariant.scannedFileSize < + endpos) { + in->variant.fileVariant. + scannedFileSize = endpos; + if (!dev->useHeaderFileSize) { + in->variant.fileVariant. + fileSize = + in->variant.fileVariant. + scannedFileSize; + } + + } + /* T((" %d %d data %d %d\n",blk,c,tags.objectId,tags.chunkId)); */ + } else { + /* chunkId == 0, so it is an ObjectHeader. + * Thus, we read in the object header and make the object + */ + yaffs_SetChunkBit(dev, blk, c); + bi->pagesInUse++; + + result = yaffs_ReadChunkWithTagsFromNAND(dev, chunk, + chunkData, + NULL); + + oh = (yaffs_ObjectHeader *) chunkData; + + in = yaffs_FindObjectByNumber(dev, + tags.objectId); + if (in && in->variantType != oh->type) { + /* This should not happen, but somehow + * Wev'e ended up with an objectId that has been reused but not yet + * deleted, and worse still it has changed type. Delete the old object. + */ + + yaffs_DestroyObject(in); + + in = 0; + } + + in = yaffs_FindOrCreateObjectByNumber(dev, + tags. + objectId, + oh->type); + + if(!in) + alloc_failed = 1; + + if (in && oh->shadowsObject > 0) { + + struct yaffs_ShadowFixerStruct *fixer; + fixer = YMALLOC(sizeof(struct yaffs_ShadowFixerStruct)); + if(fixer){ + fixer-> next = shadowFixerList; + shadowFixerList = fixer; + fixer->objectId = tags.objectId; + fixer->shadowedId = oh->shadowsObject; + } + + } + + if (in && in->valid) { + /* We have already filled this one. We have a duplicate and need to resolve it. */ + + unsigned existingSerial = in->serial; + unsigned newSerial = tags.serialNumber; + + if (((existingSerial + 1) & 3) == newSerial) { + /* Use new one - destroy the exisiting one */ + yaffs_DeleteChunk(dev, + in->hdrChunk, + 1, __LINE__); + in->valid = 0; + } else { + /* Use existing - destroy this one. */ + yaffs_DeleteChunk(dev, chunk, 1, + __LINE__); + } + } + + if (in && !in->valid && + (tags.objectId == YAFFS_OBJECTID_ROOT || + tags.objectId == YAFFS_OBJECTID_LOSTNFOUND)) { + /* We only load some info, don't fiddle with directory structure */ + in->valid = 1; + in->variantType = oh->type; + + in->yst_mode = oh->yst_mode; +#ifdef CONFIG_YAFFS_WINCE + in->win_atime[0] = oh->win_atime[0]; + in->win_ctime[0] = oh->win_ctime[0]; + in->win_mtime[0] = oh->win_mtime[0]; + in->win_atime[1] = oh->win_atime[1]; + in->win_ctime[1] = oh->win_ctime[1]; + in->win_mtime[1] = oh->win_mtime[1]; +#else + in->yst_uid = oh->yst_uid; + in->yst_gid = oh->yst_gid; + in->yst_atime = oh->yst_atime; + in->yst_mtime = oh->yst_mtime; + in->yst_ctime = oh->yst_ctime; + in->yst_rdev = oh->yst_rdev; +#endif + in->hdrChunk = chunk; + in->serial = tags.serialNumber; + + } else if (in && !in->valid) { + /* we need to load this info */ + + in->valid = 1; + in->variantType = oh->type; + + in->yst_mode = oh->yst_mode; +#ifdef CONFIG_YAFFS_WINCE + in->win_atime[0] = oh->win_atime[0]; + in->win_ctime[0] = oh->win_ctime[0]; + in->win_mtime[0] = oh->win_mtime[0]; + in->win_atime[1] = oh->win_atime[1]; + in->win_ctime[1] = oh->win_ctime[1]; + in->win_mtime[1] = oh->win_mtime[1]; +#else + in->yst_uid = oh->yst_uid; + in->yst_gid = oh->yst_gid; + in->yst_atime = oh->yst_atime; + in->yst_mtime = oh->yst_mtime; + in->yst_ctime = oh->yst_ctime; + in->yst_rdev = oh->yst_rdev; +#endif + in->hdrChunk = chunk; + in->serial = tags.serialNumber; + + yaffs_SetObjectName(in, oh->name); + in->dirty = 0; + + /* directory stuff... + * hook up to parent + */ + + parent = + yaffs_FindOrCreateObjectByNumber + (dev, oh->parentObjectId, + YAFFS_OBJECT_TYPE_DIRECTORY); + if (parent->variantType == + YAFFS_OBJECT_TYPE_UNKNOWN) { + /* Set up as a directory */ + parent->variantType = + YAFFS_OBJECT_TYPE_DIRECTORY; + YINIT_LIST_HEAD(&parent->variant. + directoryVariant. + children); + } else if (parent->variantType != + YAFFS_OBJECT_TYPE_DIRECTORY) + { + /* Hoosterman, another problem.... + * We're trying to use a non-directory as a directory + */ + + T(YAFFS_TRACE_ERROR, + (TSTR + ("yaffs tragedy: attempting to use non-directory as a directory in scan. Put in lost+found." + TENDSTR))); + parent = dev->lostNFoundDir; + } + + yaffs_AddObjectToDirectory(parent, in); + + if (0 && (parent == dev->deletedDir || + parent == dev->unlinkedDir)) { + in->deleted = 1; /* If it is unlinked at start up then it wants deleting */ + dev->nDeletedFiles++; + } + /* Note re hardlinks. + * Since we might scan a hardlink before its equivalent object is scanned + * we put them all in a list. + * After scanning is complete, we should have all the objects, so we run through this + * list and fix up all the chains. + */ + + switch (in->variantType) { + case YAFFS_OBJECT_TYPE_UNKNOWN: + /* Todo got a problem */ + break; + case YAFFS_OBJECT_TYPE_FILE: + if (dev->useHeaderFileSize) + + in->variant.fileVariant. + fileSize = + oh->fileSize; + + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + in->variant.hardLinkVariant. + equivalentObjectId = + oh->equivalentObjectId; + in->hardLinks.next = + (struct ylist_head *) + hardList; + hardList = in; + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + /* Do nothing */ + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + /* Do nothing */ + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + in->variant.symLinkVariant.alias = + yaffs_CloneString(oh->alias); + if(!in->variant.symLinkVariant.alias) + alloc_failed = 1; + break; + } + +/* + if (parent == dev->deletedDir) { + yaffs_DestroyObject(in); + bi->hasShrinkHeader = 1; + } +*/ + } + } + } + + if (state == YAFFS_BLOCK_STATE_NEEDS_SCANNING) { + /* If we got this far while scanning, then the block is fully allocated.*/ + state = YAFFS_BLOCK_STATE_FULL; + } + + bi->blockState = state; + + /* Now let's see if it was dirty */ + if (bi->pagesInUse == 0 && + !bi->hasShrinkHeader && + bi->blockState == YAFFS_BLOCK_STATE_FULL) { + yaffs_BlockBecameDirty(dev, blk); + } + + } + + + /* Ok, we've done all the scanning. + * Fix up the hard link chains. + * We should now have scanned all the objects, now it's time to add these + * hardlinks. + */ + + yaffs_HardlinkFixup(dev,hardList); + + /* Fix up any shadowed objects */ + { + struct yaffs_ShadowFixerStruct *fixer; + yaffs_Object *obj; + + while(shadowFixerList){ + fixer = shadowFixerList; + shadowFixerList = fixer->next; + /* Complete the rename transaction by deleting the shadowed object + * then setting the object header to unshadowed. + */ + obj = yaffs_FindObjectByNumber(dev,fixer->shadowedId); + if(obj) + yaffs_DestroyObject(obj); + + obj = yaffs_FindObjectByNumber(dev,fixer->objectId); + if(obj){ + yaffs_UpdateObjectHeader(obj,NULL,1,0,0); + } + + YFREE(fixer); + } + } + + yaffs_ReleaseTempBuffer(dev, chunkData, __LINE__); + + if(alloc_failed){ + return YAFFS_FAIL; + } + + T(YAFFS_TRACE_SCAN, (TSTR("yaffs_Scan ends" TENDSTR))); + + + return YAFFS_OK; +} + +static void yaffs_CheckObjectDetailsLoaded(yaffs_Object *in) +{ + __u8 *chunkData; + yaffs_ObjectHeader *oh; + yaffs_Device *dev; + yaffs_ExtendedTags tags; + int result; + int alloc_failed = 0; + + if(!in) + return; + + dev = in->myDev; + +#if 0 + T(YAFFS_TRACE_SCAN,(TSTR("details for object %d %s loaded" TENDSTR), + in->objectId, + in->lazyLoaded ? "not yet" : "already")); +#endif + + if(in->lazyLoaded && in->hdrChunk > 0){ + in->lazyLoaded = 0; + chunkData = yaffs_GetTempBuffer(dev, __LINE__); + + result = yaffs_ReadChunkWithTagsFromNAND(dev,in->hdrChunk,chunkData,&tags); + oh = (yaffs_ObjectHeader *) chunkData; + + in->yst_mode = oh->yst_mode; +#ifdef CONFIG_YAFFS_WINCE + in->win_atime[0] = oh->win_atime[0]; + in->win_ctime[0] = oh->win_ctime[0]; + in->win_mtime[0] = oh->win_mtime[0]; + in->win_atime[1] = oh->win_atime[1]; + in->win_ctime[1] = oh->win_ctime[1]; + in->win_mtime[1] = oh->win_mtime[1]; +#else + in->yst_uid = oh->yst_uid; + in->yst_gid = oh->yst_gid; + in->yst_atime = oh->yst_atime; + in->yst_mtime = oh->yst_mtime; + in->yst_ctime = oh->yst_ctime; + in->yst_rdev = oh->yst_rdev; + +#endif + yaffs_SetObjectName(in, oh->name); + + if(in->variantType == YAFFS_OBJECT_TYPE_SYMLINK){ + in->variant.symLinkVariant.alias = + yaffs_CloneString(oh->alias); + if(!in->variant.symLinkVariant.alias) + alloc_failed = 1; /* Not returned to caller */ + } + + yaffs_ReleaseTempBuffer(dev,chunkData, __LINE__); + } +} + +static int yaffs_ScanBackwards(yaffs_Device * dev) +{ + yaffs_ExtendedTags tags; + int blk; + int blockIterator; + int startIterator; + int endIterator; + int nBlocksToScan = 0; + + int chunk; + int result; + int c; + int deleted; + yaffs_BlockState state; + yaffs_Object *hardList = NULL; + yaffs_BlockInfo *bi; + __u32 sequenceNumber; + yaffs_ObjectHeader *oh; + yaffs_Object *in; + yaffs_Object *parent; + int nBlocks = dev->internalEndBlock - dev->internalStartBlock + 1; + int itsUnlinked; + __u8 *chunkData; + + int fileSize; + int isShrink; + int foundChunksInBlock; + int equivalentObjectId; + int alloc_failed = 0; + + + yaffs_BlockIndex *blockIndex = NULL; + int altBlockIndex = 0; + + if (!dev->isYaffs2) { + T(YAFFS_TRACE_SCAN, + (TSTR("yaffs_ScanBackwards is only for YAFFS2!" TENDSTR))); + return YAFFS_FAIL; + } + + T(YAFFS_TRACE_SCAN, + (TSTR + ("yaffs_ScanBackwards starts intstartblk %d intendblk %d..." + TENDSTR), dev->internalStartBlock, dev->internalEndBlock)); + + + dev->sequenceNumber = YAFFS_LOWEST_SEQUENCE_NUMBER; + + blockIndex = YMALLOC(nBlocks * sizeof(yaffs_BlockIndex)); + + if(!blockIndex) { + blockIndex = YMALLOC_ALT(nBlocks * sizeof(yaffs_BlockIndex)); + altBlockIndex = 1; + } + + if(!blockIndex) { + T(YAFFS_TRACE_SCAN, + (TSTR("yaffs_Scan() could not allocate block index!" TENDSTR))); + return YAFFS_FAIL; + } + + dev->blocksInCheckpoint = 0; + + chunkData = yaffs_GetTempBuffer(dev, __LINE__); + + /* Scan all the blocks to determine their state */ + for (blk = dev->internalStartBlock; blk <= dev->internalEndBlock; blk++) { + bi = yaffs_GetBlockInfo(dev, blk); + yaffs_ClearChunkBits(dev, blk); + bi->pagesInUse = 0; + bi->softDeletions = 0; + + yaffs_QueryInitialBlockState(dev, blk, &state, &sequenceNumber); + + bi->blockState = state; + bi->sequenceNumber = sequenceNumber; + + if(bi->sequenceNumber == YAFFS_SEQUENCE_CHECKPOINT_DATA) + bi->blockState = state = YAFFS_BLOCK_STATE_CHECKPOINT; + + T(YAFFS_TRACE_SCAN_DEBUG, + (TSTR("Block scanning block %d state %d seq %d" TENDSTR), blk, + state, sequenceNumber)); + + + if(state == YAFFS_BLOCK_STATE_CHECKPOINT){ + dev->blocksInCheckpoint++; + + } else if (state == YAFFS_BLOCK_STATE_DEAD) { + T(YAFFS_TRACE_BAD_BLOCKS, + (TSTR("block %d is bad" TENDSTR), blk)); + } else if (state == YAFFS_BLOCK_STATE_EMPTY) { + T(YAFFS_TRACE_SCAN_DEBUG, + (TSTR("Block empty " TENDSTR))); + dev->nErasedBlocks++; + dev->nFreeChunks += dev->nChunksPerBlock; + } else if (state == YAFFS_BLOCK_STATE_NEEDS_SCANNING) { + + /* Determine the highest sequence number */ + if (sequenceNumber >= YAFFS_LOWEST_SEQUENCE_NUMBER && + sequenceNumber < YAFFS_HIGHEST_SEQUENCE_NUMBER) { + + blockIndex[nBlocksToScan].seq = sequenceNumber; + blockIndex[nBlocksToScan].block = blk; + + nBlocksToScan++; + + if (sequenceNumber >= dev->sequenceNumber) { + dev->sequenceNumber = sequenceNumber; + } + } else { + /* TODO: Nasty sequence number! */ + T(YAFFS_TRACE_SCAN, + (TSTR + ("Block scanning block %d has bad sequence number %d" + TENDSTR), blk, sequenceNumber)); + + } + } + } + + T(YAFFS_TRACE_SCAN, + (TSTR("%d blocks to be sorted..." TENDSTR), nBlocksToScan)); + + + + YYIELD(); + + /* Sort the blocks */ +#ifndef CONFIG_YAFFS_USE_OWN_SORT + { + /* Use qsort now. */ + yaffs_qsort(blockIndex, nBlocksToScan, sizeof(yaffs_BlockIndex), ybicmp); + } +#else + { + /* Dungy old bubble sort... */ + + yaffs_BlockIndex temp; + int i; + int j; + + for (i = 0; i < nBlocksToScan; i++) + for (j = i + 1; j < nBlocksToScan; j++) + if (blockIndex[i].seq > blockIndex[j].seq) { + temp = blockIndex[j]; + blockIndex[j] = blockIndex[i]; + blockIndex[i] = temp; + } + } +#endif + + YYIELD(); + + T(YAFFS_TRACE_SCAN, (TSTR("...done" TENDSTR))); + + /* Now scan the blocks looking at the data. */ + startIterator = 0; + endIterator = nBlocksToScan - 1; + T(YAFFS_TRACE_SCAN_DEBUG, + (TSTR("%d blocks to be scanned" TENDSTR), nBlocksToScan)); + + /* For each block.... backwards */ + for (blockIterator = endIterator; !alloc_failed && blockIterator >= startIterator; + blockIterator--) { + /* Cooperative multitasking! This loop can run for so + long that watchdog timers expire. */ + YYIELD(); + + /* get the block to scan in the correct order */ + blk = blockIndex[blockIterator].block; + + bi = yaffs_GetBlockInfo(dev, blk); + + + state = bi->blockState; + + deleted = 0; + + /* For each chunk in each block that needs scanning.... */ + foundChunksInBlock = 0; + for (c = dev->nChunksPerBlock - 1; + !alloc_failed && c >= 0 && + (state == YAFFS_BLOCK_STATE_NEEDS_SCANNING || + state == YAFFS_BLOCK_STATE_ALLOCATING); c--) { + /* Scan backwards... + * Read the tags and decide what to do + */ + + chunk = blk * dev->nChunksPerBlock + c; + + result = yaffs_ReadChunkWithTagsFromNAND(dev, chunk, NULL, + &tags); + + /* Let's have a good look at this chunk... */ + + if (!tags.chunkUsed) { + /* An unassigned chunk in the block. + * If there are used chunks after this one, then + * it is a chunk that was skipped due to failing the erased + * check. Just skip it so that it can be deleted. + * But, more typically, We get here when this is an unallocated + * chunk and his means that either the block is empty or + * this is the one being allocated from + */ + + if(foundChunksInBlock) + { + /* This is a chunk that was skipped due to failing the erased check */ + + } else if (c == 0) { + /* We're looking at the first chunk in the block so the block is unused */ + state = YAFFS_BLOCK_STATE_EMPTY; + dev->nErasedBlocks++; + } else { + if (state == YAFFS_BLOCK_STATE_NEEDS_SCANNING || + state == YAFFS_BLOCK_STATE_ALLOCATING) { + if(dev->sequenceNumber == bi->sequenceNumber) { + /* this is the block being allocated from */ + + T(YAFFS_TRACE_SCAN, + (TSTR + (" Allocating from %d %d" + TENDSTR), blk, c)); + + state = YAFFS_BLOCK_STATE_ALLOCATING; + dev->allocationBlock = blk; + dev->allocationPage = c; + dev->allocationBlockFinder = blk; + } + else { + /* This is a partially written block that is not + * the current allocation block. This block must have + * had a write failure, so set up for retirement. + */ + + /* bi->needsRetiring = 1; ??? TODO */ + bi->gcPrioritise = 1; + + T(YAFFS_TRACE_ALWAYS, + (TSTR("Partially written block %d detected" TENDSTR), + blk)); + } + + } + + } + + dev->nFreeChunks++; + + } else if (tags.chunkId > 0) { + /* chunkId > 0 so it is a data chunk... */ + unsigned int endpos; + __u32 chunkBase = + (tags.chunkId - 1) * dev->nDataBytesPerChunk; + + foundChunksInBlock = 1; + + + yaffs_SetChunkBit(dev, blk, c); + bi->pagesInUse++; + + in = yaffs_FindOrCreateObjectByNumber(dev, + tags. + objectId, + YAFFS_OBJECT_TYPE_FILE); + if(!in){ + /* Out of memory */ + alloc_failed = 1; + } + + if (in && + in->variantType == YAFFS_OBJECT_TYPE_FILE + && chunkBase < + in->variant.fileVariant.shrinkSize) { + /* This has not been invalidated by a resize */ + if(!yaffs_PutChunkIntoFile(in, tags.chunkId, + chunk, -1)){ + alloc_failed = 1; + } + + /* File size is calculated by looking at the data chunks if we have not + * seen an object header yet. Stop this practice once we find an object header. + */ + endpos = + (tags.chunkId - + 1) * dev->nDataBytesPerChunk + + tags.byteCount; + + if (!in->valid && /* have not got an object header yet */ + in->variant.fileVariant. + scannedFileSize < endpos) { + in->variant.fileVariant. + scannedFileSize = endpos; + in->variant.fileVariant. + fileSize = + in->variant.fileVariant. + scannedFileSize; + } + + } else if(in) { + /* This chunk has been invalidated by a resize, so delete */ + yaffs_DeleteChunk(dev, chunk, 1, __LINE__); + + } + } else { + /* chunkId == 0, so it is an ObjectHeader. + * Thus, we read in the object header and make the object + */ + foundChunksInBlock = 1; + + yaffs_SetChunkBit(dev, blk, c); + bi->pagesInUse++; + + oh = NULL; + in = NULL; + + if (tags.extraHeaderInfoAvailable) { + in = yaffs_FindOrCreateObjectByNumber + (dev, tags.objectId, + tags.extraObjectType); + } + + if (!in || +#ifdef CONFIG_YAFFS_DISABLE_LAZY_LOAD + !in->valid || +#endif + tags.extraShadows || + (!in->valid && + (tags.objectId == YAFFS_OBJECTID_ROOT || + tags.objectId == YAFFS_OBJECTID_LOSTNFOUND)) + ) { + + /* If we don't have valid info then we need to read the chunk + * TODO In future we can probably defer reading the chunk and + * living with invalid data until needed. + */ + + result = yaffs_ReadChunkWithTagsFromNAND(dev, + chunk, + chunkData, + NULL); + + oh = (yaffs_ObjectHeader *) chunkData; + + if(dev->inbandTags){ + /* Fix up the header if they got corrupted by inband tags */ + oh->shadowsObject = oh->inbandShadowsObject; + oh->isShrink = oh->inbandIsShrink; + } + + if (!in) + in = yaffs_FindOrCreateObjectByNumber(dev, tags.objectId, oh->type); + + } + + if (!in) { + /* TODO Hoosterman we have a problem! */ + T(YAFFS_TRACE_ERROR, + (TSTR + ("yaffs tragedy: Could not make object for object %d at chunk %d during scan" + TENDSTR), tags.objectId, chunk)); + + } + + if (in->valid) { + /* We have already filled this one. + * We have a duplicate that will be discarded, but + * we first have to suck out resize info if it is a file. + */ + + if ((in->variantType == YAFFS_OBJECT_TYPE_FILE) && + ((oh && + oh-> type == YAFFS_OBJECT_TYPE_FILE)|| + (tags.extraHeaderInfoAvailable && + tags.extraObjectType == YAFFS_OBJECT_TYPE_FILE)) + ) { + __u32 thisSize = + (oh) ? oh->fileSize : tags. + extraFileLength; + __u32 parentObjectId = + (oh) ? oh-> + parentObjectId : tags. + extraParentObjectId; + + + isShrink = + (oh) ? oh->isShrink : tags. + extraIsShrinkHeader; + + /* If it is deleted (unlinked at start also means deleted) + * we treat the file size as being zeroed at this point. + */ + if (parentObjectId == + YAFFS_OBJECTID_DELETED + || parentObjectId == + YAFFS_OBJECTID_UNLINKED) { + thisSize = 0; + isShrink = 1; + } + + if (isShrink && + in->variant.fileVariant. + shrinkSize > thisSize) { + in->variant.fileVariant. + shrinkSize = + thisSize; + } + + if (isShrink) { + bi->hasShrinkHeader = 1; + } + + } + /* Use existing - destroy this one. */ + yaffs_DeleteChunk(dev, chunk, 1, __LINE__); + + } + + if (!in->valid && + (tags.objectId == YAFFS_OBJECTID_ROOT || + tags.objectId == + YAFFS_OBJECTID_LOSTNFOUND)) { + /* We only load some info, don't fiddle with directory structure */ + in->valid = 1; + + if(oh) { + in->variantType = oh->type; + + in->yst_mode = oh->yst_mode; +#ifdef CONFIG_YAFFS_WINCE + in->win_atime[0] = oh->win_atime[0]; + in->win_ctime[0] = oh->win_ctime[0]; + in->win_mtime[0] = oh->win_mtime[0]; + in->win_atime[1] = oh->win_atime[1]; + in->win_ctime[1] = oh->win_ctime[1]; + in->win_mtime[1] = oh->win_mtime[1]; +#else + in->yst_uid = oh->yst_uid; + in->yst_gid = oh->yst_gid; + in->yst_atime = oh->yst_atime; + in->yst_mtime = oh->yst_mtime; + in->yst_ctime = oh->yst_ctime; + in->yst_rdev = oh->yst_rdev; + +#endif + } else { + in->variantType = tags.extraObjectType; + in->lazyLoaded = 1; + } + + in->hdrChunk = chunk; + + } else if (!in->valid) { + /* we need to load this info */ + + in->valid = 1; + in->hdrChunk = chunk; + + if(oh) { + in->variantType = oh->type; + + in->yst_mode = oh->yst_mode; +#ifdef CONFIG_YAFFS_WINCE + in->win_atime[0] = oh->win_atime[0]; + in->win_ctime[0] = oh->win_ctime[0]; + in->win_mtime[0] = oh->win_mtime[0]; + in->win_atime[1] = oh->win_atime[1]; + in->win_ctime[1] = oh->win_ctime[1]; + in->win_mtime[1] = oh->win_mtime[1]; +#else + in->yst_uid = oh->yst_uid; + in->yst_gid = oh->yst_gid; + in->yst_atime = oh->yst_atime; + in->yst_mtime = oh->yst_mtime; + in->yst_ctime = oh->yst_ctime; + in->yst_rdev = oh->yst_rdev; +#endif + + if (oh->shadowsObject > 0) + yaffs_HandleShadowedObject(dev, + oh-> + shadowsObject, + 1); + + + yaffs_SetObjectName(in, oh->name); + parent = + yaffs_FindOrCreateObjectByNumber + (dev, oh->parentObjectId, + YAFFS_OBJECT_TYPE_DIRECTORY); + + fileSize = oh->fileSize; + isShrink = oh->isShrink; + equivalentObjectId = oh->equivalentObjectId; + + } + else { + in->variantType = tags.extraObjectType; + parent = + yaffs_FindOrCreateObjectByNumber + (dev, tags.extraParentObjectId, + YAFFS_OBJECT_TYPE_DIRECTORY); + fileSize = tags.extraFileLength; + isShrink = tags.extraIsShrinkHeader; + equivalentObjectId = tags.extraEquivalentObjectId; + in->lazyLoaded = 1; + + } + in->dirty = 0; + + /* directory stuff... + * hook up to parent + */ + + if (parent->variantType == + YAFFS_OBJECT_TYPE_UNKNOWN) { + /* Set up as a directory */ + parent->variantType = + YAFFS_OBJECT_TYPE_DIRECTORY; + YINIT_LIST_HEAD(&parent->variant. + directoryVariant. + children); + } else if (parent->variantType != + YAFFS_OBJECT_TYPE_DIRECTORY) + { + /* Hoosterman, another problem.... + * We're trying to use a non-directory as a directory + */ + + T(YAFFS_TRACE_ERROR, + (TSTR + ("yaffs tragedy: attempting to use non-directory as a directory in scan. Put in lost+found." + TENDSTR))); + parent = dev->lostNFoundDir; + } + + yaffs_AddObjectToDirectory(parent, in); + + itsUnlinked = (parent == dev->deletedDir) || + (parent == dev->unlinkedDir); + + if (isShrink) { + /* Mark the block as having a shrinkHeader */ + bi->hasShrinkHeader = 1; + } + + /* Note re hardlinks. + * Since we might scan a hardlink before its equivalent object is scanned + * we put them all in a list. + * After scanning is complete, we should have all the objects, so we run + * through this list and fix up all the chains. + */ + + switch (in->variantType) { + case YAFFS_OBJECT_TYPE_UNKNOWN: + /* Todo got a problem */ + break; + case YAFFS_OBJECT_TYPE_FILE: + + if (in->variant.fileVariant. + scannedFileSize < fileSize) { + /* This covers the case where the file size is greater + * than where the data is + * This will happen if the file is resized to be larger + * than its current data extents. + */ + in->variant.fileVariant.fileSize = fileSize; + in->variant.fileVariant.scannedFileSize = + in->variant.fileVariant.fileSize; + } + + if (isShrink && + in->variant.fileVariant.shrinkSize > fileSize) { + in->variant.fileVariant.shrinkSize = fileSize; + } + + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + if(!itsUnlinked) { + in->variant.hardLinkVariant.equivalentObjectId = + equivalentObjectId; + in->hardLinks.next = + (struct ylist_head *) hardList; + hardList = in; + } + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + /* Do nothing */ + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + /* Do nothing */ + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + if(oh){ + in->variant.symLinkVariant.alias = + yaffs_CloneString(oh-> + alias); + if(!in->variant.symLinkVariant.alias) + alloc_failed = 1; + } + break; + } + + } + + } + + } /* End of scanning for each chunk */ + + if (state == YAFFS_BLOCK_STATE_NEEDS_SCANNING) { + /* If we got this far while scanning, then the block is fully allocated. */ + state = YAFFS_BLOCK_STATE_FULL; + } + + bi->blockState = state; + + /* Now let's see if it was dirty */ + if (bi->pagesInUse == 0 && + !bi->hasShrinkHeader && + bi->blockState == YAFFS_BLOCK_STATE_FULL) { + yaffs_BlockBecameDirty(dev, blk); + } + + } + + if (altBlockIndex) + YFREE_ALT(blockIndex); + else + YFREE(blockIndex); + + /* Ok, we've done all the scanning. + * Fix up the hard link chains. + * We should now have scanned all the objects, now it's time to add these + * hardlinks. + */ + yaffs_HardlinkFixup(dev,hardList); + + + yaffs_ReleaseTempBuffer(dev, chunkData, __LINE__); + + if(alloc_failed){ + return YAFFS_FAIL; + } + + T(YAFFS_TRACE_SCAN, (TSTR("yaffs_ScanBackwards ends" TENDSTR))); + + return YAFFS_OK; +} + +/*------------------------------ Directory Functions ----------------------------- */ + +static void yaffs_VerifyObjectInDirectory(yaffs_Object *obj) +{ + struct ylist_head *lh; + yaffs_Object *listObj; + + int count = 0; + + if(!obj){ + T(YAFFS_TRACE_ALWAYS, (TSTR("No object to verify" TENDSTR))); + YBUG(); + } + + if(yaffs_SkipVerification(obj->myDev)) + return; + + if(!obj->parent){ + T(YAFFS_TRACE_ALWAYS, (TSTR("Object does not have parent" TENDSTR))); + YBUG(); + } + + if(obj->parent->variantType != YAFFS_OBJECT_TYPE_DIRECTORY){ + T(YAFFS_TRACE_ALWAYS, (TSTR("Parent is not directory" TENDSTR))); + YBUG(); + } + + /* Iterate through the objects in each hash entry */ + + ylist_for_each(lh, &obj->parent->variant.directoryVariant.children) { + if (lh) { + listObj = ylist_entry(lh, yaffs_Object, siblings); + yaffs_VerifyObject(listObj); + if(obj == listObj) + count ++; + } + } + + if(count != 1){ + T(YAFFS_TRACE_ALWAYS, (TSTR("Object in directory %d times" TENDSTR),count)); + YBUG(); + } + +} + +static void yaffs_VerifyDirectory(yaffs_Object *directory) +{ + + struct ylist_head *lh; + yaffs_Object *listObj; + + if(!directory) + YBUG(); + + if(yaffs_SkipFullVerification(directory->myDev)) + return; + + + if(directory->variantType != YAFFS_OBJECT_TYPE_DIRECTORY){ + T(YAFFS_TRACE_ALWAYS, (TSTR("Directory has wrong type: %d" TENDSTR),directory->variantType)); + YBUG(); + } + + /* Iterate through the objects in each hash entry */ + + ylist_for_each(lh, &directory->variant.directoryVariant.children) { + if (lh) { + listObj = ylist_entry(lh, yaffs_Object, siblings); + if(listObj->parent != directory){ + T(YAFFS_TRACE_ALWAYS, (TSTR("Object in directory list has wrong parent %p" TENDSTR),listObj->parent)); + YBUG(); + } + yaffs_VerifyObjectInDirectory(listObj); + } + } + +} + + +static void yaffs_RemoveObjectFromDirectory(yaffs_Object * obj) +{ + yaffs_Device *dev = obj->myDev; + yaffs_Object *parent; + + yaffs_VerifyObjectInDirectory(obj); + parent = obj->parent; + + yaffs_VerifyDirectory(parent); + + if(dev && dev->removeObjectCallback) + dev->removeObjectCallback(obj); + + + ylist_del_init(&obj->siblings); + obj->parent = NULL; + + yaffs_VerifyDirectory(parent); + +} + + +static void yaffs_AddObjectToDirectory(yaffs_Object * directory, + yaffs_Object * obj) +{ + + if (!directory) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: Trying to add an object to a null pointer directory" + TENDSTR))); + YBUG(); + } + if (directory->variantType != YAFFS_OBJECT_TYPE_DIRECTORY) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: Trying to add an object to a non-directory" + TENDSTR))); + YBUG(); + } + + if (obj->siblings.prev == NULL) { + /* Not initialised */ + YBUG(); + + } else if (ylist_empty(&obj->siblings)) { + YBUG(); + } + + + yaffs_VerifyDirectory(directory); + + yaffs_RemoveObjectFromDirectory(obj); + + + /* Now add it */ + ylist_add(&obj->siblings, &directory->variant.directoryVariant.children); + obj->parent = directory; + + if (directory == obj->myDev->unlinkedDir + || directory == obj->myDev->deletedDir) { + obj->unlinked = 1; + obj->myDev->nUnlinkedFiles++; + obj->renameAllowed = 0; + } + + yaffs_VerifyDirectory(directory); + yaffs_VerifyObjectInDirectory(obj); + + +} + +yaffs_Object *yaffs_FindObjectByName(yaffs_Object * directory, + const YCHAR * name) +{ + int sum; + + struct ylist_head *i; + YCHAR buffer[YAFFS_MAX_NAME_LENGTH + 1]; + + yaffs_Object *l; + + if (!name) { + return NULL; + } + + if (!directory) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: yaffs_FindObjectByName: null pointer directory" + TENDSTR))); + YBUG(); + } + if (directory->variantType != YAFFS_OBJECT_TYPE_DIRECTORY) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: yaffs_FindObjectByName: non-directory" TENDSTR))); + YBUG(); + } + + sum = yaffs_CalcNameSum(name); + + ylist_for_each(i, &directory->variant.directoryVariant.children) { + if (i) { + l = ylist_entry(i, yaffs_Object, siblings); + + if(l->parent != directory) + YBUG(); + + yaffs_CheckObjectDetailsLoaded(l); + + /* Special case for lost-n-found */ + if (l->objectId == YAFFS_OBJECTID_LOSTNFOUND) { + if (yaffs_strcmp(name, YAFFS_LOSTNFOUND_NAME) == 0) { + return l; + } + } else if (yaffs_SumCompare(l->sum, sum) || l->hdrChunk <= 0){ + /* LostnFound chunk called Objxxx + * Do a real check + */ + yaffs_GetObjectName(l, buffer, + YAFFS_MAX_NAME_LENGTH); + if (yaffs_strncmp(name, buffer,YAFFS_MAX_NAME_LENGTH) == 0) { + return l; + } + + } + } + } + + return NULL; +} + + +#if 0 +int yaffs_ApplyToDirectoryChildren(yaffs_Object * theDir, + int (*fn) (yaffs_Object *)) +{ + struct ylist_head *i; + yaffs_Object *l; + + if (!theDir) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: yaffs_FindObjectByName: null pointer directory" + TENDSTR))); + YBUG(); + } + if (theDir->variantType != YAFFS_OBJECT_TYPE_DIRECTORY) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("tragedy: yaffs_FindObjectByName: non-directory" TENDSTR))); + YBUG(); + } + + ylist_for_each(i, &theDir->variant.directoryVariant.children) { + if (i) { + l = ylist_entry(i, yaffs_Object, siblings); + if (l && !fn(l)) { + return YAFFS_FAIL; + } + } + } + + return YAFFS_OK; + +} +#endif + +/* GetEquivalentObject dereferences any hard links to get to the + * actual object. + */ + +yaffs_Object *yaffs_GetEquivalentObject(yaffs_Object * obj) +{ + if (obj && obj->variantType == YAFFS_OBJECT_TYPE_HARDLINK) { + /* We want the object id of the equivalent object, not this one */ + obj = obj->variant.hardLinkVariant.equivalentObject; + yaffs_CheckObjectDetailsLoaded(obj); + } + return obj; + +} + +int yaffs_GetObjectName(yaffs_Object * obj, YCHAR * name, int buffSize) +{ + memset(name, 0, buffSize * sizeof(YCHAR)); + + yaffs_CheckObjectDetailsLoaded(obj); + + if (obj->objectId == YAFFS_OBJECTID_LOSTNFOUND) { + yaffs_strncpy(name, YAFFS_LOSTNFOUND_NAME, buffSize - 1); + } else if (obj->hdrChunk <= 0) { + YCHAR locName[20]; + YCHAR numString[20]; + YCHAR *x = &numString[19]; + unsigned v = obj->objectId; + numString[19] = 0; + while(v>0){ + x--; + *x = '0' + (v % 10); + v /= 10; + } + /* make up a name */ + yaffs_strcpy(locName, YAFFS_LOSTNFOUND_PREFIX); + yaffs_strcat(locName,x); + yaffs_strncpy(name, locName, buffSize - 1); + + } +#ifdef CONFIG_YAFFS_SHORT_NAMES_IN_RAM + else if (obj->shortName[0]) { + yaffs_strcpy(name, obj->shortName); + } +#endif + else { + int result; + __u8 *buffer = yaffs_GetTempBuffer(obj->myDev, __LINE__); + + yaffs_ObjectHeader *oh = (yaffs_ObjectHeader *) buffer; + + memset(buffer, 0, obj->myDev->nDataBytesPerChunk); + + if (obj->hdrChunk > 0) { + result = yaffs_ReadChunkWithTagsFromNAND(obj->myDev, + obj->hdrChunk, buffer, + NULL); + } + yaffs_strncpy(name, oh->name, buffSize - 1); + + yaffs_ReleaseTempBuffer(obj->myDev, buffer, __LINE__); + } + + return yaffs_strlen(name); +} + +int yaffs_GetObjectFileLength(yaffs_Object * obj) +{ + + /* Dereference any hard linking */ + obj = yaffs_GetEquivalentObject(obj); + + if (obj->variantType == YAFFS_OBJECT_TYPE_FILE) { + return obj->variant.fileVariant.fileSize; + } + if (obj->variantType == YAFFS_OBJECT_TYPE_SYMLINK) { + return yaffs_strlen(obj->variant.symLinkVariant.alias); + } else { + /* Only a directory should drop through to here */ + return obj->myDev->nDataBytesPerChunk; + } +} + +int yaffs_GetObjectLinkCount(yaffs_Object * obj) +{ + int count = 0; + struct ylist_head *i; + + if (!obj->unlinked) { + count++; /* the object itself */ + } + ylist_for_each(i, &obj->hardLinks) { + count++; /* add the hard links; */ + } + return count; + +} + +int yaffs_GetObjectInode(yaffs_Object * obj) +{ + obj = yaffs_GetEquivalentObject(obj); + + return obj->objectId; +} + +unsigned yaffs_GetObjectType(yaffs_Object * obj) +{ + obj = yaffs_GetEquivalentObject(obj); + + switch (obj->variantType) { + case YAFFS_OBJECT_TYPE_FILE: + return DT_REG; + break; + case YAFFS_OBJECT_TYPE_DIRECTORY: + return DT_DIR; + break; + case YAFFS_OBJECT_TYPE_SYMLINK: + return DT_LNK; + break; + case YAFFS_OBJECT_TYPE_HARDLINK: + return DT_REG; + break; + case YAFFS_OBJECT_TYPE_SPECIAL: + if (S_ISFIFO(obj->yst_mode)) + return DT_FIFO; + if (S_ISCHR(obj->yst_mode)) + return DT_CHR; + if (S_ISBLK(obj->yst_mode)) + return DT_BLK; + if (S_ISSOCK(obj->yst_mode)) + return DT_SOCK; + default: + return DT_REG; + break; + } +} + +YCHAR *yaffs_GetSymlinkAlias(yaffs_Object * obj) +{ + obj = yaffs_GetEquivalentObject(obj); + if (obj->variantType == YAFFS_OBJECT_TYPE_SYMLINK) { + return yaffs_CloneString(obj->variant.symLinkVariant.alias); + } else { + return yaffs_CloneString(_Y("")); + } +} + +#ifndef CONFIG_YAFFS_WINCE + +int yaffs_SetAttributes(yaffs_Object * obj, struct iattr *attr) +{ + unsigned int valid = attr->ia_valid; + + if (valid & ATTR_MODE) + obj->yst_mode = attr->ia_mode; + if (valid & ATTR_UID) + obj->yst_uid = attr->ia_uid; + if (valid & ATTR_GID) + obj->yst_gid = attr->ia_gid; + + if (valid & ATTR_ATIME) + obj->yst_atime = Y_TIME_CONVERT(attr->ia_atime); + if (valid & ATTR_CTIME) + obj->yst_ctime = Y_TIME_CONVERT(attr->ia_ctime); + if (valid & ATTR_MTIME) + obj->yst_mtime = Y_TIME_CONVERT(attr->ia_mtime); + + if (valid & ATTR_SIZE) + yaffs_ResizeFile(obj, attr->ia_size); + + yaffs_UpdateObjectHeader(obj, NULL, 1, 0, 0); + + return YAFFS_OK; + +} +int yaffs_GetAttributes(yaffs_Object * obj, struct iattr *attr) +{ + unsigned int valid = 0; + + attr->ia_mode = obj->yst_mode; + valid |= ATTR_MODE; + attr->ia_uid = obj->yst_uid; + valid |= ATTR_UID; + attr->ia_gid = obj->yst_gid; + valid |= ATTR_GID; + + Y_TIME_CONVERT(attr->ia_atime) = obj->yst_atime; + valid |= ATTR_ATIME; + Y_TIME_CONVERT(attr->ia_ctime) = obj->yst_ctime; + valid |= ATTR_CTIME; + Y_TIME_CONVERT(attr->ia_mtime) = obj->yst_mtime; + valid |= ATTR_MTIME; + + attr->ia_size = yaffs_GetFileSize(obj); + valid |= ATTR_SIZE; + + attr->ia_valid = valid; + + return YAFFS_OK; + +} + +#endif + +#if 0 +int yaffs_DumpObject(yaffs_Object * obj) +{ + YCHAR name[257]; + + yaffs_GetObjectName(obj, name, 256); + + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("Object %d, inode %d \"%s\"\n dirty %d valid %d serial %d sum %d" + " chunk %d type %d size %d\n" + TENDSTR), obj->objectId, yaffs_GetObjectInode(obj), name, + obj->dirty, obj->valid, obj->serial, obj->sum, obj->hdrChunk, + yaffs_GetObjectType(obj), yaffs_GetObjectFileLength(obj))); + + return YAFFS_OK; +} +#endif + +/*---------------------------- Initialisation code -------------------------------------- */ + +static int yaffs_CheckDevFunctions(const yaffs_Device * dev) +{ + + /* Common functions, gotta have */ + if (!dev->eraseBlockInNAND || !dev->initialiseNAND) + return 0; + +#ifdef CONFIG_YAFFS_YAFFS2 + + /* Can use the "with tags" style interface for yaffs1 or yaffs2 */ + if (dev->writeChunkWithTagsToNAND && + dev->readChunkWithTagsFromNAND && + !dev->writeChunkToNAND && + !dev->readChunkFromNAND && + dev->markNANDBlockBad && dev->queryNANDBlock) + return 1; +#endif + + /* Can use the "spare" style interface for yaffs1 */ + if (!dev->isYaffs2 && + !dev->writeChunkWithTagsToNAND && + !dev->readChunkWithTagsFromNAND && + dev->writeChunkToNAND && + dev->readChunkFromNAND && + !dev->markNANDBlockBad && !dev->queryNANDBlock) + return 1; + + return 0; /* bad */ +} + + +static int yaffs_CreateInitialDirectories(yaffs_Device *dev) +{ + /* Initialise the unlinked, deleted, root and lost and found directories */ + + dev->lostNFoundDir = dev->rootDir = NULL; + dev->unlinkedDir = dev->deletedDir = NULL; + + dev->unlinkedDir = + yaffs_CreateFakeDirectory(dev, YAFFS_OBJECTID_UNLINKED, S_IFDIR); + + dev->deletedDir = + yaffs_CreateFakeDirectory(dev, YAFFS_OBJECTID_DELETED, S_IFDIR); + + dev->rootDir = + yaffs_CreateFakeDirectory(dev, YAFFS_OBJECTID_ROOT, + YAFFS_ROOT_MODE | S_IFDIR); + dev->lostNFoundDir = + yaffs_CreateFakeDirectory(dev, YAFFS_OBJECTID_LOSTNFOUND, + YAFFS_LOSTNFOUND_MODE | S_IFDIR); + + if(dev->lostNFoundDir && dev->rootDir && dev->unlinkedDir && dev->deletedDir){ + yaffs_AddObjectToDirectory(dev->rootDir, dev->lostNFoundDir); + return YAFFS_OK; + } + + return YAFFS_FAIL; +} + +int yaffs_GutsInitialise(yaffs_Device * dev) +{ + int init_failed = 0; + unsigned x; + int bits; + + T(YAFFS_TRACE_TRACING, (TSTR("yaffs: yaffs_GutsInitialise()" TENDSTR))); + + /* Check stuff that must be set */ + + if (!dev) { + T(YAFFS_TRACE_ALWAYS, (TSTR("yaffs: Need a device" TENDSTR))); + return YAFFS_FAIL; + } + + dev->internalStartBlock = dev->startBlock; + dev->internalEndBlock = dev->endBlock; + dev->blockOffset = 0; + dev->chunkOffset = 0; + dev->nFreeChunks = 0; + + dev->gcBlock = -1; + + if (dev->startBlock == 0) { + dev->internalStartBlock = dev->startBlock + 1; + dev->internalEndBlock = dev->endBlock + 1; + dev->blockOffset = 1; + dev->chunkOffset = dev->nChunksPerBlock; + } + + /* Check geometry parameters. */ + + if ((!dev->inbandTags && dev->isYaffs2 && dev->totalBytesPerChunk < 1024) || + (!dev->isYaffs2 && dev->totalBytesPerChunk < 512) || + (dev->inbandTags && !dev->isYaffs2 ) || + dev->nChunksPerBlock < 2 || + dev->nReservedBlocks < 2 || + dev->internalStartBlock <= 0 || + dev->internalEndBlock <= 0 || + dev->internalEndBlock <= (dev->internalStartBlock + dev->nReservedBlocks + 2) // otherwise it is too small + ) { + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("yaffs: NAND geometry problems: chunk size %d, type is yaffs%s, inbandTags %d " + TENDSTR), dev->totalBytesPerChunk, dev->isYaffs2 ? "2" : "", dev->inbandTags)); + return YAFFS_FAIL; + } + + if (yaffs_InitialiseNAND(dev) != YAFFS_OK) { + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: InitialiseNAND failed" TENDSTR))); + return YAFFS_FAIL; + } + + /* Sort out space for inband tags, if required */ + if(dev->inbandTags) + dev->nDataBytesPerChunk = dev->totalBytesPerChunk - sizeof(yaffs_PackedTags2TagsPart); + else + dev->nDataBytesPerChunk = dev->totalBytesPerChunk; + + /* Got the right mix of functions? */ + if (!yaffs_CheckDevFunctions(dev)) { + /* Function missing */ + T(YAFFS_TRACE_ALWAYS, + (TSTR + ("yaffs: device function(s) missing or wrong\n" TENDSTR))); + + return YAFFS_FAIL; + } + + /* This is really a compilation check. */ + if (!yaffs_CheckStructures()) { + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs_CheckStructures failed\n" TENDSTR))); + return YAFFS_FAIL; + } + + if (dev->isMounted) { + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: device already mounted\n" TENDSTR))); + return YAFFS_FAIL; + } + + /* Finished with most checks. One or two more checks happen later on too. */ + + dev->isMounted = 1; + + /* OK now calculate a few things for the device */ + + /* + * Calculate all the chunk size manipulation numbers: + */ + x = dev->nDataBytesPerChunk; + /* We always use dev->chunkShift and dev->chunkDiv */ + dev->chunkShift = Shifts(x); + x >>= dev->chunkShift; + dev->chunkDiv = x; + /* We only use chunk mask if chunkDiv is 1 */ + dev->chunkMask = (1<chunkShift) - 1; + + /* + * Calculate chunkGroupBits. + * We need to find the next power of 2 > than internalEndBlock + */ + + x = dev->nChunksPerBlock * (dev->internalEndBlock + 1); + + bits = ShiftsGE(x); + + /* Set up tnode width if wide tnodes are enabled. */ + if(!dev->wideTnodesDisabled){ + /* bits must be even so that we end up with 32-bit words */ + if(bits & 1) + bits++; + if(bits < 16) + dev->tnodeWidth = 16; + else + dev->tnodeWidth = bits; + } + else + dev->tnodeWidth = 16; + + dev->tnodeMask = (1<tnodeWidth)-1; + + /* Level0 Tnodes are 16 bits or wider (if wide tnodes are enabled), + * so if the bitwidth of the + * chunk range we're using is greater than 16 we need + * to figure out chunk shift and chunkGroupSize + */ + + if (bits <= dev->tnodeWidth) + dev->chunkGroupBits = 0; + else + dev->chunkGroupBits = bits - dev->tnodeWidth; + + + dev->chunkGroupSize = 1 << dev->chunkGroupBits; + + if (dev->nChunksPerBlock < dev->chunkGroupSize) { + /* We have a problem because the soft delete won't work if + * the chunk group size > chunks per block. + * This can be remedied by using larger "virtual blocks". + */ + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: chunk group too large\n" TENDSTR))); + + return YAFFS_FAIL; + } + + /* OK, we've finished verifying the device, lets continue with initialisation */ + + /* More device initialisation */ + dev->garbageCollections = 0; + dev->passiveGarbageCollections = 0; + dev->currentDirtyChecker = 0; + dev->bufferedBlock = -1; + dev->doingBufferedBlockRewrite = 0; + dev->nDeletedFiles = 0; + dev->nBackgroundDeletions = 0; + dev->nUnlinkedFiles = 0; + dev->eccFixed = 0; + dev->eccUnfixed = 0; + dev->tagsEccFixed = 0; + dev->tagsEccUnfixed = 0; + dev->nErasureFailures = 0; + dev->nErasedBlocks = 0; + dev->isDoingGC = 0; + dev->hasPendingPrioritisedGCs = 1; /* Assume the worst for now, will get fixed on first GC */ + + /* Initialise temporary buffers and caches. */ + if(!yaffs_InitialiseTempBuffers(dev)) + init_failed = 1; + + dev->srCache = NULL; + dev->gcCleanupList = NULL; + + + if (!init_failed && + dev->nShortOpCaches > 0) { + int i; + void *buf; + int srCacheBytes = dev->nShortOpCaches * sizeof(yaffs_ChunkCache); + + if (dev->nShortOpCaches > YAFFS_MAX_SHORT_OP_CACHES) { + dev->nShortOpCaches = YAFFS_MAX_SHORT_OP_CACHES; + } + + dev->srCache = YMALLOC(srCacheBytes); + + buf = (__u8 *) dev->srCache; + + if(dev->srCache) + memset(dev->srCache,0,srCacheBytes); + + for (i = 0; i < dev->nShortOpCaches && buf; i++) { + dev->srCache[i].object = NULL; + dev->srCache[i].lastUse = 0; + dev->srCache[i].dirty = 0; + dev->srCache[i].data = buf = YMALLOC_DMA(dev->totalBytesPerChunk); + } + if(!buf) + init_failed = 1; + + dev->srLastUse = 0; + } + + dev->cacheHits = 0; + + if(!init_failed){ + dev->gcCleanupList = YMALLOC(dev->nChunksPerBlock * sizeof(__u32)); + if(!dev->gcCleanupList) + init_failed = 1; + } + + if (dev->isYaffs2) { + dev->useHeaderFileSize = 1; + } + if(!init_failed && !yaffs_InitialiseBlocks(dev)) + init_failed = 1; + + yaffs_InitialiseTnodes(dev); + yaffs_InitialiseObjects(dev); + + if(!init_failed && !yaffs_CreateInitialDirectories(dev)) + init_failed = 1; + + + if(!init_failed){ + /* Now scan the flash. */ + if (dev->isYaffs2) { + if(yaffs_CheckpointRestore(dev)) { + yaffs_CheckObjectDetailsLoaded(dev->rootDir); + T(YAFFS_TRACE_ALWAYS, + (TSTR("yaffs: restored from checkpoint" TENDSTR))); + } else { + + /* Clean up the mess caused by an aborted checkpoint load + * and scan backwards. + */ + yaffs_DeinitialiseBlocks(dev); + yaffs_DeinitialiseTnodes(dev); + yaffs_DeinitialiseObjects(dev); + + + dev->nErasedBlocks = 0; + dev->nFreeChunks = 0; + dev->allocationBlock = -1; + dev->allocationPage = -1; + dev->nDeletedFiles = 0; + dev->nUnlinkedFiles = 0; + dev->nBackgroundDeletions = 0; + dev->oldestDirtySequence = 0; + + if(!init_failed && !yaffs_InitialiseBlocks(dev)) + init_failed = 1; + + yaffs_InitialiseTnodes(dev); + yaffs_InitialiseObjects(dev); + + if(!init_failed && !yaffs_CreateInitialDirectories(dev)) + init_failed = 1; + + if(!init_failed && !yaffs_ScanBackwards(dev)) + init_failed = 1; + } + }else + if(!yaffs_Scan(dev)) + init_failed = 1; + + yaffs_StripDeletedObjects(dev); + } + + if(init_failed){ + /* Clean up the mess */ + T(YAFFS_TRACE_TRACING, + (TSTR("yaffs: yaffs_GutsInitialise() aborted.\n" TENDSTR))); + + yaffs_Deinitialise(dev); + return YAFFS_FAIL; + } + + /* Zero out stats */ + dev->nPageReads = 0; + dev->nPageWrites = 0; + dev->nBlockErasures = 0; + dev->nGCCopies = 0; + dev->nRetriedWrites = 0; + + dev->nRetiredBlocks = 0; + + yaffs_VerifyFreeChunks(dev); + yaffs_VerifyBlocks(dev); + + + T(YAFFS_TRACE_TRACING, + (TSTR("yaffs: yaffs_GutsInitialise() done.\n" TENDSTR))); + return YAFFS_OK; + +} + +void yaffs_Deinitialise(yaffs_Device * dev) +{ + if (dev->isMounted) { + int i; + + yaffs_DeinitialiseBlocks(dev); + yaffs_DeinitialiseTnodes(dev); + yaffs_DeinitialiseObjects(dev); + if (dev->nShortOpCaches > 0 && + dev->srCache) { + + for (i = 0; i < dev->nShortOpCaches; i++) { + if(dev->srCache[i].data) + YFREE(dev->srCache[i].data); + dev->srCache[i].data = NULL; + } + + YFREE(dev->srCache); + dev->srCache = NULL; + } + + YFREE(dev->gcCleanupList); + + for (i = 0; i < YAFFS_N_TEMP_BUFFERS; i++) { + YFREE(dev->tempBuffer[i].buffer); + } + + + dev->isMounted = 0; + + if(dev->deinitialiseNAND) + dev->deinitialiseNAND(dev); + } + +} + +static int yaffs_CountFreeChunks(yaffs_Device * dev) +{ + int nFree; + int b; + + yaffs_BlockInfo *blk; + + for (nFree = 0, b = dev->internalStartBlock; b <= dev->internalEndBlock; + b++) { + blk = yaffs_GetBlockInfo(dev, b); + + switch (blk->blockState) { + case YAFFS_BLOCK_STATE_EMPTY: + case YAFFS_BLOCK_STATE_ALLOCATING: + case YAFFS_BLOCK_STATE_COLLECTING: + case YAFFS_BLOCK_STATE_FULL: + nFree += + (dev->nChunksPerBlock - blk->pagesInUse + + blk->softDeletions); + break; + default: + break; + } + + } + + return nFree; +} + +int yaffs_GetNumberOfFreeChunks(yaffs_Device * dev) +{ + /* This is what we report to the outside world */ + + int nFree; + int nDirtyCacheChunks; + int blocksForCheckpoint; + +#if 1 + nFree = dev->nFreeChunks; +#else + nFree = yaffs_CountFreeChunks(dev); +#endif + + nFree += dev->nDeletedFiles; + + /* Now count the number of dirty chunks in the cache and subtract those */ + + { + int i; + for (nDirtyCacheChunks = 0, i = 0; i < dev->nShortOpCaches; i++) { + if (dev->srCache[i].dirty) + nDirtyCacheChunks++; + } + } + + nFree -= nDirtyCacheChunks; + + nFree -= ((dev->nReservedBlocks + 1) * dev->nChunksPerBlock); + + /* Now we figure out how much to reserve for the checkpoint and report that... */ + blocksForCheckpoint = yaffs_CalcCheckpointBlocksRequired(dev) - dev->blocksInCheckpoint; + if(blocksForCheckpoint < 0) + blocksForCheckpoint = 0; + + nFree -= (blocksForCheckpoint * dev->nChunksPerBlock); + + if (nFree < 0) + nFree = 0; + + return nFree; + +} + +static int yaffs_freeVerificationFailures; + +static void yaffs_VerifyFreeChunks(yaffs_Device * dev) +{ + int counted; + int difference; + + if(yaffs_SkipVerification(dev)) + return; + + counted = yaffs_CountFreeChunks(dev); + + difference = dev->nFreeChunks - counted; + + if (difference) { + T(YAFFS_TRACE_ALWAYS, + (TSTR("Freechunks verification failure %d %d %d" TENDSTR), + dev->nFreeChunks, counted, difference)); + yaffs_freeVerificationFailures++; + } +} + +/*---------------------------------------- YAFFS test code ----------------------*/ + +#define yaffs_CheckStruct(structure,syze, name) \ + do { \ + if(sizeof(structure) != syze) \ + { \ + T(YAFFS_TRACE_ALWAYS,(TSTR("%s should be %d but is %d\n" TENDSTR),\ + name,syze,sizeof(structure))); \ + return YAFFS_FAIL; \ + } \ + } while(0) + +static int yaffs_CheckStructures(void) +{ +/* yaffs_CheckStruct(yaffs_Tags,8,"yaffs_Tags"); */ +/* yaffs_CheckStruct(yaffs_TagsUnion,8,"yaffs_TagsUnion"); */ +/* yaffs_CheckStruct(yaffs_Spare,16,"yaffs_Spare"); */ +#ifndef CONFIG_YAFFS_TNODE_LIST_DEBUG + yaffs_CheckStruct(yaffs_Tnode, 2 * YAFFS_NTNODES_LEVEL0, "yaffs_Tnode"); +#endif +#ifndef CONFIG_YAFFS_WINCE + yaffs_CheckStruct(yaffs_ObjectHeader, 512, "yaffs_ObjectHeader"); +#endif + return YAFFS_OK; +} diff --git a/fs/yaffs2/yaffs_guts.h b/fs/yaffs2/yaffs_guts.h new file mode 100644 index 0000000..849d951 --- /dev/null +++ b/fs/yaffs2/yaffs_guts.h @@ -0,0 +1,906 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_GUTS_H__ +#define __YAFFS_GUTS_H__ + +#include "devextras.h" +#include "yportenv.h" + +#define YAFFS_OK 1 +#define YAFFS_FAIL 0 + +/* Give us a Y=0x59, + * Give us an A=0x41, + * Give us an FF=0xFF + * Give us an S=0x53 + * And what have we got... + */ +#define YAFFS_MAGIC 0x5941FF53 + +#define YAFFS_NTNODES_LEVEL0 16 +#define YAFFS_TNODES_LEVEL0_BITS 4 +#define YAFFS_TNODES_LEVEL0_MASK 0xf + +#define YAFFS_NTNODES_INTERNAL (YAFFS_NTNODES_LEVEL0 / 2) +#define YAFFS_TNODES_INTERNAL_BITS (YAFFS_TNODES_LEVEL0_BITS - 1) +#define YAFFS_TNODES_INTERNAL_MASK 0x7 +#define YAFFS_TNODES_MAX_LEVEL 6 + +#ifndef CONFIG_YAFFS_NO_YAFFS1 +#define YAFFS_BYTES_PER_SPARE 16 +#define YAFFS_BYTES_PER_CHUNK 512 +#define YAFFS_CHUNK_SIZE_SHIFT 9 +#define YAFFS_CHUNKS_PER_BLOCK 32 +#define YAFFS_BYTES_PER_BLOCK (YAFFS_CHUNKS_PER_BLOCK*YAFFS_BYTES_PER_CHUNK) +#endif + +#define YAFFS_MIN_YAFFS2_CHUNK_SIZE 1024 +#define YAFFS_MIN_YAFFS2_SPARE_SIZE 32 + +#define YAFFS_MAX_CHUNK_ID 0x000FFFFF + +#define YAFFS_UNUSED_OBJECT_ID 0x0003FFFF + +#define YAFFS_ALLOCATION_NOBJECTS 100 +#define YAFFS_ALLOCATION_NTNODES 100 +#define YAFFS_ALLOCATION_NLINKS 100 + +#define YAFFS_NOBJECT_BUCKETS 256 + + +#define YAFFS_OBJECT_SPACE 0x40000 + +#define YAFFS_CHECKPOINT_VERSION 3 + +#ifdef CONFIG_YAFFS_UNICODE +#define YAFFS_MAX_NAME_LENGTH 127 +#define YAFFS_MAX_ALIAS_LENGTH 79 +#else +#define YAFFS_MAX_NAME_LENGTH 255 +#define YAFFS_MAX_ALIAS_LENGTH 159 +#endif + +#define YAFFS_SHORT_NAME_LENGTH 15 + +/* Some special object ids for pseudo objects */ +#define YAFFS_OBJECTID_ROOT 1 +#define YAFFS_OBJECTID_LOSTNFOUND 2 +#define YAFFS_OBJECTID_UNLINKED 3 +#define YAFFS_OBJECTID_DELETED 4 + +/* Sseudo object ids for checkpointing */ +#define YAFFS_OBJECTID_SB_HEADER 0x10 +#define YAFFS_OBJECTID_CHECKPOINT_DATA 0x20 +#define YAFFS_SEQUENCE_CHECKPOINT_DATA 0x21 + +/* */ + +#define YAFFS_MAX_SHORT_OP_CACHES 20 + +#define YAFFS_N_TEMP_BUFFERS 6 + +/* We limit the number attempts at sucessfully saving a chunk of data. + * Small-page devices have 32 pages per block; large-page devices have 64. + * Default to something in the order of 5 to 10 blocks worth of chunks. + */ +#define YAFFS_WR_ATTEMPTS (5*64) + +/* Sequence numbers are used in YAFFS2 to determine block allocation order. + * The range is limited slightly to help distinguish bad numbers from good. + * This also allows us to perhaps in the future use special numbers for + * special purposes. + * EFFFFF00 allows the allocation of 8 blocks per second (~1Mbytes) for 15 years, + * and is a larger number than the lifetime of a 2GB device. + */ +#define YAFFS_LOWEST_SEQUENCE_NUMBER 0x00001000 +#define YAFFS_HIGHEST_SEQUENCE_NUMBER 0xEFFFFF00 + +/* ChunkCache is used for short read/write operations.*/ +typedef struct { + struct yaffs_ObjectStruct *object; + int chunkId; + int lastUse; + int dirty; + int nBytes; /* Only valid if the cache is dirty */ + int locked; /* Can't push out or flush while locked. */ +#ifdef CONFIG_YAFFS_YAFFS2 + __u8 *data; +#else + __u8 data[YAFFS_BYTES_PER_CHUNK]; +#endif +} yaffs_ChunkCache; + + + +/* Tags structures in RAM + * NB This uses bitfield. Bitfields should not straddle a u32 boundary otherwise + * the structure size will get blown out. + */ + +#ifndef CONFIG_YAFFS_NO_YAFFS1 +typedef struct { + unsigned chunkId:20; + unsigned serialNumber:2; + unsigned byteCountLSB:10; + unsigned objectId:18; + unsigned ecc:12; + unsigned byteCountMSB:2; + +} yaffs_Tags; + +typedef union { + yaffs_Tags asTags; + __u8 asBytes[8]; +} yaffs_TagsUnion; + +#endif + +/* Stuff used for extended tags in YAFFS2 */ + +typedef enum { + YAFFS_ECC_RESULT_UNKNOWN, + YAFFS_ECC_RESULT_NO_ERROR, + YAFFS_ECC_RESULT_FIXED, + YAFFS_ECC_RESULT_UNFIXED +} yaffs_ECCResult; + +typedef enum { + YAFFS_OBJECT_TYPE_UNKNOWN, + YAFFS_OBJECT_TYPE_FILE, + YAFFS_OBJECT_TYPE_SYMLINK, + YAFFS_OBJECT_TYPE_DIRECTORY, + YAFFS_OBJECT_TYPE_HARDLINK, + YAFFS_OBJECT_TYPE_SPECIAL +} yaffs_ObjectType; + +#define YAFFS_OBJECT_TYPE_MAX YAFFS_OBJECT_TYPE_SPECIAL + +typedef struct { + + unsigned validMarker0; + unsigned chunkUsed; /* Status of the chunk: used or unused */ + unsigned objectId; /* If 0 then this is not part of an object (unused) */ + unsigned chunkId; /* If 0 then this is a header, else a data chunk */ + unsigned byteCount; /* Only valid for data chunks */ + + /* The following stuff only has meaning when we read */ + yaffs_ECCResult eccResult; + unsigned blockBad; + + /* YAFFS 1 stuff */ + unsigned chunkDeleted; /* The chunk is marked deleted */ + unsigned serialNumber; /* Yaffs1 2-bit serial number */ + + /* YAFFS2 stuff */ + unsigned sequenceNumber; /* The sequence number of this block */ + + /* Extra info if this is an object header (YAFFS2 only) */ + + unsigned extraHeaderInfoAvailable; /* There is extra info available if this is not zero */ + unsigned extraParentObjectId; /* The parent object */ + unsigned extraIsShrinkHeader; /* Is it a shrink header? */ + unsigned extraShadows; /* Does this shadow another object? */ + + yaffs_ObjectType extraObjectType; /* What object type? */ + + unsigned extraFileLength; /* Length if it is a file */ + unsigned extraEquivalentObjectId; /* Equivalent object Id if it is a hard link */ + + unsigned validMarker1; + +} yaffs_ExtendedTags; + +/* Spare structure for YAFFS1 */ +typedef struct { + __u8 tagByte0; + __u8 tagByte1; + __u8 tagByte2; + __u8 tagByte3; + __u8 pageStatus; /* set to 0 to delete the chunk */ + __u8 blockStatus; + __u8 tagByte4; + __u8 tagByte5; + __u8 ecc1[3]; + __u8 tagByte6; + __u8 tagByte7; + __u8 ecc2[3]; +} yaffs_Spare; + +/*Special structure for passing through to mtd */ +struct yaffs_NANDSpare { + yaffs_Spare spare; + int eccres1; + int eccres2; +}; + +/* Block data in RAM */ + +typedef enum { + YAFFS_BLOCK_STATE_UNKNOWN = 0, + + YAFFS_BLOCK_STATE_SCANNING, + YAFFS_BLOCK_STATE_NEEDS_SCANNING, + /* The block might have something on it (ie it is allocating or full, perhaps empty) + * but it needs to be scanned to determine its true state. + * This state is only valid during yaffs_Scan. + * NB We tolerate empty because the pre-scanner might be incapable of deciding + * However, if this state is returned on a YAFFS2 device, then we expect a sequence number + */ + + YAFFS_BLOCK_STATE_EMPTY, + /* This block is empty */ + + YAFFS_BLOCK_STATE_ALLOCATING, + /* This block is partially allocated. + * At least one page holds valid data. + * This is the one currently being used for page + * allocation. Should never be more than one of these + */ + + YAFFS_BLOCK_STATE_FULL, + /* All the pages in this block have been allocated. + */ + + YAFFS_BLOCK_STATE_DIRTY, + /* All pages have been allocated and deleted. + * Erase me, reuse me. + */ + + YAFFS_BLOCK_STATE_CHECKPOINT, + /* This block is assigned to holding checkpoint data. + */ + + YAFFS_BLOCK_STATE_COLLECTING, + /* This block is being garbage collected */ + + YAFFS_BLOCK_STATE_DEAD + /* This block has failed and is not in use */ +} yaffs_BlockState; + +#define YAFFS_NUMBER_OF_BLOCK_STATES (YAFFS_BLOCK_STATE_DEAD + 1) + + +typedef struct { + + int softDeletions:10; /* number of soft deleted pages */ + int pagesInUse:10; /* number of pages in use */ + unsigned blockState:4; /* One of the above block states. NB use unsigned because enum is sometimes an int */ + __u32 needsRetiring:1; /* Data has failed on this block, need to get valid data off */ + /* and retire the block. */ + __u32 skipErasedCheck: 1; /* If this is set we can skip the erased check on this block */ + __u32 gcPrioritise: 1; /* An ECC check or blank check has failed on this block. + It should be prioritised for GC */ + __u32 chunkErrorStrikes:3; /* How many times we've had ecc etc failures on this block and tried to reuse it */ + +#ifdef CONFIG_YAFFS_YAFFS2 + __u32 hasShrinkHeader:1; /* This block has at least one shrink object header */ + __u32 sequenceNumber; /* block sequence number for yaffs2 */ +#endif + +} yaffs_BlockInfo; + +/* -------------------------- Object structure -------------------------------*/ +/* This is the object structure as stored on NAND */ + +typedef struct { + yaffs_ObjectType type; + + /* Apply to everything */ + int parentObjectId; + __u16 sum__NoLongerUsed; /* checksum of name. No longer used */ + YCHAR name[YAFFS_MAX_NAME_LENGTH + 1]; + + /* The following apply to directories, files, symlinks - not hard links */ + __u32 yst_mode; /* protection */ + +#ifdef CONFIG_YAFFS_WINCE + __u32 notForWinCE[5]; +#else + __u32 yst_uid; + __u32 yst_gid; + __u32 yst_atime; + __u32 yst_mtime; + __u32 yst_ctime; +#endif + + /* File size applies to files only */ + int fileSize; + + /* Equivalent object id applies to hard links only. */ + int equivalentObjectId; + + /* Alias is for symlinks only. */ + YCHAR alias[YAFFS_MAX_ALIAS_LENGTH + 1]; + + __u32 yst_rdev; /* device stuff for block and char devices (major/min) */ + +#ifdef CONFIG_YAFFS_WINCE + __u32 win_ctime[2]; + __u32 win_atime[2]; + __u32 win_mtime[2]; +#else + __u32 roomToGrow[6]; + +#endif + __u32 inbandShadowsObject; + __u32 inbandIsShrink; + + __u32 reservedSpace[2]; + int shadowsObject; /* This object header shadows the specified object if > 0 */ + + /* isShrink applies to object headers written when we shrink the file (ie resize) */ + __u32 isShrink; + +} yaffs_ObjectHeader; + +/*--------------------------- Tnode -------------------------- */ + +union yaffs_Tnode_union { +#ifdef CONFIG_YAFFS_TNODE_LIST_DEBUG + union yaffs_Tnode_union *internal[YAFFS_NTNODES_INTERNAL + 1]; +#else + union yaffs_Tnode_union *internal[YAFFS_NTNODES_INTERNAL]; +#endif +/* __u16 level0[YAFFS_NTNODES_LEVEL0]; */ + +}; + +typedef union yaffs_Tnode_union yaffs_Tnode; + +struct yaffs_TnodeList_struct { + struct yaffs_TnodeList_struct *next; + yaffs_Tnode *tnodes; +}; + +typedef struct yaffs_TnodeList_struct yaffs_TnodeList; + +/*------------------------ Object -----------------------------*/ +/* An object can be one of: + * - a directory (no data, has children links + * - a regular file (data.... not prunes :->). + * - a symlink [symbolic link] (the alias). + * - a hard link + */ + +typedef struct { + __u32 fileSize; + __u32 scannedFileSize; + __u32 shrinkSize; + int topLevel; + yaffs_Tnode *top; +} yaffs_FileStructure; + +typedef struct { + struct ylist_head children; /* list of child links */ +} yaffs_DirectoryStructure; + +typedef struct { + YCHAR *alias; +} yaffs_SymLinkStructure; + +typedef struct { + struct yaffs_ObjectStruct *equivalentObject; + __u32 equivalentObjectId; +} yaffs_HardLinkStructure; + +typedef union { + yaffs_FileStructure fileVariant; + yaffs_DirectoryStructure directoryVariant; + yaffs_SymLinkStructure symLinkVariant; + yaffs_HardLinkStructure hardLinkVariant; +} yaffs_ObjectVariant; + +struct yaffs_ObjectStruct { + __u8 deleted:1; /* This should only apply to unlinked files. */ + __u8 softDeleted:1; /* it has also been soft deleted */ + __u8 unlinked:1; /* An unlinked file. The file should be in the unlinked directory.*/ + __u8 fake:1; /* A fake object has no presence on NAND. */ + __u8 renameAllowed:1; /* Some objects are not allowed to be renamed. */ + __u8 unlinkAllowed:1; + __u8 dirty:1; /* the object needs to be written to flash */ + __u8 valid:1; /* When the file system is being loaded up, this + * object might be created before the data + * is available (ie. file data records appear before the header). + */ + __u8 lazyLoaded:1; /* This object has been lazy loaded and is missing some detail */ + + __u8 deferedFree:1; /* For Linux kernel. Object is removed from NAND, but is + * still in the inode cache. Free of object is defered. + * until the inode is released. + */ + + __u8 serial; /* serial number of chunk in NAND. Cached here */ +/* __u16 sum_prev; */ + __u16 sum; /* sum of the name to speed searching */ +/* __u16 sum_trailer; */ + + struct yaffs_DeviceStruct *myDev; /* The device I'm on */ + + struct ylist_head hashLink; /* list of objects in this hash bucket */ + + struct ylist_head hardLinks; /* all the equivalent hard linked objects */ + + /* directory structure stuff */ + /* also used for linking up the free list */ + struct yaffs_ObjectStruct *parent; + struct ylist_head siblings; + + /* Where's my object header in NAND? */ + int hdrChunk; + + int nDataChunks; /* Number of data chunks attached to the file. */ + + __u32 objectId; /* the object id value */ + + __u32 yst_mode; + +#ifdef CONFIG_YAFFS_SHORT_NAMES_IN_RAM + YCHAR shortName[YAFFS_SHORT_NAME_LENGTH + 1]; +#endif + +#ifndef __KERNEL__ + __u32 inUse; +#endif + +#ifdef CONFIG_YAFFS_WINCE + __u32 win_ctime[2]; + __u32 win_mtime[2]; + __u32 win_atime[2]; +#else + __u32 yst_uid; + __u32 yst_gid; + __u32 yst_atime; + __u32 yst_mtime; + __u32 yst_ctime; +#endif + + __u32 yst_rdev; + +#ifdef __KERNEL__ + struct inode *myInode; + +#endif + + yaffs_ObjectType variantType; + + yaffs_ObjectVariant variant; + +}; + +typedef struct yaffs_ObjectStruct yaffs_Object; + +struct yaffs_ObjectList_struct { + yaffs_Object *objects; + struct yaffs_ObjectList_struct *next; +}; + +typedef struct yaffs_ObjectList_struct yaffs_ObjectList; + +typedef struct { + struct ylist_head list; + int count; +} yaffs_ObjectBucket; + + +/* yaffs_CheckpointObject holds the definition of an object as dumped + * by checkpointing. + */ + +typedef struct { + int structType; + __u32 objectId; + __u32 parentId; + int hdrChunk; + yaffs_ObjectType variantType:3; + __u8 deleted:1; + __u8 softDeleted:1; + __u8 unlinked:1; + __u8 fake:1; + __u8 renameAllowed:1; + __u8 unlinkAllowed:1; + __u8 serial; + + int nDataChunks; + __u32 fileSizeOrEquivalentObjectId; + +}yaffs_CheckpointObject; + +/*--------------------- Temporary buffers ---------------- + * + * These are chunk-sized working buffers. Each device has a few + */ + +typedef struct { + __u8 *buffer; + int line; /* track from whence this buffer was allocated */ + int maxLine; +} yaffs_TempBuffer; + +/*----------------- Device ---------------------------------*/ + +struct yaffs_DeviceStruct { + struct ylist_head devList; + const char *name; + + /* Entry parameters set up way early. Yaffs sets up the rest.*/ + int nDataBytesPerChunk; /* Should be a power of 2 >= 512 */ + int nChunksPerBlock; /* does not need to be a power of 2 */ + int spareBytesPerChunk;/* spare area size */ + int startBlock; /* Start block we're allowed to use */ + int endBlock; /* End block we're allowed to use */ + int nReservedBlocks; /* We want this tuneable so that we can reduce */ + /* reserved blocks on NOR and RAM. */ + + + /* Stuff used by the shared space checkpointing mechanism */ + /* If this value is zero, then this mechanism is disabled */ + +// int nCheckpointReservedBlocks; /* Blocks to reserve for checkpoint data */ + + + + + int nShortOpCaches; /* If <= 0, then short op caching is disabled, else + * the number of short op caches (don't use too many) + */ + + int useHeaderFileSize; /* Flag to determine if we should use file sizes from the header */ + + int useNANDECC; /* Flag to decide whether or not to use NANDECC */ + + void *genericDevice; /* Pointer to device context + * On an mtd this holds the mtd pointer. + */ + void *superBlock; + + /* NAND access functions (Must be set before calling YAFFS)*/ + + int (*writeChunkToNAND) (struct yaffs_DeviceStruct * dev, + int chunkInNAND, const __u8 * data, + const yaffs_Spare * spare); + int (*readChunkFromNAND) (struct yaffs_DeviceStruct * dev, + int chunkInNAND, __u8 * data, + yaffs_Spare * spare); + int (*eraseBlockInNAND) (struct yaffs_DeviceStruct * dev, + int blockInNAND); + int (*initialiseNAND) (struct yaffs_DeviceStruct * dev); + int (*deinitialiseNAND) (struct yaffs_DeviceStruct * dev); + +#ifdef CONFIG_YAFFS_YAFFS2 + int (*writeChunkWithTagsToNAND) (struct yaffs_DeviceStruct * dev, + int chunkInNAND, const __u8 * data, + const yaffs_ExtendedTags * tags); + int (*readChunkWithTagsFromNAND) (struct yaffs_DeviceStruct * dev, + int chunkInNAND, __u8 * data, + yaffs_ExtendedTags * tags); + int (*markNANDBlockBad) (struct yaffs_DeviceStruct * dev, int blockNo); + int (*queryNANDBlock) (struct yaffs_DeviceStruct * dev, int blockNo, + yaffs_BlockState * state, __u32 *sequenceNumber); +#endif + + int isYaffs2; + + /* The removeObjectCallback function must be supplied by OS flavours that + * need it. The Linux kernel does not use this, but yaffs direct does use + * it to implement the faster readdir + */ + void (*removeObjectCallback)(struct yaffs_ObjectStruct *obj); + + /* Callback to mark the superblock dirsty */ + void (*markSuperBlockDirty)(void * superblock); + + int wideTnodesDisabled; /* Set to disable wide tnodes */ + + YCHAR *pathDividers; /* String of legal path dividers */ + + + /* End of stuff that must be set before initialisation. */ + + /* Checkpoint control. Can be set before or after initialisation */ + __u8 skipCheckpointRead; + __u8 skipCheckpointWrite; + + /* Runtime parameters. Set up by YAFFS. */ + + __u16 chunkGroupBits; /* 0 for devices <= 32MB. else log2(nchunks) - 16 */ + __u16 chunkGroupSize; /* == 2^^chunkGroupBits */ + + /* Stuff to support wide tnodes */ + __u32 tnodeWidth; + __u32 tnodeMask; + + /* Stuff for figuring out file offset to chunk conversions */ + __u32 chunkShift; /* Shift value */ + __u32 chunkDiv; /* Divisor after shifting: 1 for power-of-2 sizes */ + __u32 chunkMask; /* Mask to use for power-of-2 case */ + + /* Stuff to handle inband tags */ + int inbandTags; + __u32 totalBytesPerChunk; + +#ifdef __KERNEL__ + + struct semaphore sem; /* Semaphore for waiting on erasure.*/ + struct semaphore grossLock; /* Gross locking semaphore */ + __u8 *spareBuffer; /* For mtdif2 use. Don't know the size of the buffer + * at compile time so we have to allocate it. + */ + void (*putSuperFunc) (struct super_block * sb); +#endif + + int isMounted; + + int isCheckpointed; + + + /* Stuff to support block offsetting to support start block zero */ + int internalStartBlock; + int internalEndBlock; + int blockOffset; + int chunkOffset; + + + /* Runtime checkpointing stuff */ + int checkpointPageSequence; /* running sequence number of checkpoint pages */ + int checkpointByteCount; + int checkpointByteOffset; + __u8 *checkpointBuffer; + int checkpointOpenForWrite; + int blocksInCheckpoint; + int checkpointCurrentChunk; + int checkpointCurrentBlock; + int checkpointNextBlock; + int *checkpointBlockList; + int checkpointMaxBlocks; + __u32 checkpointSum; + __u32 checkpointXor; + + int nCheckpointBlocksRequired; /* Number of blocks needed to store current checkpoint set */ + + /* Block Info */ + yaffs_BlockInfo *blockInfo; + __u8 *chunkBits; /* bitmap of chunks in use */ + unsigned blockInfoAlt:1; /* was allocated using alternative strategy */ + unsigned chunkBitsAlt:1; /* was allocated using alternative strategy */ + int chunkBitmapStride; /* Number of bytes of chunkBits per block. + * Must be consistent with nChunksPerBlock. + */ + + int nErasedBlocks; + int allocationBlock; /* Current block being allocated off */ + __u32 allocationPage; + int allocationBlockFinder; /* Used to search for next allocation block */ + + /* Runtime state */ + int nTnodesCreated; + yaffs_Tnode *freeTnodes; + int nFreeTnodes; + yaffs_TnodeList *allocatedTnodeList; + + int isDoingGC; + int gcBlock; + int gcChunk; + + int nObjectsCreated; + yaffs_Object *freeObjects; + int nFreeObjects; + + int nHardLinks; + + yaffs_ObjectList *allocatedObjectList; + + yaffs_ObjectBucket objectBucket[YAFFS_NOBJECT_BUCKETS]; + + int nFreeChunks; + + int currentDirtyChecker; /* Used to find current dirtiest block */ + + __u32 *gcCleanupList; /* objects to delete at the end of a GC. */ + int nonAggressiveSkip; /* GC state/mode */ + + /* Statistcs */ + int nPageWrites; + int nPageReads; + int nBlockErasures; + int nErasureFailures; + int nGCCopies; + int garbageCollections; + int passiveGarbageCollections; + int nRetriedWrites; + int nRetiredBlocks; + int eccFixed; + int eccUnfixed; + int tagsEccFixed; + int tagsEccUnfixed; + int nDeletions; + int nUnmarkedDeletions; + + int hasPendingPrioritisedGCs; /* We think this device might have pending prioritised gcs */ + + /* Special directories */ + yaffs_Object *rootDir; + yaffs_Object *lostNFoundDir; + + /* Buffer areas for storing data to recover from write failures TODO + * __u8 bufferedData[YAFFS_CHUNKS_PER_BLOCK][YAFFS_BYTES_PER_CHUNK]; + * yaffs_Spare bufferedSpare[YAFFS_CHUNKS_PER_BLOCK]; + */ + + int bufferedBlock; /* Which block is buffered here? */ + int doingBufferedBlockRewrite; + + yaffs_ChunkCache *srCache; + int srLastUse; + + int cacheHits; + + /* Stuff for background deletion and unlinked files.*/ + yaffs_Object *unlinkedDir; /* Directory where unlinked and deleted files live. */ + yaffs_Object *deletedDir; /* Directory where deleted objects are sent to disappear. */ + yaffs_Object *unlinkedDeletion; /* Current file being background deleted.*/ + int nDeletedFiles; /* Count of files awaiting deletion;*/ + int nUnlinkedFiles; /* Count of unlinked files. */ + int nBackgroundDeletions; /* Count of background deletions. */ + + + /* Temporary buffer management */ + yaffs_TempBuffer tempBuffer[YAFFS_N_TEMP_BUFFERS]; + int maxTemp; + int tempInUse; + int unmanagedTempAllocations; + int unmanagedTempDeallocations; + + /* yaffs2 runtime stuff */ + unsigned sequenceNumber; /* Sequence number of currently allocating block */ + unsigned oldestDirtySequence; + +}; + +typedef struct yaffs_DeviceStruct yaffs_Device; + +/* The static layout of block usage etc is stored in the super block header */ +typedef struct { + int StructType; + int version; + int checkpointStartBlock; + int checkpointEndBlock; + int startBlock; + int endBlock; + int rfu[100]; +} yaffs_SuperBlockHeader; + +/* The CheckpointDevice structure holds the device information that changes at runtime and + * must be preserved over unmount/mount cycles. + */ +typedef struct { + int structType; + int nErasedBlocks; + int allocationBlock; /* Current block being allocated off */ + __u32 allocationPage; + int nFreeChunks; + + int nDeletedFiles; /* Count of files awaiting deletion;*/ + int nUnlinkedFiles; /* Count of unlinked files. */ + int nBackgroundDeletions; /* Count of background deletions. */ + + /* yaffs2 runtime stuff */ + unsigned sequenceNumber; /* Sequence number of currently allocating block */ + unsigned oldestDirtySequence; + +} yaffs_CheckpointDevice; + + +typedef struct { + int structType; + __u32 magic; + __u32 version; + __u32 head; +} yaffs_CheckpointValidity; + + +/*----------------------- YAFFS Functions -----------------------*/ + +int yaffs_GutsInitialise(yaffs_Device * dev); +void yaffs_Deinitialise(yaffs_Device * dev); + +int yaffs_GetNumberOfFreeChunks(yaffs_Device * dev); + +int yaffs_RenameObject(yaffs_Object * oldDir, const YCHAR * oldName, + yaffs_Object * newDir, const YCHAR * newName); + +int yaffs_Unlink(yaffs_Object * dir, const YCHAR * name); +int yaffs_DeleteFile(yaffs_Object * obj); + +int yaffs_GetObjectName(yaffs_Object * obj, YCHAR * name, int buffSize); +int yaffs_GetObjectFileLength(yaffs_Object * obj); +int yaffs_GetObjectInode(yaffs_Object * obj); +unsigned yaffs_GetObjectType(yaffs_Object * obj); +int yaffs_GetObjectLinkCount(yaffs_Object * obj); + +int yaffs_SetAttributes(yaffs_Object * obj, struct iattr *attr); +int yaffs_GetAttributes(yaffs_Object * obj, struct iattr *attr); + +/* File operations */ +int yaffs_ReadDataFromFile(yaffs_Object * obj, __u8 * buffer, loff_t offset, + int nBytes); +int yaffs_WriteDataToFile(yaffs_Object * obj, const __u8 * buffer, loff_t offset, + int nBytes, int writeThrough); +int yaffs_ResizeFile(yaffs_Object * obj, loff_t newSize); + +yaffs_Object *yaffs_MknodFile(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid); +int yaffs_FlushFile(yaffs_Object * obj, int updateTime); + +/* Flushing and checkpointing */ +void yaffs_FlushEntireDeviceCache(yaffs_Device *dev); + +int yaffs_CheckpointSave(yaffs_Device *dev); +int yaffs_CheckpointRestore(yaffs_Device *dev); + +/* Directory operations */ +yaffs_Object *yaffs_MknodDirectory(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid); +yaffs_Object *yaffs_FindObjectByName(yaffs_Object * theDir, const YCHAR * name); +int yaffs_ApplyToDirectoryChildren(yaffs_Object * theDir, + int (*fn) (yaffs_Object *)); + +yaffs_Object *yaffs_FindObjectByNumber(yaffs_Device * dev, __u32 number); + +/* Link operations */ +yaffs_Object *yaffs_Link(yaffs_Object * parent, const YCHAR * name, + yaffs_Object * equivalentObject); + +yaffs_Object *yaffs_GetEquivalentObject(yaffs_Object * obj); + +/* Symlink operations */ +yaffs_Object *yaffs_MknodSymLink(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid, + const YCHAR * alias); +YCHAR *yaffs_GetSymlinkAlias(yaffs_Object * obj); + +/* Special inodes (fifos, sockets and devices) */ +yaffs_Object *yaffs_MknodSpecial(yaffs_Object * parent, const YCHAR * name, + __u32 mode, __u32 uid, __u32 gid, __u32 rdev); + +/* Special directories */ +yaffs_Object *yaffs_Root(yaffs_Device * dev); +yaffs_Object *yaffs_LostNFound(yaffs_Device * dev); + +#ifdef CONFIG_YAFFS_WINCE +/* CONFIG_YAFFS_WINCE special stuff */ +void yfsd_WinFileTimeNow(__u32 target[2]); +#endif + +#ifdef __KERNEL__ + +void yaffs_HandleDeferedFree(yaffs_Object * obj); +#endif + +/* Debug dump */ +int yaffs_DumpObject(yaffs_Object * obj); + +void yaffs_GutsTest(yaffs_Device * dev); + +/* A few useful functions */ +void yaffs_InitialiseTags(yaffs_ExtendedTags * tags); +void yaffs_DeleteChunk(yaffs_Device * dev, int chunkId, int markNAND, int lyn); +int yaffs_CheckFF(__u8 * buffer, int nBytes); +void yaffs_HandleChunkError(yaffs_Device *dev, yaffs_BlockInfo *bi); + +__u8 *yaffs_GetTempBuffer(yaffs_Device * dev, int lineNo); +void yaffs_ReleaseTempBuffer(yaffs_Device * dev, __u8 * buffer, int lineNo); + +#endif diff --git a/fs/yaffs2/yaffs_mtdif.c b/fs/yaffs2/yaffs_mtdif.c new file mode 100644 index 0000000..56bb225 --- /dev/null +++ b/fs/yaffs2/yaffs_mtdif.c @@ -0,0 +1,241 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +const char *yaffs_mtdif_c_version = + "$Id: yaffs_mtdif.c,v 1.21 2007/12/13 15:35:18 wookey Exp $"; + +#include "yportenv.h" + + +#include "yaffs_mtdif.h" + +#include "linux/mtd/mtd.h" +#include "linux/types.h" +#include "linux/time.h" +#include "linux/mtd/nand.h" + +#if (MTD_VERSION_CODE < MTD_VERSION(2,6,18)) +static struct nand_oobinfo yaffs_oobinfo = { + .useecc = 1, + .eccbytes = 6, + .eccpos = {8, 9, 10, 13, 14, 15} +}; + +static struct nand_oobinfo yaffs_noeccinfo = { + .useecc = 0, +}; +#endif + +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) +static inline void translate_spare2oob(const yaffs_Spare *spare, __u8 *oob) +{ + oob[0] = spare->tagByte0; + oob[1] = spare->tagByte1; + oob[2] = spare->tagByte2; + oob[3] = spare->tagByte3; + oob[4] = spare->tagByte4; + oob[5] = spare->tagByte5 & 0x3f; + oob[5] |= spare->blockStatus == 'Y' ? 0: 0x80; + oob[5] |= spare->pageStatus == 0 ? 0: 0x40; + oob[6] = spare->tagByte6; + oob[7] = spare->tagByte7; +} + +static inline void translate_oob2spare(yaffs_Spare *spare, __u8 *oob) +{ + struct yaffs_NANDSpare *nspare = (struct yaffs_NANDSpare *)spare; + spare->tagByte0 = oob[0]; + spare->tagByte1 = oob[1]; + spare->tagByte2 = oob[2]; + spare->tagByte3 = oob[3]; + spare->tagByte4 = oob[4]; + spare->tagByte5 = oob[5] == 0xff ? 0xff : oob[5] & 0x3f; + spare->blockStatus = oob[5] & 0x80 ? 0xff : 'Y'; + spare->pageStatus = oob[5] & 0x40 ? 0xff : 0; + spare->ecc1[0] = spare->ecc1[1] = spare->ecc1[2] = 0xff; + spare->tagByte6 = oob[6]; + spare->tagByte7 = oob[7]; + spare->ecc2[0] = spare->ecc2[1] = spare->ecc2[2] = 0xff; + + nspare->eccres1 = nspare->eccres2 = 0; /* FIXME */ +} +#endif + +int nandmtd_WriteChunkToNAND(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, const yaffs_Spare * spare) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + struct mtd_oob_ops ops; +#endif + size_t dummy; + int retval = 0; + + loff_t addr = ((loff_t) chunkInNAND) * dev->nDataBytesPerChunk; +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + __u8 spareAsBytes[8]; /* OOB */ + + if (data && !spare) + retval = mtd->write(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data); + else if (spare) { + if (dev->useNANDECC) { + translate_spare2oob(spare, spareAsBytes); + ops.mode = MTD_OOB_AUTO; + ops.ooblen = 8; /* temp hack */ + } else { + ops.mode = MTD_OOB_RAW; + ops.ooblen = YAFFS_BYTES_PER_SPARE; + } + ops.len = data ? dev->nDataBytesPerChunk : ops.ooblen; + ops.datbuf = (u8 *)data; + ops.ooboffs = 0; + ops.oobbuf = spareAsBytes; + retval = mtd->write_oob(mtd, addr, &ops); + } +#else + __u8 *spareAsBytes = (__u8 *) spare; + + if (data && spare) { + if (dev->useNANDECC) + retval = + mtd->write_ecc(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data, spareAsBytes, + &yaffs_oobinfo); + else + retval = + mtd->write_ecc(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data, spareAsBytes, + &yaffs_noeccinfo); + } else { + if (data) + retval = + mtd->write(mtd, addr, dev->nDataBytesPerChunk, &dummy, + data); + if (spare) + retval = + mtd->write_oob(mtd, addr, YAFFS_BYTES_PER_SPARE, + &dummy, spareAsBytes); + } +#endif + + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; +} + +int nandmtd_ReadChunkFromNAND(yaffs_Device * dev, int chunkInNAND, __u8 * data, + yaffs_Spare * spare) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + struct mtd_oob_ops ops; +#endif + size_t dummy; + int retval = 0; + + loff_t addr = ((loff_t) chunkInNAND) * dev->nDataBytesPerChunk; +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + __u8 spareAsBytes[8]; /* OOB */ + + if (data && !spare) + retval = mtd->read(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data); + else if (spare) { + if (dev->useNANDECC) { + ops.mode = MTD_OOB_AUTO; + ops.ooblen = 8; /* temp hack */ + } else { + ops.mode = MTD_OOB_RAW; + ops.ooblen = YAFFS_BYTES_PER_SPARE; + } + ops.len = data ? dev->nDataBytesPerChunk : ops.ooblen; + ops.datbuf = data; + ops.ooboffs = 0; + ops.oobbuf = spareAsBytes; + retval = mtd->read_oob(mtd, addr, &ops); + if (dev->useNANDECC) + translate_oob2spare(spare, spareAsBytes); + } +#else + __u8 *spareAsBytes = (__u8 *) spare; + + if (data && spare) { + if (dev->useNANDECC) { + /* Careful, this call adds 2 ints */ + /* to the end of the spare data. Calling function */ + /* should allocate enough memory for spare, */ + /* i.e. [YAFFS_BYTES_PER_SPARE+2*sizeof(int)]. */ + retval = + mtd->read_ecc(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data, spareAsBytes, + &yaffs_oobinfo); + } else { + retval = + mtd->read_ecc(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data, spareAsBytes, + &yaffs_noeccinfo); + } + } else { + if (data) + retval = + mtd->read(mtd, addr, dev->nDataBytesPerChunk, &dummy, + data); + if (spare) + retval = + mtd->read_oob(mtd, addr, YAFFS_BYTES_PER_SPARE, + &dummy, spareAsBytes); + } +#endif + + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; +} + +int nandmtd_EraseBlockInNAND(yaffs_Device * dev, int blockNumber) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); + __u32 addr = + ((loff_t) blockNumber) * dev->nDataBytesPerChunk + * dev->nChunksPerBlock; + struct erase_info ei; + int retval = 0; + + ei.mtd = mtd; + ei.addr = addr; + ei.len = dev->nDataBytesPerChunk * dev->nChunksPerBlock; + ei.time = 1000; + ei.retries = 2; + ei.callback = NULL; + ei.priv = (u_long) dev; + + /* Todo finish off the ei if required */ + + sema_init(&dev->sem, 0); + + retval = mtd->erase(mtd, &ei); + + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; +} + +int nandmtd_InitialiseNAND(yaffs_Device * dev) +{ + return YAFFS_OK; +} + diff --git a/fs/yaffs2/yaffs_mtdif.h b/fs/yaffs2/yaffs_mtdif.h new file mode 100644 index 0000000..511b017 --- /dev/null +++ b/fs/yaffs2/yaffs_mtdif.h @@ -0,0 +1,32 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_MTDIF_H__ +#define __YAFFS_MTDIF_H__ + +#include "yaffs_guts.h" + +#if (MTD_VERSION_CODE < MTD_VERSION(2,6,18)) +extern struct nand_oobinfo yaffs_oobinfo; +extern struct nand_oobinfo yaffs_noeccinfo; +#endif + +int nandmtd_WriteChunkToNAND(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, const yaffs_Spare * spare); +int nandmtd_ReadChunkFromNAND(yaffs_Device * dev, int chunkInNAND, __u8 * data, + yaffs_Spare * spare); +int nandmtd_EraseBlockInNAND(yaffs_Device * dev, int blockNumber); +int nandmtd_InitialiseNAND(yaffs_Device * dev); +#endif diff --git a/fs/yaffs2/yaffs_mtdif1.c b/fs/yaffs2/yaffs_mtdif1.c new file mode 100644 index 0000000..f00d6ef --- /dev/null +++ b/fs/yaffs2/yaffs_mtdif1.c @@ -0,0 +1,369 @@ +/* + * YAFFS: Yet another FFS. A NAND-flash specific file system. + * yaffs_mtdif1.c NAND mtd interface functions for small-page NAND. + * + * Copyright (C) 2002 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * This module provides the interface between yaffs_nand.c and the + * MTD API. This version is used when the MTD interface supports the + * 'mtd_oob_ops' style calls to read_oob and write_oob, circa 2.6.17, + * and we have small-page NAND device. + * + * These functions are invoked via function pointers in yaffs_nand.c. + * This replaces functionality provided by functions in yaffs_mtdif.c + * and the yaffs_TagsCompatability functions in yaffs_tagscompat.c that are + * called in yaffs_mtdif.c when the function pointers are NULL. + * We assume the MTD layer is performing ECC (useNANDECC is true). + */ + +#include "yportenv.h" +#include "yaffs_guts.h" +#include "yaffs_packedtags1.h" +#include "yaffs_tagscompat.h" // for yaffs_CalcTagsECC + +#include "linux/kernel.h" +#include "linux/version.h" +#include "linux/types.h" +#include "linux/mtd/mtd.h" + +/* Don't compile this module if we don't have MTD's mtd_oob_ops interface */ +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + +const char *yaffs_mtdif1_c_version = "$Id: yaffs_mtdif1.c,v 1.8 2008/07/23 03:35:12 charles Exp $"; + +#ifndef CONFIG_YAFFS_9BYTE_TAGS +# define YTAG1_SIZE 8 +#else +# define YTAG1_SIZE 9 +#endif + +#if 0 +/* Use the following nand_ecclayout with MTD when using + * CONFIG_YAFFS_9BYTE_TAGS and the older on-NAND tags layout. + * If you have existing Yaffs images and the byte order differs from this, + * adjust 'oobfree' to match your existing Yaffs data. + * + * This nand_ecclayout scatters/gathers to/from the old-yaffs layout with the + * pageStatus byte (at NAND spare offset 4) scattered/gathered from/to + * the 9th byte. + * + * Old-style on-NAND format: T0,T1,T2,T3,P,B,T4,T5,E0,E1,E2,T6,T7,E3,E4,E5 + * We have/need PackedTags1 plus pageStatus: T0,T1,T2,T3,T4,T5,T6,T7,P + * where Tn are the tag bytes, En are MTD's ECC bytes, P is the pageStatus + * byte and B is the small-page bad-block indicator byte. + */ +static struct nand_ecclayout nand_oob_16 = { + .eccbytes = 6, + .eccpos = { 8, 9, 10, 13, 14, 15 }, + .oobavail = 9, + .oobfree = { { 0, 4 }, { 6, 2 }, { 11, 2 }, { 4, 1 } } +}; +#endif + +/* Write a chunk (page) of data to NAND. + * + * Caller always provides ExtendedTags data which are converted to a more + * compact (packed) form for storage in NAND. A mini-ECC runs over the + * contents of the tags meta-data; used to valid the tags when read. + * + * - Pack ExtendedTags to PackedTags1 form + * - Compute mini-ECC for PackedTags1 + * - Write data and packed tags to NAND. + * + * Note: Due to the use of the PackedTags1 meta-data which does not include + * a full sequence number (as found in the larger PackedTags2 form) it is + * necessary for Yaffs to re-write a chunk/page (just once) to mark it as + * discarded and dirty. This is not ideal: newer NAND parts are supposed + * to be written just once. When Yaffs performs this operation, this + * function is called with a NULL data pointer -- calling MTD write_oob + * without data is valid usage (2.6.17). + * + * Any underlying MTD error results in YAFFS_FAIL. + * Returns YAFFS_OK or YAFFS_FAIL. + */ +int nandmtd1_WriteChunkWithTagsToNAND(yaffs_Device *dev, + int chunkInNAND, const __u8 * data, const yaffs_ExtendedTags * etags) +{ + struct mtd_info * mtd = dev->genericDevice; + int chunkBytes = dev->nDataBytesPerChunk; + loff_t addr = ((loff_t)chunkInNAND) * chunkBytes; + struct mtd_oob_ops ops; + yaffs_PackedTags1 pt1; + int retval; + + /* we assume that PackedTags1 and yaffs_Tags are compatible */ + compile_time_assertion(sizeof(yaffs_PackedTags1) == 12); + compile_time_assertion(sizeof(yaffs_Tags) == 8); + + dev->nPageWrites++; + + yaffs_PackTags1(&pt1, etags); + yaffs_CalcTagsECC((yaffs_Tags *)&pt1); + + /* When deleting a chunk, the upper layer provides only skeletal + * etags, one with chunkDeleted set. However, we need to update the + * tags, not erase them completely. So we use the NAND write property + * that only zeroed-bits stick and set tag bytes to all-ones and + * zero just the (not) deleted bit. + */ +#ifndef CONFIG_YAFFS_9BYTE_TAGS + if (etags->chunkDeleted) { + memset(&pt1, 0xff, 8); + /* clear delete status bit to indicate deleted */ + pt1.deleted = 0; + } +#else + ((__u8 *)&pt1)[8] = 0xff; + if (etags->chunkDeleted) { + memset(&pt1, 0xff, 8); + /* zero pageStatus byte to indicate deleted */ + ((__u8 *)&pt1)[8] = 0; + } +#endif + + memset(&ops, 0, sizeof(ops)); + ops.mode = MTD_OOB_AUTO; + ops.len = (data) ? chunkBytes : 0; + ops.ooblen = YTAG1_SIZE; + ops.datbuf = (__u8 *)data; + ops.oobbuf = (__u8 *)&pt1; + + retval = mtd->write_oob(mtd, addr, &ops); + if (retval) { + yaffs_trace(YAFFS_TRACE_MTD, + "write_oob failed, chunk %d, mtd error %d\n", + chunkInNAND, retval); + } + return retval ? YAFFS_FAIL : YAFFS_OK; +} + +/* Return with empty ExtendedTags but add eccResult. + */ +static int rettags(yaffs_ExtendedTags * etags, int eccResult, int retval) +{ + if (etags) { + memset(etags, 0, sizeof(*etags)); + etags->eccResult = eccResult; + } + return retval; +} + +/* Read a chunk (page) from NAND. + * + * Caller expects ExtendedTags data to be usable even on error; that is, + * all members except eccResult and blockBad are zeroed. + * + * - Check ECC results for data (if applicable) + * - Check for blank/erased block (return empty ExtendedTags if blank) + * - Check the PackedTags1 mini-ECC (correct if necessary/possible) + * - Convert PackedTags1 to ExtendedTags + * - Update eccResult and blockBad members to refect state. + * + * Returns YAFFS_OK or YAFFS_FAIL. + */ +int nandmtd1_ReadChunkWithTagsFromNAND(yaffs_Device *dev, + int chunkInNAND, __u8 * data, yaffs_ExtendedTags * etags) +{ + struct mtd_info * mtd = dev->genericDevice; + int chunkBytes = dev->nDataBytesPerChunk; + loff_t addr = ((loff_t)chunkInNAND) * chunkBytes; + int eccres = YAFFS_ECC_RESULT_NO_ERROR; + struct mtd_oob_ops ops; + yaffs_PackedTags1 pt1; + int retval; + int deleted; + + dev->nPageReads++; + + memset(&ops, 0, sizeof(ops)); + ops.mode = MTD_OOB_AUTO; + ops.len = (data) ? chunkBytes : 0; + ops.ooblen = YTAG1_SIZE; + ops.datbuf = data; + ops.oobbuf = (__u8 *)&pt1; + +#if (MTD_VERSION_CODE < MTD_VERSION(2,6,20)) + /* In MTD 2.6.18 to 2.6.19 nand_base.c:nand_do_read_oob() has a bug; + * help it out with ops.len = ops.ooblen when ops.datbuf == NULL. + */ + ops.len = (ops.datbuf) ? ops.len : ops.ooblen; +#endif + /* Read page and oob using MTD. + * Check status and determine ECC result. + */ + retval = mtd->read_oob(mtd, addr, &ops); + if (retval) { + yaffs_trace(YAFFS_TRACE_MTD, + "read_oob failed, chunk %d, mtd error %d\n", + chunkInNAND, retval); + } + + switch (retval) { + case 0: + /* no error */ + break; + + case -EUCLEAN: + /* MTD's ECC fixed the data */ + eccres = YAFFS_ECC_RESULT_FIXED; + dev->eccFixed++; + break; + + case -EBADMSG: + /* MTD's ECC could not fix the data */ + dev->eccUnfixed++; + /* fall into... */ + default: + rettags(etags, YAFFS_ECC_RESULT_UNFIXED, 0); + etags->blockBad = (mtd->block_isbad)(mtd, addr); + return YAFFS_FAIL; + } + + /* Check for a blank/erased chunk. + */ + if (yaffs_CheckFF((__u8 *)&pt1, 8)) { + /* when blank, upper layers want eccResult to be <= NO_ERROR */ + return rettags(etags, YAFFS_ECC_RESULT_NO_ERROR, YAFFS_OK); + } + +#ifndef CONFIG_YAFFS_9BYTE_TAGS + /* Read deleted status (bit) then return it to it's non-deleted + * state before performing tags mini-ECC check. pt1.deleted is + * inverted. + */ + deleted = !pt1.deleted; + pt1.deleted = 1; +#else + deleted = (yaffs_CountBits(((__u8 *)&pt1)[8]) < 7); +#endif + + /* Check the packed tags mini-ECC and correct if necessary/possible. + */ + retval = yaffs_CheckECCOnTags((yaffs_Tags *)&pt1); + switch (retval) { + case 0: + /* no tags error, use MTD result */ + break; + case 1: + /* recovered tags-ECC error */ + dev->tagsEccFixed++; + if (eccres == YAFFS_ECC_RESULT_NO_ERROR) + eccres = YAFFS_ECC_RESULT_FIXED; + break; + default: + /* unrecovered tags-ECC error */ + dev->tagsEccUnfixed++; + return rettags(etags, YAFFS_ECC_RESULT_UNFIXED, YAFFS_FAIL); + } + + /* Unpack the tags to extended form and set ECC result. + * [set shouldBeFF just to keep yaffs_UnpackTags1 happy] + */ + pt1.shouldBeFF = 0xFFFFFFFF; + yaffs_UnpackTags1(etags, &pt1); + etags->eccResult = eccres; + + /* Set deleted state */ + etags->chunkDeleted = deleted; + return YAFFS_OK; +} + +/* Mark a block bad. + * + * This is a persistant state. + * Use of this function should be rare. + * + * Returns YAFFS_OK or YAFFS_FAIL. + */ +int nandmtd1_MarkNANDBlockBad(struct yaffs_DeviceStruct *dev, int blockNo) +{ + struct mtd_info * mtd = dev->genericDevice; + int blocksize = dev->nChunksPerBlock * dev->nDataBytesPerChunk; + int retval; + + yaffs_trace(YAFFS_TRACE_BAD_BLOCKS, "marking block %d bad\n", blockNo); + + retval = mtd->block_markbad(mtd, (loff_t)blocksize * blockNo); + return (retval) ? YAFFS_FAIL : YAFFS_OK; +} + +/* Check any MTD prerequists. + * + * Returns YAFFS_OK or YAFFS_FAIL. + */ +static int nandmtd1_TestPrerequists(struct mtd_info * mtd) +{ + /* 2.6.18 has mtd->ecclayout->oobavail */ + /* 2.6.21 has mtd->ecclayout->oobavail and mtd->oobavail */ + int oobavail = mtd->ecclayout->oobavail; + + if (oobavail < YTAG1_SIZE) { + yaffs_trace(YAFFS_TRACE_ERROR, + "mtd device has only %d bytes for tags, need %d\n", + oobavail, YTAG1_SIZE); + return YAFFS_FAIL; + } + return YAFFS_OK; +} + +/* Query for the current state of a specific block. + * + * Examine the tags of the first chunk of the block and return the state: + * - YAFFS_BLOCK_STATE_DEAD, the block is marked bad + * - YAFFS_BLOCK_STATE_NEEDS_SCANNING, the block is in use + * - YAFFS_BLOCK_STATE_EMPTY, the block is clean + * + * Always returns YAFFS_OK. + */ +int nandmtd1_QueryNANDBlock(struct yaffs_DeviceStruct *dev, int blockNo, + yaffs_BlockState * pState, __u32 *pSequenceNumber) +{ + struct mtd_info * mtd = dev->genericDevice; + int chunkNo = blockNo * dev->nChunksPerBlock; + loff_t addr = (loff_t)chunkNo * dev->nDataBytesPerChunk; + yaffs_ExtendedTags etags; + int state = YAFFS_BLOCK_STATE_DEAD; + int seqnum = 0; + int retval; + + /* We don't yet have a good place to test for MTD config prerequists. + * Do it here as we are called during the initial scan. + */ + if (nandmtd1_TestPrerequists(mtd) != YAFFS_OK) { + return YAFFS_FAIL; + } + + retval = nandmtd1_ReadChunkWithTagsFromNAND(dev, chunkNo, NULL, &etags); + etags.blockBad = (mtd->block_isbad)(mtd, addr); + if (etags.blockBad) { + yaffs_trace(YAFFS_TRACE_BAD_BLOCKS, + "block %d is marked bad\n", blockNo); + state = YAFFS_BLOCK_STATE_DEAD; + } + else if (etags.eccResult != YAFFS_ECC_RESULT_NO_ERROR) { + /* bad tags, need to look more closely */ + state = YAFFS_BLOCK_STATE_NEEDS_SCANNING; + } + else if (etags.chunkUsed) { + state = YAFFS_BLOCK_STATE_NEEDS_SCANNING; + seqnum = etags.sequenceNumber; + } + else { + state = YAFFS_BLOCK_STATE_EMPTY; + } + + *pState = state; + *pSequenceNumber = seqnum; + + /* query always succeeds */ + return YAFFS_OK; +} + +#endif /*MTD_VERSION*/ diff --git a/fs/yaffs2/yaffs_mtdif1.h b/fs/yaffs2/yaffs_mtdif1.h new file mode 100644 index 0000000..5fa056c --- /dev/null +++ b/fs/yaffs2/yaffs_mtdif1.h @@ -0,0 +1,28 @@ +/* + * YAFFS: Yet another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_MTDIF1_H__ +#define __YAFFS_MTDIF1_H__ + +int nandmtd1_WriteChunkWithTagsToNAND(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, const yaffs_ExtendedTags * tags); + +int nandmtd1_ReadChunkWithTagsFromNAND(yaffs_Device * dev, int chunkInNAND, + __u8 * data, yaffs_ExtendedTags * tags); + +int nandmtd1_MarkNANDBlockBad(struct yaffs_DeviceStruct *dev, int blockNo); + +int nandmtd1_QueryNANDBlock(struct yaffs_DeviceStruct *dev, int blockNo, + yaffs_BlockState * state, __u32 *sequenceNumber); + +#endif diff --git a/fs/yaffs2/yaffs_mtdif2.c b/fs/yaffs2/yaffs_mtdif2.c new file mode 100644 index 0000000..ff4531b --- /dev/null +++ b/fs/yaffs2/yaffs_mtdif2.c @@ -0,0 +1,248 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* mtd interface for YAFFS2 */ + +const char *yaffs_mtdif2_c_version = + "$Id: yaffs_mtdif2.c,v 1.22 2008/11/02 22:47:13 charles Exp $"; + +#include "yportenv.h" + + +#include "yaffs_mtdif2.h" + +#include "linux/mtd/mtd.h" +#include "linux/types.h" +#include "linux/time.h" + +#include "yaffs_packedtags2.h" + +/* NB For use with inband tags.... + * We assume that the data buffer is of size totalBytersPerChunk so that we can also + * use it to load the tags. + */ +int nandmtd2_WriteChunkWithTagsToNAND(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, + const yaffs_ExtendedTags * tags) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + struct mtd_oob_ops ops; +#else + size_t dummy; +#endif + int retval = 0; + + loff_t addr; + + yaffs_PackedTags2 pt; + + T(YAFFS_TRACE_MTD, + (TSTR + ("nandmtd2_WriteChunkWithTagsToNAND chunk %d data %p tags %p" + TENDSTR), chunkInNAND, data, tags)); + + + addr = ((loff_t) chunkInNAND) * dev->totalBytesPerChunk; + + /* For yaffs2 writing there must be both data and tags. + * If we're using inband tags, then the tags are stuffed into + * the end of the data buffer. + */ + if(!data || !tags) + BUG(); + else if(dev->inbandTags){ + yaffs_PackedTags2TagsPart *pt2tp; + pt2tp = (yaffs_PackedTags2TagsPart *)(data + dev->nDataBytesPerChunk); + yaffs_PackTags2TagsPart(pt2tp,tags); + } + else + yaffs_PackTags2(&pt, tags); + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) + ops.mode = MTD_OOB_AUTO; + ops.ooblen = (dev->inbandTags) ? 0 : sizeof(pt); + ops.len = dev->totalBytesPerChunk; + ops.ooboffs = 0; + ops.datbuf = (__u8 *)data; + ops.oobbuf = (dev->inbandTags) ? NULL : (void *)&pt; + retval = mtd->write_oob(mtd, addr, &ops); + +#else + if (!dev->inbandTags) { + retval = + mtd->write_ecc(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data, (__u8 *) & pt, NULL); + } else { + retval = + mtd->write(mtd, addr, dev->totalBytesPerChunk, &dummy, + data); + } +#endif + + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; +} + +int nandmtd2_ReadChunkWithTagsFromNAND(yaffs_Device * dev, int chunkInNAND, + __u8 * data, yaffs_ExtendedTags * tags) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); +#if (MTD_VERSION_CODE > MTD_VERSION(2,6,17)) + struct mtd_oob_ops ops; +#endif + size_t dummy; + int retval = 0; + int localData = 0; + + loff_t addr = ((loff_t) chunkInNAND) * dev->totalBytesPerChunk; + + yaffs_PackedTags2 pt; + + T(YAFFS_TRACE_MTD, + (TSTR + ("nandmtd2_ReadChunkWithTagsFromNAND chunk %d data %p tags %p" + TENDSTR), chunkInNAND, data, tags)); + + if(dev->inbandTags){ + + if(!data) { + localData = 1; + data = yaffs_GetTempBuffer(dev,__LINE__); + } + + + } + + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,17)) + if (dev->inbandTags || (data && !tags)) + retval = mtd->read(mtd, addr, dev->totalBytesPerChunk, + &dummy, data); + else if (tags) { + ops.mode = MTD_OOB_AUTO; + ops.ooblen = sizeof(pt); + ops.len = data ? dev->nDataBytesPerChunk : sizeof(pt); + ops.ooboffs = 0; + ops.datbuf = data; + ops.oobbuf = dev->spareBuffer; + retval = mtd->read_oob(mtd, addr, &ops); + } +#else + if (!dev->inbandTags && data && tags) { + + retval = mtd->read_ecc(mtd, addr, dev->nDataBytesPerChunk, + &dummy, data, dev->spareBuffer, + NULL); + } else { + if (data) + retval = + mtd->read(mtd, addr, dev->nDataBytesPerChunk, &dummy, + data); + if (!dev->inbandTags && tags) + retval = + mtd->read_oob(mtd, addr, mtd->oobsize, &dummy, + dev->spareBuffer); + } +#endif + + + if(dev->inbandTags){ + if(tags){ + yaffs_PackedTags2TagsPart * pt2tp; + pt2tp = (yaffs_PackedTags2TagsPart *)&data[dev->nDataBytesPerChunk]; + yaffs_UnpackTags2TagsPart(tags,pt2tp); + } + } + else { + if (tags){ + memcpy(&pt, dev->spareBuffer, sizeof(pt)); + yaffs_UnpackTags2(tags, &pt); + } + } + + if(localData) + yaffs_ReleaseTempBuffer(dev,data,__LINE__); + + if(tags && retval == -EBADMSG && tags->eccResult == YAFFS_ECC_RESULT_NO_ERROR) + tags->eccResult = YAFFS_ECC_RESULT_UNFIXED; + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; +} + +int nandmtd2_MarkNANDBlockBad(struct yaffs_DeviceStruct *dev, int blockNo) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); + int retval; + T(YAFFS_TRACE_MTD, + (TSTR("nandmtd2_MarkNANDBlockBad %d" TENDSTR), blockNo)); + + retval = + mtd->block_markbad(mtd, + blockNo * dev->nChunksPerBlock * + dev->totalBytesPerChunk); + + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; + +} + +int nandmtd2_QueryNANDBlock(struct yaffs_DeviceStruct *dev, int blockNo, + yaffs_BlockState * state, __u32 *sequenceNumber) +{ + struct mtd_info *mtd = (struct mtd_info *)(dev->genericDevice); + int retval; + + T(YAFFS_TRACE_MTD, + (TSTR("nandmtd2_QueryNANDBlock %d" TENDSTR), blockNo)); + retval = + mtd->block_isbad(mtd, + blockNo * dev->nChunksPerBlock * + dev->totalBytesPerChunk); + + if (retval) { + T(YAFFS_TRACE_MTD, (TSTR("block is bad" TENDSTR))); + + *state = YAFFS_BLOCK_STATE_DEAD; + *sequenceNumber = 0; + } else { + yaffs_ExtendedTags t; + nandmtd2_ReadChunkWithTagsFromNAND(dev, + blockNo * + dev->nChunksPerBlock, NULL, + &t); + + if (t.chunkUsed) { + *sequenceNumber = t.sequenceNumber; + *state = YAFFS_BLOCK_STATE_NEEDS_SCANNING; + } else { + *sequenceNumber = 0; + *state = YAFFS_BLOCK_STATE_EMPTY; + } + } + T(YAFFS_TRACE_MTD, + (TSTR("block is bad seq %d state %d" TENDSTR), *sequenceNumber, + *state)); + + if (retval == 0) + return YAFFS_OK; + else + return YAFFS_FAIL; +} + diff --git a/fs/yaffs2/yaffs_mtdif2.h b/fs/yaffs2/yaffs_mtdif2.h new file mode 100644 index 0000000..19f63b3 --- /dev/null +++ b/fs/yaffs2/yaffs_mtdif2.h @@ -0,0 +1,29 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_MTDIF2_H__ +#define __YAFFS_MTDIF2_H__ + +#include "yaffs_guts.h" +int nandmtd2_WriteChunkWithTagsToNAND(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, + const yaffs_ExtendedTags * tags); +int nandmtd2_ReadChunkWithTagsFromNAND(yaffs_Device * dev, int chunkInNAND, + __u8 * data, yaffs_ExtendedTags * tags); +int nandmtd2_MarkNANDBlockBad(struct yaffs_DeviceStruct *dev, int blockNo); +int nandmtd2_QueryNANDBlock(struct yaffs_DeviceStruct *dev, int blockNo, + yaffs_BlockState * state, __u32 *sequenceNumber); + +#endif diff --git a/fs/yaffs2/yaffs_nand.c b/fs/yaffs2/yaffs_nand.c new file mode 100644 index 0000000..932b2b5 --- /dev/null +++ b/fs/yaffs2/yaffs_nand.c @@ -0,0 +1,135 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +const char *yaffs_nand_c_version = + "$Id: yaffs_nand.c,v 1.9 2008/05/05 07:58:58 charles Exp $"; + +#include "yaffs_nand.h" +#include "yaffs_tagscompat.h" +#include "yaffs_tagsvalidity.h" + +#include "yaffs_getblockinfo.h" + +int yaffs_ReadChunkWithTagsFromNAND(yaffs_Device * dev, int chunkInNAND, + __u8 * buffer, + yaffs_ExtendedTags * tags) +{ + int result; + yaffs_ExtendedTags localTags; + + int realignedChunkInNAND = chunkInNAND - dev->chunkOffset; + + /* If there are no tags provided, use local tags to get prioritised gc working */ + if(!tags) + tags = &localTags; + + if (dev->readChunkWithTagsFromNAND) + result = dev->readChunkWithTagsFromNAND(dev, realignedChunkInNAND, buffer, + tags); + else + result = yaffs_TagsCompatabilityReadChunkWithTagsFromNAND(dev, + realignedChunkInNAND, + buffer, + tags); + if(tags && + tags->eccResult > YAFFS_ECC_RESULT_NO_ERROR){ + + yaffs_BlockInfo *bi = yaffs_GetBlockInfo(dev, chunkInNAND/dev->nChunksPerBlock); + yaffs_HandleChunkError(dev,bi); + } + + return result; +} + +int yaffs_WriteChunkWithTagsToNAND(yaffs_Device * dev, + int chunkInNAND, + const __u8 * buffer, + yaffs_ExtendedTags * tags) +{ + chunkInNAND -= dev->chunkOffset; + + + if (tags) { + tags->sequenceNumber = dev->sequenceNumber; + tags->chunkUsed = 1; + if (!yaffs_ValidateTags(tags)) { + T(YAFFS_TRACE_ERROR, + (TSTR("Writing uninitialised tags" TENDSTR))); + YBUG(); + } + T(YAFFS_TRACE_WRITE, + (TSTR("Writing chunk %d tags %d %d" TENDSTR), chunkInNAND, + tags->objectId, tags->chunkId)); + } else { + T(YAFFS_TRACE_ERROR, (TSTR("Writing with no tags" TENDSTR))); + YBUG(); + } + + if (dev->writeChunkWithTagsToNAND) + return dev->writeChunkWithTagsToNAND(dev, chunkInNAND, buffer, + tags); + else + return yaffs_TagsCompatabilityWriteChunkWithTagsToNAND(dev, + chunkInNAND, + buffer, + tags); +} + +int yaffs_MarkBlockBad(yaffs_Device * dev, int blockNo) +{ + blockNo -= dev->blockOffset; + +; + if (dev->markNANDBlockBad) + return dev->markNANDBlockBad(dev, blockNo); + else + return yaffs_TagsCompatabilityMarkNANDBlockBad(dev, blockNo); +} + +int yaffs_QueryInitialBlockState(yaffs_Device * dev, + int blockNo, + yaffs_BlockState * state, + __u32 *sequenceNumber) +{ + blockNo -= dev->blockOffset; + + if (dev->queryNANDBlock) + return dev->queryNANDBlock(dev, blockNo, state, sequenceNumber); + else + return yaffs_TagsCompatabilityQueryNANDBlock(dev, blockNo, + state, + sequenceNumber); +} + + +int yaffs_EraseBlockInNAND(struct yaffs_DeviceStruct *dev, + int blockInNAND) +{ + int result; + + blockInNAND -= dev->blockOffset; + + + dev->nBlockErasures++; + result = dev->eraseBlockInNAND(dev, blockInNAND); + + return result; +} + +int yaffs_InitialiseNAND(struct yaffs_DeviceStruct *dev) +{ + return dev->initialiseNAND(dev); +} + + + diff --git a/fs/yaffs2/yaffs_nand.h b/fs/yaffs2/yaffs_nand.h new file mode 100644 index 0000000..5fa334b --- /dev/null +++ b/fs/yaffs2/yaffs_nand.h @@ -0,0 +1,44 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_NAND_H__ +#define __YAFFS_NAND_H__ +#include "yaffs_guts.h" + + + +int yaffs_ReadChunkWithTagsFromNAND(yaffs_Device * dev, int chunkInNAND, + __u8 * buffer, + yaffs_ExtendedTags * tags); + +int yaffs_WriteChunkWithTagsToNAND(yaffs_Device * dev, + int chunkInNAND, + const __u8 * buffer, + yaffs_ExtendedTags * tags); + +int yaffs_MarkBlockBad(yaffs_Device * dev, int blockNo); + +int yaffs_QueryInitialBlockState(yaffs_Device * dev, + int blockNo, + yaffs_BlockState * state, + unsigned *sequenceNumber); + +int yaffs_EraseBlockInNAND(struct yaffs_DeviceStruct *dev, + int blockInNAND); + +int yaffs_InitialiseNAND(struct yaffs_DeviceStruct *dev); + +#endif + diff --git a/fs/yaffs2/yaffs_nandemul2k.h b/fs/yaffs2/yaffs_nandemul2k.h new file mode 100644 index 0000000..c8576b3 --- /dev/null +++ b/fs/yaffs2/yaffs_nandemul2k.h @@ -0,0 +1,39 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +/* Interface to emulated NAND functions (2k page size) */ + +#ifndef __YAFFS_NANDEMUL2K_H__ +#define __YAFFS_NANDEMUL2K_H__ + +#include "yaffs_guts.h" + +int nandemul2k_WriteChunkWithTagsToNAND(struct yaffs_DeviceStruct *dev, + int chunkInNAND, const __u8 * data, + const yaffs_ExtendedTags * tags); +int nandemul2k_ReadChunkWithTagsFromNAND(struct yaffs_DeviceStruct *dev, + int chunkInNAND, __u8 * data, + yaffs_ExtendedTags * tags); +int nandemul2k_MarkNANDBlockBad(struct yaffs_DeviceStruct *dev, int blockNo); +int nandemul2k_QueryNANDBlock(struct yaffs_DeviceStruct *dev, int blockNo, + yaffs_BlockState * state, __u32 *sequenceNumber); +int nandemul2k_EraseBlockInNAND(struct yaffs_DeviceStruct *dev, + int blockInNAND); +int nandemul2k_InitialiseNAND(struct yaffs_DeviceStruct *dev); +int nandemul2k_GetBytesPerChunk(void); +int nandemul2k_GetChunksPerBlock(void); +int nandemul2k_GetNumberOfBlocks(void); + +#endif diff --git a/fs/yaffs2/yaffs_packedtags1.c b/fs/yaffs2/yaffs_packedtags1.c new file mode 100644 index 0000000..f480bf1 --- /dev/null +++ b/fs/yaffs2/yaffs_packedtags1.c @@ -0,0 +1,52 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "yaffs_packedtags1.h" +#include "yportenv.h" + +void yaffs_PackTags1(yaffs_PackedTags1 * pt, const yaffs_ExtendedTags * t) +{ + pt->chunkId = t->chunkId; + pt->serialNumber = t->serialNumber; + pt->byteCount = t->byteCount; + pt->objectId = t->objectId; + pt->ecc = 0; + pt->deleted = (t->chunkDeleted) ? 0 : 1; + pt->unusedStuff = 0; + pt->shouldBeFF = 0xFFFFFFFF; + +} + +void yaffs_UnpackTags1(yaffs_ExtendedTags * t, const yaffs_PackedTags1 * pt) +{ + static const __u8 allFF[] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +0xff }; + + if (memcmp(allFF, pt, sizeof(yaffs_PackedTags1))) { + t->blockBad = 0; + if (pt->shouldBeFF != 0xFFFFFFFF) { + t->blockBad = 1; + } + t->chunkUsed = 1; + t->objectId = pt->objectId; + t->chunkId = pt->chunkId; + t->byteCount = pt->byteCount; + t->eccResult = YAFFS_ECC_RESULT_NO_ERROR; + t->chunkDeleted = (pt->deleted) ? 0 : 1; + t->serialNumber = pt->serialNumber; + } else { + memset(t, 0, sizeof(yaffs_ExtendedTags)); + + } +} diff --git a/fs/yaffs2/yaffs_packedtags1.h b/fs/yaffs2/yaffs_packedtags1.h new file mode 100644 index 0000000..776c5c2 --- /dev/null +++ b/fs/yaffs2/yaffs_packedtags1.h @@ -0,0 +1,37 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +/* This is used to pack YAFFS1 tags, not YAFFS2 tags. */ + +#ifndef __YAFFS_PACKEDTAGS1_H__ +#define __YAFFS_PACKEDTAGS1_H__ + +#include "yaffs_guts.h" + +typedef struct { + unsigned chunkId:20; + unsigned serialNumber:2; + unsigned byteCount:10; + unsigned objectId:18; + unsigned ecc:12; + unsigned deleted:1; + unsigned unusedStuff:1; + unsigned shouldBeFF; + +} yaffs_PackedTags1; + +void yaffs_PackTags1(yaffs_PackedTags1 * pt, const yaffs_ExtendedTags * t); +void yaffs_UnpackTags1(yaffs_ExtendedTags * t, const yaffs_PackedTags1 * pt); +#endif diff --git a/fs/yaffs2/yaffs_packedtags2.c b/fs/yaffs2/yaffs_packedtags2.c new file mode 100644 index 0000000..957ed8b --- /dev/null +++ b/fs/yaffs2/yaffs_packedtags2.c @@ -0,0 +1,208 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "yaffs_packedtags2.h" +#include "yportenv.h" +#include "yaffs_tagsvalidity.h" + +/* This code packs a set of extended tags into a binary structure for + * NAND storage + */ + +/* Some of the information is "extra" struff which can be packed in to + * speed scanning + * This is defined by having the EXTRA_HEADER_INFO_FLAG set. + */ + +/* Extra flags applied to chunkId */ + +#define EXTRA_HEADER_INFO_FLAG 0x80000000 +#define EXTRA_SHRINK_FLAG 0x40000000 +#define EXTRA_SHADOWS_FLAG 0x20000000 +#define EXTRA_SPARE_FLAGS 0x10000000 + +#define ALL_EXTRA_FLAGS 0xF0000000 + +/* Also, the top 4 bits of the object Id are set to the object type. */ +#define EXTRA_OBJECT_TYPE_SHIFT (28) +#define EXTRA_OBJECT_TYPE_MASK ((0x0F) << EXTRA_OBJECT_TYPE_SHIFT) + + +static void yaffs_DumpPackedTags2TagsPart(const yaffs_PackedTags2TagsPart * ptt) +{ + T(YAFFS_TRACE_MTD, + (TSTR("packed tags obj %d chunk %d byte %d seq %d" TENDSTR), + ptt->objectId, ptt->chunkId, ptt->byteCount, + ptt->sequenceNumber)); +} +static void yaffs_DumpPackedTags2(const yaffs_PackedTags2 * pt) +{ + yaffs_DumpPackedTags2TagsPart(&pt->t); +} + +static void yaffs_DumpTags2(const yaffs_ExtendedTags * t) +{ + T(YAFFS_TRACE_MTD, + (TSTR + ("ext.tags eccres %d blkbad %d chused %d obj %d chunk%d byte %d del %d ser %d seq %d" + TENDSTR), t->eccResult, t->blockBad, t->chunkUsed, t->objectId, + t->chunkId, t->byteCount, t->chunkDeleted, t->serialNumber, + t->sequenceNumber)); + +} + +void yaffs_PackTags2TagsPart(yaffs_PackedTags2TagsPart * ptt, const yaffs_ExtendedTags * t) +{ + ptt->chunkId = t->chunkId; + ptt->sequenceNumber = t->sequenceNumber; + ptt->byteCount = t->byteCount; + ptt->objectId = t->objectId; + + if (t->chunkId == 0 && t->extraHeaderInfoAvailable) { + /* Store the extra header info instead */ + /* We save the parent object in the chunkId */ + ptt->chunkId = EXTRA_HEADER_INFO_FLAG + | t->extraParentObjectId; + if (t->extraIsShrinkHeader) { + ptt->chunkId |= EXTRA_SHRINK_FLAG; + } + if (t->extraShadows) { + ptt->chunkId |= EXTRA_SHADOWS_FLAG; + } + + ptt->objectId &= ~EXTRA_OBJECT_TYPE_MASK; + ptt->objectId |= + (t->extraObjectType << EXTRA_OBJECT_TYPE_SHIFT); + + if (t->extraObjectType == YAFFS_OBJECT_TYPE_HARDLINK) { + ptt->byteCount = t->extraEquivalentObjectId; + } else if (t->extraObjectType == YAFFS_OBJECT_TYPE_FILE) { + ptt->byteCount = t->extraFileLength; + } else { + ptt->byteCount = 0; + } + } + + yaffs_DumpPackedTags2TagsPart(ptt); + yaffs_DumpTags2(t); +} + + +void yaffs_PackTags2(yaffs_PackedTags2 * pt, const yaffs_ExtendedTags * t) +{ + yaffs_PackTags2TagsPart(&pt->t,t); + +#ifndef YAFFS_IGNORE_TAGS_ECC + { + yaffs_ECCCalculateOther((unsigned char *)&pt->t, + sizeof(yaffs_PackedTags2TagsPart), + &pt->ecc); + } +#endif +} + + +void yaffs_UnpackTags2TagsPart(yaffs_ExtendedTags * t, yaffs_PackedTags2TagsPart * ptt) +{ + + memset(t, 0, sizeof(yaffs_ExtendedTags)); + + yaffs_InitialiseTags(t); + + if (ptt->sequenceNumber != 0xFFFFFFFF) { + t->blockBad = 0; + t->chunkUsed = 1; + t->objectId = ptt->objectId; + t->chunkId = ptt->chunkId; + t->byteCount = ptt->byteCount; + t->chunkDeleted = 0; + t->serialNumber = 0; + t->sequenceNumber = ptt->sequenceNumber; + + /* Do extra header info stuff */ + + if (ptt->chunkId & EXTRA_HEADER_INFO_FLAG) { + t->chunkId = 0; + t->byteCount = 0; + + t->extraHeaderInfoAvailable = 1; + t->extraParentObjectId = + ptt->chunkId & (~(ALL_EXTRA_FLAGS)); + t->extraIsShrinkHeader = + (ptt->chunkId & EXTRA_SHRINK_FLAG) ? 1 : 0; + t->extraShadows = + (ptt->chunkId & EXTRA_SHADOWS_FLAG) ? 1 : 0; + t->extraObjectType = + ptt->objectId >> EXTRA_OBJECT_TYPE_SHIFT; + t->objectId &= ~EXTRA_OBJECT_TYPE_MASK; + + if (t->extraObjectType == YAFFS_OBJECT_TYPE_HARDLINK) { + t->extraEquivalentObjectId = ptt->byteCount; + } else { + t->extraFileLength = ptt->byteCount; + } + } + } + + yaffs_DumpPackedTags2TagsPart(ptt); + yaffs_DumpTags2(t); + +} + + +void yaffs_UnpackTags2(yaffs_ExtendedTags * t, yaffs_PackedTags2 * pt) +{ + + yaffs_UnpackTags2TagsPart(t,&pt->t); + + if (pt->t.sequenceNumber != 0xFFFFFFFF) { + /* Page is in use */ +#ifdef YAFFS_IGNORE_TAGS_ECC + { + t->eccResult = YAFFS_ECC_RESULT_NO_ERROR; + } +#else + { + yaffs_ECCOther ecc; + int result; + yaffs_ECCCalculateOther((unsigned char *)&pt->t, + sizeof + (yaffs_PackedTags2TagsPart), + &ecc); + result = + yaffs_ECCCorrectOther((unsigned char *)&pt->t, + sizeof + (yaffs_PackedTags2TagsPart), + &pt->ecc, &ecc); + switch(result){ + case 0: + t->eccResult = YAFFS_ECC_RESULT_NO_ERROR; + break; + case 1: + t->eccResult = YAFFS_ECC_RESULT_FIXED; + break; + case -1: + t->eccResult = YAFFS_ECC_RESULT_UNFIXED; + break; + default: + t->eccResult = YAFFS_ECC_RESULT_UNKNOWN; + } + } +#endif + } + + yaffs_DumpPackedTags2(pt); + yaffs_DumpTags2(t); + +} + diff --git a/fs/yaffs2/yaffs_packedtags2.h b/fs/yaffs2/yaffs_packedtags2.h new file mode 100644 index 0000000..75761d3 --- /dev/null +++ b/fs/yaffs2/yaffs_packedtags2.h @@ -0,0 +1,43 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +/* This is used to pack YAFFS2 tags, not YAFFS1tags. */ + +#ifndef __YAFFS_PACKEDTAGS2_H__ +#define __YAFFS_PACKEDTAGS2_H__ + +#include "yaffs_guts.h" +#include "yaffs_ecc.h" + +typedef struct { + unsigned sequenceNumber; + unsigned objectId; + unsigned chunkId; + unsigned byteCount; +} yaffs_PackedTags2TagsPart; + +typedef struct { + yaffs_PackedTags2TagsPart t; + yaffs_ECCOther ecc; +} yaffs_PackedTags2; + +/* Full packed tags with ECC, used for oob tags */ +void yaffs_PackTags2(yaffs_PackedTags2 * pt, const yaffs_ExtendedTags * t); +void yaffs_UnpackTags2(yaffs_ExtendedTags * t, yaffs_PackedTags2 * pt); + +/* Only the tags part (no ECC for use with inband tags */ +void yaffs_PackTags2TagsPart(yaffs_PackedTags2TagsPart * pt, const yaffs_ExtendedTags * t); +void yaffs_UnpackTags2TagsPart(yaffs_ExtendedTags * t, yaffs_PackedTags2TagsPart * pt); +#endif diff --git a/fs/yaffs2/yaffs_qsort.c b/fs/yaffs2/yaffs_qsort.c new file mode 100644 index 0000000..474be9c --- /dev/null +++ b/fs/yaffs2/yaffs_qsort.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "yportenv.h" +//#include + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static __inline void +swapfunc(char *a, char *b, int n, int swaptype) +{ + if (swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static __inline char * +med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *)) +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +void +yaffs_qsort(void *aa, size_t n, size_t es, + int (*cmp)(const void *, const void *)) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + register char *a = aa; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es) + for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = (char *)a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es) + for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min((long)(pd - pc), (long)(pn - pd - es)); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + yaffs_qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* yaffs_qsort(pn - r, r / es, es, cmp);*/ +} diff --git a/fs/yaffs2/yaffs_qsort.h b/fs/yaffs2/yaffs_qsort.h new file mode 100644 index 0000000..610b7ec --- /dev/null +++ b/fs/yaffs2/yaffs_qsort.h @@ -0,0 +1,23 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + + +#ifndef __YAFFS_QSORT_H__ +#define __YAFFS_QSORT_H__ + +extern void yaffs_qsort (void *const base, size_t total_elems, size_t size, + int (*cmp)(const void *, const void *)); + +#endif diff --git a/fs/yaffs2/yaffs_tagscompat.c b/fs/yaffs2/yaffs_tagscompat.c new file mode 100644 index 0000000..f6c4053 --- /dev/null +++ b/fs/yaffs2/yaffs_tagscompat.c @@ -0,0 +1,547 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "yaffs_guts.h" +#include "yaffs_tagscompat.h" +#include "yaffs_ecc.h" +#include "yaffs_getblockinfo.h" + +static void yaffs_HandleReadDataError(yaffs_Device * dev, int chunkInNAND); +#ifdef NOTYET +static void yaffs_CheckWrittenBlock(yaffs_Device * dev, int chunkInNAND); +static void yaffs_HandleWriteChunkOk(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, + const yaffs_Spare * spare); +static void yaffs_HandleUpdateChunk(yaffs_Device * dev, int chunkInNAND, + const yaffs_Spare * spare); +static void yaffs_HandleWriteChunkError(yaffs_Device * dev, int chunkInNAND); +#endif + +static const char yaffs_countBitsTable[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 +}; + +int yaffs_CountBits(__u8 x) +{ + int retVal; + retVal = yaffs_countBitsTable[x]; + return retVal; +} + +/********** Tags ECC calculations *********/ + +void yaffs_CalcECC(const __u8 * data, yaffs_Spare * spare) +{ + yaffs_ECCCalculate(data, spare->ecc1); + yaffs_ECCCalculate(&data[256], spare->ecc2); +} + +void yaffs_CalcTagsECC(yaffs_Tags * tags) +{ + /* Calculate an ecc */ + + unsigned char *b = ((yaffs_TagsUnion *) tags)->asBytes; + unsigned i, j; + unsigned ecc = 0; + unsigned bit = 0; + + tags->ecc = 0; + + for (i = 0; i < 8; i++) { + for (j = 1; j & 0xff; j <<= 1) { + bit++; + if (b[i] & j) { + ecc ^= bit; + } + } + } + + tags->ecc = ecc; + +} + +int yaffs_CheckECCOnTags(yaffs_Tags * tags) +{ + unsigned ecc = tags->ecc; + + yaffs_CalcTagsECC(tags); + + ecc ^= tags->ecc; + + if (ecc && ecc <= 64) { + /* TODO: Handle the failure better. Retire? */ + unsigned char *b = ((yaffs_TagsUnion *) tags)->asBytes; + + ecc--; + + b[ecc / 8] ^= (1 << (ecc & 7)); + + /* Now recvalc the ecc */ + yaffs_CalcTagsECC(tags); + + return 1; /* recovered error */ + } else if (ecc) { + /* Wierd ecc failure value */ + /* TODO Need to do somethiong here */ + return -1; /* unrecovered error */ + } + + return 0; +} + +/********** Tags **********/ + +static void yaffs_LoadTagsIntoSpare(yaffs_Spare * sparePtr, + yaffs_Tags * tagsPtr) +{ + yaffs_TagsUnion *tu = (yaffs_TagsUnion *) tagsPtr; + + yaffs_CalcTagsECC(tagsPtr); + + sparePtr->tagByte0 = tu->asBytes[0]; + sparePtr->tagByte1 = tu->asBytes[1]; + sparePtr->tagByte2 = tu->asBytes[2]; + sparePtr->tagByte3 = tu->asBytes[3]; + sparePtr->tagByte4 = tu->asBytes[4]; + sparePtr->tagByte5 = tu->asBytes[5]; + sparePtr->tagByte6 = tu->asBytes[6]; + sparePtr->tagByte7 = tu->asBytes[7]; +} + +static void yaffs_GetTagsFromSpare(yaffs_Device * dev, yaffs_Spare * sparePtr, + yaffs_Tags * tagsPtr) +{ + yaffs_TagsUnion *tu = (yaffs_TagsUnion *) tagsPtr; + int result; + + tu->asBytes[0] = sparePtr->tagByte0; + tu->asBytes[1] = sparePtr->tagByte1; + tu->asBytes[2] = sparePtr->tagByte2; + tu->asBytes[3] = sparePtr->tagByte3; + tu->asBytes[4] = sparePtr->tagByte4; + tu->asBytes[5] = sparePtr->tagByte5; + tu->asBytes[6] = sparePtr->tagByte6; + tu->asBytes[7] = sparePtr->tagByte7; + + result = yaffs_CheckECCOnTags(tagsPtr); + if (result > 0) { + dev->tagsEccFixed++; + } else if (result < 0) { + dev->tagsEccUnfixed++; + } +} + +static void yaffs_SpareInitialise(yaffs_Spare * spare) +{ + memset(spare, 0xFF, sizeof(yaffs_Spare)); +} + +static int yaffs_WriteChunkToNAND(struct yaffs_DeviceStruct *dev, + int chunkInNAND, const __u8 * data, + yaffs_Spare * spare) +{ + if (chunkInNAND < dev->startBlock * dev->nChunksPerBlock) { + T(YAFFS_TRACE_ERROR, + (TSTR("**>> yaffs chunk %d is not valid" TENDSTR), + chunkInNAND)); + return YAFFS_FAIL; + } + + dev->nPageWrites++; + return dev->writeChunkToNAND(dev, chunkInNAND, data, spare); +} + +static int yaffs_ReadChunkFromNAND(struct yaffs_DeviceStruct *dev, + int chunkInNAND, + __u8 * data, + yaffs_Spare * spare, + yaffs_ECCResult * eccResult, + int doErrorCorrection) +{ + int retVal; + yaffs_Spare localSpare; + + dev->nPageReads++; + + if (!spare && data) { + /* If we don't have a real spare, then we use a local one. */ + /* Need this for the calculation of the ecc */ + spare = &localSpare; + } + + if (!dev->useNANDECC) { + retVal = dev->readChunkFromNAND(dev, chunkInNAND, data, spare); + if (data && doErrorCorrection) { + /* Do ECC correction */ + /* Todo handle any errors */ + int eccResult1, eccResult2; + __u8 calcEcc[3]; + + yaffs_ECCCalculate(data, calcEcc); + eccResult1 = + yaffs_ECCCorrect(data, spare->ecc1, calcEcc); + yaffs_ECCCalculate(&data[256], calcEcc); + eccResult2 = + yaffs_ECCCorrect(&data[256], spare->ecc2, calcEcc); + + if (eccResult1 > 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>yaffs ecc error fix performed on chunk %d:0" + TENDSTR), chunkInNAND)); + dev->eccFixed++; + } else if (eccResult1 < 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>yaffs ecc error unfixed on chunk %d:0" + TENDSTR), chunkInNAND)); + dev->eccUnfixed++; + } + + if (eccResult2 > 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>yaffs ecc error fix performed on chunk %d:1" + TENDSTR), chunkInNAND)); + dev->eccFixed++; + } else if (eccResult2 < 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>yaffs ecc error unfixed on chunk %d:1" + TENDSTR), chunkInNAND)); + dev->eccUnfixed++; + } + + if (eccResult1 || eccResult2) { + /* We had a data problem on this page */ + yaffs_HandleReadDataError(dev, chunkInNAND); + } + + if (eccResult1 < 0 || eccResult2 < 0) + *eccResult = YAFFS_ECC_RESULT_UNFIXED; + else if (eccResult1 > 0 || eccResult2 > 0) + *eccResult = YAFFS_ECC_RESULT_FIXED; + else + *eccResult = YAFFS_ECC_RESULT_NO_ERROR; + } + } else { + /* Must allocate enough memory for spare+2*sizeof(int) */ + /* for ecc results from device. */ + struct yaffs_NANDSpare nspare; + + memset(&nspare,0,sizeof(nspare)); + + retVal = + dev->readChunkFromNAND(dev, chunkInNAND, data, + (yaffs_Spare *) & nspare); + memcpy(spare, &nspare, sizeof(yaffs_Spare)); + if (data && doErrorCorrection) { + if (nspare.eccres1 > 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>mtd ecc error fix performed on chunk %d:0" + TENDSTR), chunkInNAND)); + } else if (nspare.eccres1 < 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>mtd ecc error unfixed on chunk %d:0" + TENDSTR), chunkInNAND)); + } + + if (nspare.eccres2 > 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>mtd ecc error fix performed on chunk %d:1" + TENDSTR), chunkInNAND)); + } else if (nspare.eccres2 < 0) { + T(YAFFS_TRACE_ERROR, + (TSTR + ("**>>mtd ecc error unfixed on chunk %d:1" + TENDSTR), chunkInNAND)); + } + + if (nspare.eccres1 || nspare.eccres2) { + /* We had a data problem on this page */ + yaffs_HandleReadDataError(dev, chunkInNAND); + } + + if (nspare.eccres1 < 0 || nspare.eccres2 < 0) + *eccResult = YAFFS_ECC_RESULT_UNFIXED; + else if (nspare.eccres1 > 0 || nspare.eccres2 > 0) + *eccResult = YAFFS_ECC_RESULT_FIXED; + else + *eccResult = YAFFS_ECC_RESULT_NO_ERROR; + + } + } + return retVal; +} + +#ifdef NOTYET +static int yaffs_CheckChunkErased(struct yaffs_DeviceStruct *dev, + int chunkInNAND) +{ + + static int init = 0; + static __u8 cmpbuf[YAFFS_BYTES_PER_CHUNK]; + static __u8 data[YAFFS_BYTES_PER_CHUNK]; + /* Might as well always allocate the larger size for */ + /* dev->useNANDECC == true; */ + static __u8 spare[sizeof(struct yaffs_NANDSpare)]; + + dev->readChunkFromNAND(dev, chunkInNAND, data, (yaffs_Spare *) spare); + + if (!init) { + memset(cmpbuf, 0xff, YAFFS_BYTES_PER_CHUNK); + init = 1; + } + + if (memcmp(cmpbuf, data, YAFFS_BYTES_PER_CHUNK)) + return YAFFS_FAIL; + if (memcmp(cmpbuf, spare, 16)) + return YAFFS_FAIL; + + return YAFFS_OK; + +} +#endif + +/* + * Functions for robustisizing + */ + +static void yaffs_HandleReadDataError(yaffs_Device * dev, int chunkInNAND) +{ + int blockInNAND = chunkInNAND / dev->nChunksPerBlock; + + /* Mark the block for retirement */ + yaffs_GetBlockInfo(dev, blockInNAND + dev->blockOffset)->needsRetiring = 1; + T(YAFFS_TRACE_ERROR | YAFFS_TRACE_BAD_BLOCKS, + (TSTR("**>>Block %d marked for retirement" TENDSTR), blockInNAND)); + + /* TODO: + * Just do a garbage collection on the affected block + * then retire the block + * NB recursion + */ +} + +#ifdef NOTYET +static void yaffs_CheckWrittenBlock(yaffs_Device * dev, int chunkInNAND) +{ +} + +static void yaffs_HandleWriteChunkOk(yaffs_Device * dev, int chunkInNAND, + const __u8 * data, + const yaffs_Spare * spare) +{ +} + +static void yaffs_HandleUpdateChunk(yaffs_Device * dev, int chunkInNAND, + const yaffs_Spare * spare) +{ +} + +static void yaffs_HandleWriteChunkError(yaffs_Device * dev, int chunkInNAND) +{ + int blockInNAND = chunkInNAND / dev->nChunksPerBlock; + + /* Mark the block for retirement */ + yaffs_GetBlockInfo(dev, blockInNAND)->needsRetiring = 1; + /* Delete the chunk */ + yaffs_DeleteChunk(dev, chunkInNAND, 1, __LINE__); +} + +static int yaffs_VerifyCompare(const __u8 * d0, const __u8 * d1, + const yaffs_Spare * s0, const yaffs_Spare * s1) +{ + + if (memcmp(d0, d1, YAFFS_BYTES_PER_CHUNK) != 0 || + s0->tagByte0 != s1->tagByte0 || + s0->tagByte1 != s1->tagByte1 || + s0->tagByte2 != s1->tagByte2 || + s0->tagByte3 != s1->tagByte3 || + s0->tagByte4 != s1->tagByte4 || + s0->tagByte5 != s1->tagByte5 || + s0->tagByte6 != s1->tagByte6 || + s0->tagByte7 != s1->tagByte7 || + s0->ecc1[0] != s1->ecc1[0] || + s0->ecc1[1] != s1->ecc1[1] || + s0->ecc1[2] != s1->ecc1[2] || + s0->ecc2[0] != s1->ecc2[0] || + s0->ecc2[1] != s1->ecc2[1] || s0->ecc2[2] != s1->ecc2[2]) { + return 0; + } + + return 1; +} +#endif /* NOTYET */ + +int yaffs_TagsCompatabilityWriteChunkWithTagsToNAND(yaffs_Device * dev, + int chunkInNAND, + const __u8 * data, + const yaffs_ExtendedTags * + eTags) +{ + yaffs_Spare spare; + yaffs_Tags tags; + + yaffs_SpareInitialise(&spare); + + if (eTags->chunkDeleted) { + spare.pageStatus = 0; + } else { + tags.objectId = eTags->objectId; + tags.chunkId = eTags->chunkId; + + tags.byteCountLSB = eTags->byteCount & 0x3ff; + + if(dev->nDataBytesPerChunk >= 1024){ + tags.byteCountMSB = (eTags->byteCount >> 10) & 3; + } else { + tags.byteCountMSB = 3; + } + + + tags.serialNumber = eTags->serialNumber; + + if (!dev->useNANDECC && data) { + yaffs_CalcECC(data, &spare); + } + yaffs_LoadTagsIntoSpare(&spare, &tags); + + } + + return yaffs_WriteChunkToNAND(dev, chunkInNAND, data, &spare); +} + +int yaffs_TagsCompatabilityReadChunkWithTagsFromNAND(yaffs_Device * dev, + int chunkInNAND, + __u8 * data, + yaffs_ExtendedTags * eTags) +{ + + yaffs_Spare spare; + yaffs_Tags tags; + yaffs_ECCResult eccResult = YAFFS_ECC_RESULT_UNKNOWN; + + static yaffs_Spare spareFF; + static int init = 0; + + if (!init) { + memset(&spareFF, 0xFF, sizeof(spareFF)); + init = 1; + } + + if (yaffs_ReadChunkFromNAND + (dev, chunkInNAND, data, &spare, &eccResult, 1)) { + /* eTags may be NULL */ + if (eTags) { + + int deleted = + (yaffs_CountBits(spare.pageStatus) < 7) ? 1 : 0; + + eTags->chunkDeleted = deleted; + eTags->eccResult = eccResult; + eTags->blockBad = 0; /* We're reading it */ + /* therefore it is not a bad block */ + eTags->chunkUsed = + (memcmp(&spareFF, &spare, sizeof(spareFF)) != + 0) ? 1 : 0; + + if (eTags->chunkUsed) { + yaffs_GetTagsFromSpare(dev, &spare, &tags); + + eTags->objectId = tags.objectId; + eTags->chunkId = tags.chunkId; + eTags->byteCount = tags.byteCountLSB; + + if(dev->nDataBytesPerChunk >= 1024) + eTags->byteCount |= (((unsigned) tags.byteCountMSB) << 10); + + eTags->serialNumber = tags.serialNumber; + } + } + + return YAFFS_OK; + } else { + return YAFFS_FAIL; + } +} + +int yaffs_TagsCompatabilityMarkNANDBlockBad(struct yaffs_DeviceStruct *dev, + int blockInNAND) +{ + + yaffs_Spare spare; + + memset(&spare, 0xff, sizeof(yaffs_Spare)); + + spare.blockStatus = 'Y'; + + yaffs_WriteChunkToNAND(dev, blockInNAND * dev->nChunksPerBlock, NULL, + &spare); + yaffs_WriteChunkToNAND(dev, blockInNAND * dev->nChunksPerBlock + 1, + NULL, &spare); + + return YAFFS_OK; + +} + +int yaffs_TagsCompatabilityQueryNANDBlock(struct yaffs_DeviceStruct *dev, + int blockNo, + yaffs_BlockState *state, + __u32 *sequenceNumber) +{ + + yaffs_Spare spare0, spare1; + static yaffs_Spare spareFF; + static int init; + yaffs_ECCResult dummy; + + if (!init) { + memset(&spareFF, 0xFF, sizeof(spareFF)); + init = 1; + } + + *sequenceNumber = 0; + + yaffs_ReadChunkFromNAND(dev, blockNo * dev->nChunksPerBlock, NULL, + &spare0, &dummy, 1); + yaffs_ReadChunkFromNAND(dev, blockNo * dev->nChunksPerBlock + 1, NULL, + &spare1, &dummy, 1); + + if (yaffs_CountBits(spare0.blockStatus & spare1.blockStatus) < 7) + *state = YAFFS_BLOCK_STATE_DEAD; + else if (memcmp(&spareFF, &spare0, sizeof(spareFF)) == 0) + *state = YAFFS_BLOCK_STATE_EMPTY; + else + *state = YAFFS_BLOCK_STATE_NEEDS_SCANNING; + + return YAFFS_OK; +} diff --git a/fs/yaffs2/yaffs_tagscompat.h b/fs/yaffs2/yaffs_tagscompat.h new file mode 100644 index 0000000..6549398 --- /dev/null +++ b/fs/yaffs2/yaffs_tagscompat.h @@ -0,0 +1,41 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFS_TAGSCOMPAT_H__ +#define __YAFFS_TAGSCOMPAT_H__ + +#include "yaffs_guts.h" +int yaffs_TagsCompatabilityWriteChunkWithTagsToNAND(yaffs_Device * dev, + int chunkInNAND, + const __u8 * data, + const yaffs_ExtendedTags * + tags); +int yaffs_TagsCompatabilityReadChunkWithTagsFromNAND(yaffs_Device * dev, + int chunkInNAND, + __u8 * data, + yaffs_ExtendedTags * + tags); +int yaffs_TagsCompatabilityMarkNANDBlockBad(struct yaffs_DeviceStruct *dev, + int blockNo); +int yaffs_TagsCompatabilityQueryNANDBlock(struct yaffs_DeviceStruct *dev, + int blockNo, + yaffs_BlockState *state, + __u32 *sequenceNumber); + +void yaffs_CalcTagsECC(yaffs_Tags * tags); +int yaffs_CheckECCOnTags(yaffs_Tags * tags); +int yaffs_CountBits(__u8 byte); + +#endif diff --git a/fs/yaffs2/yaffs_tagsvalidity.c b/fs/yaffs2/yaffs_tagsvalidity.c new file mode 100644 index 0000000..9e0bd1c --- /dev/null +++ b/fs/yaffs2/yaffs_tagsvalidity.c @@ -0,0 +1,28 @@ +/* + * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "yaffs_tagsvalidity.h" + +void yaffs_InitialiseTags(yaffs_ExtendedTags * tags) +{ + memset(tags, 0, sizeof(yaffs_ExtendedTags)); + tags->validMarker0 = 0xAAAAAAAA; + tags->validMarker1 = 0x55555555; +} + +int yaffs_ValidateTags(yaffs_ExtendedTags * tags) +{ + return (tags->validMarker0 == 0xAAAAAAAA && + tags->validMarker1 == 0x55555555); + +} diff --git a/fs/yaffs2/yaffs_tagsvalidity.h b/fs/yaffs2/yaffs_tagsvalidity.h new file mode 100644 index 0000000..2fd0c24 --- /dev/null +++ b/fs/yaffs2/yaffs_tagsvalidity.h @@ -0,0 +1,24 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + + +#ifndef __YAFFS_TAGS_VALIDITY_H__ +#define __YAFFS_TAGS_VALIDITY_H__ + +#include "yaffs_guts.h" + +void yaffs_InitialiseTags(yaffs_ExtendedTags * tags); +int yaffs_ValidateTags(yaffs_ExtendedTags * tags); +#endif diff --git a/fs/yaffs2/yaffsinterface.h b/fs/yaffs2/yaffsinterface.h new file mode 100644 index 0000000..810837a --- /dev/null +++ b/fs/yaffs2/yaffsinterface.h @@ -0,0 +1,21 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + +#ifndef __YAFFSINTERFACE_H__ +#define __YAFFSINTERFACE_H__ + +int yaffs_Initialise(unsigned nBlocks); + +#endif diff --git a/fs/yaffs2/yportenv.h b/fs/yaffs2/yportenv.h new file mode 100644 index 0000000..bfda36e --- /dev/null +++ b/fs/yaffs2/yportenv.h @@ -0,0 +1,202 @@ +/* + * YAFFS: Yet another Flash File System . A NAND-flash specific file system. + * + * Copyright (C) 2002-2007 Aleph One Ltd. + * for Toby Churchill Ltd and Brightstar Engineering + * + * Created by Charles Manning + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + * + * Note: Only YAFFS headers are LGPL, YAFFS C code is covered by GPL. + */ + + +#ifndef __YPORTENV_H__ +#define __YPORTENV_H__ + +/* + * Define the MTD version in terms of Linux Kernel versions + * This allows yaffs to be used independantly of the kernel + * as well as with it. + */ + +#define MTD_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) + +#if defined CONFIG_YAFFS_WINCE + +#include "ywinceenv.h" + +#elif defined __KERNEL__ + +#include "moduleconfig.h" + +/* Linux kernel */ + +#include +#define MTD_VERSION_CODE LINUX_VERSION_CODE + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)) +#include +#endif +#include +#include +#include +#include +#include +#include + +#define YCHAR char +#define YUCHAR unsigned char +#define _Y(x) x +#define yaffs_strcat(a,b) strcat(a,b) +#define yaffs_strcpy(a,b) strcpy(a,b) +#define yaffs_strncpy(a,b,c) strncpy(a,b,c) +#define yaffs_strncmp(a,b,c) strncmp(a,b,c) +#define yaffs_strlen(s) strlen(s) +#define yaffs_sprintf sprintf +#define yaffs_toupper(a) toupper(a) + +#define Y_INLINE inline + +#define YAFFS_LOSTNFOUND_NAME "lost+found" +#define YAFFS_LOSTNFOUND_PREFIX "obj" + +/* #define YPRINTF(x) printk x */ +#define YMALLOC(x) kmalloc(x,GFP_KERNEL) +#define YFREE(x) kfree(x) +#define YMALLOC_ALT(x) vmalloc(x) +#define YFREE_ALT(x) vfree(x) +#define YMALLOC_DMA(x) YMALLOC(x) + +// KR - added for use in scan so processes aren't blocked indefinitely. +#define YYIELD() schedule() + +#define YAFFS_ROOT_MODE 0666 +#define YAFFS_LOSTNFOUND_MODE 0666 + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +#define Y_CURRENT_TIME CURRENT_TIME.tv_sec +#define Y_TIME_CONVERT(x) (x).tv_sec +#else +#define Y_CURRENT_TIME CURRENT_TIME +#define Y_TIME_CONVERT(x) (x) +#endif + +#define yaffs_SumCompare(x,y) ((x) == (y)) +#define yaffs_strcmp(a,b) strcmp(a,b) + +#define TENDSTR "\n" +#define TSTR(x) KERN_WARNING x +#define TOUT(p) printk p + +#define yaffs_trace(mask, fmt, args...) \ + do { if ((mask) & (yaffs_traceMask|YAFFS_TRACE_ERROR)) \ + printk(KERN_WARNING "yaffs: " fmt, ## args); \ + } while (0) + +#define compile_time_assertion(assertion) \ + ({ int x = __builtin_choose_expr(assertion, 0, (void)0); (void) x; }) + +#elif defined CONFIG_YAFFS_DIRECT + +#define MTD_VERSION_CODE MTD_VERSION(2,6,22) + +/* Direct interface */ +#include "ydirectenv.h" + +#elif defined CONFIG_YAFFS_UTIL + +/* Stuff for YAFFS utilities */ + +#include "stdlib.h" +#include "stdio.h" +#include "string.h" + +#include "devextras.h" + +#define YMALLOC(x) malloc(x) +#define YFREE(x) free(x) +#define YMALLOC_ALT(x) malloc(x) +#define YFREE_ALT(x) free(x) + +#define YCHAR char +#define YUCHAR unsigned char +#define _Y(x) x +#define yaffs_strcat(a,b) strcat(a,b) +#define yaffs_strcpy(a,b) strcpy(a,b) +#define yaffs_strncpy(a,b,c) strncpy(a,b,c) +#define yaffs_strlen(s) strlen(s) +#define yaffs_sprintf sprintf +#define yaffs_toupper(a) toupper(a) + +#define Y_INLINE inline + +/* #define YINFO(s) YPRINTF(( __FILE__ " %d %s\n",__LINE__,s)) */ +/* #define YALERT(s) YINFO(s) */ + +#define TENDSTR "\n" +#define TSTR(x) x +#define TOUT(p) printf p + +#define YAFFS_LOSTNFOUND_NAME "lost+found" +#define YAFFS_LOSTNFOUND_PREFIX "obj" +/* #define YPRINTF(x) printf x */ + +#define YAFFS_ROOT_MODE 0666 +#define YAFFS_LOSTNFOUND_MODE 0666 + +#define yaffs_SumCompare(x,y) ((x) == (y)) +#define yaffs_strcmp(a,b) strcmp(a,b) + +#else +/* Should have specified a configuration type */ +#error Unknown configuration + +#endif + +/* see yaffs_fs.c */ +extern unsigned int yaffs_traceMask; +extern unsigned int yaffs_wr_attempts; + +/* + * Tracing flags. + * The flags masked in YAFFS_TRACE_ALWAYS are always traced. + */ + +#define YAFFS_TRACE_OS 0x00000002 +#define YAFFS_TRACE_ALLOCATE 0x00000004 +#define YAFFS_TRACE_SCAN 0x00000008 +#define YAFFS_TRACE_BAD_BLOCKS 0x00000010 +#define YAFFS_TRACE_ERASE 0x00000020 +#define YAFFS_TRACE_GC 0x00000040 +#define YAFFS_TRACE_WRITE 0x00000080 +#define YAFFS_TRACE_TRACING 0x00000100 +#define YAFFS_TRACE_DELETION 0x00000200 +#define YAFFS_TRACE_BUFFERS 0x00000400 +#define YAFFS_TRACE_NANDACCESS 0x00000800 +#define YAFFS_TRACE_GC_DETAIL 0x00001000 +#define YAFFS_TRACE_SCAN_DEBUG 0x00002000 +#define YAFFS_TRACE_MTD 0x00004000 +#define YAFFS_TRACE_CHECKPOINT 0x00008000 + +#define YAFFS_TRACE_VERIFY 0x00010000 +#define YAFFS_TRACE_VERIFY_NAND 0x00020000 +#define YAFFS_TRACE_VERIFY_FULL 0x00040000 +#define YAFFS_TRACE_VERIFY_ALL 0x000F0000 + + +#define YAFFS_TRACE_ERROR 0x40000000 +#define YAFFS_TRACE_BUG 0x80000000 +#define YAFFS_TRACE_ALWAYS 0xF0000000 + + +#define T(mask,p) do{ if((mask) & (yaffs_traceMask | YAFFS_TRACE_ALWAYS)) TOUT(p);} while(0) + +#ifndef YBUG +#define YBUG() do {T(YAFFS_TRACE_BUG,(TSTR("==>> yaffs bug: " __FILE__ " %d" TENDSTR),__LINE__));} while(0) +#endif + +#endif diff --git a/include/asm-arm/arch-s3c2410/gpio.h b/include/asm-arm/arch-s3c2410/gpio.h index c1b9a09..6cd4eb6 100644 --- a/include/asm-arm/arch-s3c2410/gpio.h +++ b/include/asm-arm/arch-s3c2410/gpio.h @@ -87,6 +87,7 @@ static inline void gpio_free(unsigned int gpio) static inline int gpio_direction_input(unsigned int gpio) { s3c_gpio_cfgpin(gpio, S3C_GPIO_INPUT); + s3c_gpio_pullup(gpio, 0); // pull-up/down disable return 0; } diff --git a/include/asm-arm/arch-s3c2410/hardware.h b/include/asm-arm/arch-s3c2410/hardware.h index 4c305ba..594c44f 100644 --- a/include/asm-arm/arch-s3c2410/hardware.h +++ b/include/asm-arm/arch-s3c2410/hardware.h @@ -45,7 +45,7 @@ extern unsigned int s5p_gpio_getcfg(unsigned int pin); * >=0 = interrupt number for the pin */ -//extern int s3c_gpio_getirq(unsigned int pin); +extern int s3c_gpio_getirq(unsigned int pin); /* s3c_gpio_irqfilter * @@ -126,7 +126,7 @@ extern unsigned int s3c_gpio_getcfg(unsigned int pin); * >=0 = interrupt number for the pin */ -//extern int s3c_gpio_getirq(unsigned int pin); +extern int s3c_gpio_getirq(unsigned int pin); /* s3c_gpio_irqfilter * diff --git a/include/asm-arm/arch-s3c2410/hsmmc.h b/include/asm-arm/arch-s3c2410/hsmmc.h index 5700206..7abe2c0 100644 --- a/include/asm-arm/arch-s3c2410/hsmmc.h +++ b/include/asm-arm/arch-s3c2410/hsmmc.h @@ -36,8 +36,9 @@ struct s3c_hsmmc_cfg { u8 highspeed; /* ENHIGHSPD bit configuration */ - /* feedback delay control configuration (0: mmc, 1: sd) */ - struct s3c_hsmmc_fd_cfg fd_ctrl[2]; + u32 max_clock; /* MAX clock for this port */ + /* feedback delay control configuration (0: mmc, 1: sd, 2:sdio) */ + struct s3c_hsmmc_fd_cfg fd_ctrl[3]; /* clock source control */ struct s3c_hsmmc_clk_cfg clocks[NUM_OF_HSMMC_CLKSOURCES]; diff --git a/include/asm-arm/arch-s3c2410/regs-s3c6410-clock.h b/include/asm-arm/arch-s3c2410/regs-s3c6410-clock.h new file mode 100644 index 0000000..5c79755 --- /dev/null +++ b/include/asm-arm/arch-s3c2410/regs-s3c6410-clock.h @@ -0,0 +1,262 @@ +/* linux/include/asm-arm/arch-s3c2410/regs-s3c6400-clock.h + * + * Copyright (c) 2003,2004,2005,2006 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * S3C6400 clock register definitions +*/ + +#ifndef __ASM_ARM_REGS_CLOCK +#define __ASM_ARM_REGS_CLOCK "$Id: regs-s3c6410-clock.h,v 1.3 2008/04/10 05:53:03 ihlee215 Exp $" + +#define S3C_CLKREG(x) ((x) + S3C24XX_VA_SYSCON) + +#define S3C_PLLVAL(_m,_p,_s) ((_m) << 16 | ((_p) << 8) | ((_s))) + +#define S3C_APLL_LOCK S3C_CLKREG(0x00) +#define S3C_MPLL_LOCK S3C_CLKREG(0x04) +#define S3C_EPLL_LOCK S3C_CLKREG(0x08) +#define S3C_APLL_CON S3C_CLKREG(0x0C) +#define S3C_MPLL_CON S3C_CLKREG(0x10) +#define S3C_EPLL_CON0 S3C_CLKREG(0x14) +#define S3C_EPLL_CON1 S3C_CLKREG(0x18) +#define S3C_CLK_SRC S3C_CLKREG(0x1C) +#define S3C_CLK_SRC2 S3C_CLKREG(0x10C) +#define S3C_CLK_DIV0 S3C_CLKREG(0x20) +#define S3C_CLK_DIV1 S3C_CLKREG(0x24) +#define S3C_CLK_DIV2 S3C_CLKREG(0x28) +#define S3C_CLK_OUT S3C_CLKREG(0x2C) +#define S3C_HCLK_GATE S3C_CLKREG(0x30) +#define S3C_PCLK_GATE S3C_CLKREG(0x34) +#define S3C_SCLK_GATE S3C_CLKREG(0x38) +#define S3C_AHB_CON0 S3C_CLKREG(0x100) +#define S3C_AHB_CON1 S3C_CLKREG(0x104) +#define S3C_AHB_CON2 S3C_CLKREG(0x108) +#define S3C_SDMA_SEL S3C_CLKREG(0x110) +#define S3C_SW_RST S3C_CLKREG(0x114) +#define S3C_SYS_ID S3C_CLKREG(0x118) +#define S3C_MEM_SYS_CFG S3C_CLKREG(0x120) +#define S3C_QOS_OVERRIDE0 S3C_CLKREG(0x124) +#define S3C_QOS_OVERRIDE1 S3C_CLKREG(0x128) +#define S3C_MEM_CFG_STAT S3C_CLKREG(0x12C) +#define S3C_PWR_CFG S3C_CLKREG(0x804) +#define S3C_EINT_MASK S3C_CLKREG(0x808) +#define S3C_NORMAL_CFG S3C_CLKREG(0x810) +#define S3C_STOP_CFG S3C_CLKREG(0x814) +#define S3C_SLEEP_CFG S3C_CLKREG(0x818) +#define S3C_OSC_FREQ S3C_CLKREG(0x820) +#define S3C_OSC_STABLE S3C_CLKREG(0x824) +#define S3C_PWR_STABLE S3C_CLKREG(0x828) +#define S3C_FPC_STABLE S3C_CLKREG(0x82C) +#define S3C_MTC_STABLE S3C_CLKREG(0x830) +#define S3C_OTHERS S3C_CLKREG(0x900) +#define S3C_RST_STAT S3C_CLKREG(0x904) +#define S3C_WAKEUP_STAT S3C_CLKREG(0x908) +#define S3C_BLK_PWR_STAT S3C_CLKREG(0x90C) +#define S3C_INFORM0 S3C_CLKREG(0xA00) +#define S3C_INFORM1 S3C_CLKREG(0xA04) +#define S3C_INFORM2 S3C_CLKREG(0xA08) +#define S3C_INFORM3 S3C_CLKREG(0xA0C) +#define S3C_INFORM4 S3C_CLKREG(0xA10) +#define S3C_INFORM5 S3C_CLKREG(0xA14) +#define S3C_INFORM6 S3C_CLKREG(0xA18) +#define S3C_INFORM7 S3C_CLKREG(0xA1C) + +/* Block power status register bit field */ +#define S3C_BLK_ETM (1<<6) +#define S3C_BLK_S (1<<5) +#define S3C_BLK_F (1<<4) +#define S3C_BLK_P (1<<3) +#define S3C_BLK_I (1<<2) +#define S3C_BLK_V (1<<1) +#define S3C_BLK_TOP (1<<0) + +/* Power gating registers */ +#define S3C_PWRGATE_IROM (1<<30) +#define S3C_PWRGATE_DOMAIN_ETM (1<<16) +#define S3C_PWRGATE_DOMAIN_S (1<<15) +#define S3C_PWRGATE_DOMAIN_F (1<<14) +#define S3C_PWRGATE_DOMAIN_P (1<<13) +#define S3C_PWRGATE_DOMAIN_I (1<<12) +#define S3C_PWRGATE_DOMAIN_V (1<<9) + +/* MTC stable registers */ +#define S3C_STABLE_DOMAIN_ETM (0xF<<24) +#define S3C_STABLE_DOMAIN_S (0xF<<20) +#define S3C_STABLE_DOMAIN_F (0xF<<16) +#define S3C_STABLE_DOMAIN_P (0xF<<12) +#define S3C_STABLE_DOMAIN_I (0xF<<8) +#define S3C_STABLE_DOMAIN_V (0xF<<4) +#define S3C_STABLE_DOMAIN_TOP (0xF<<0) + +/* HCLK GATE Registers */ +#define S3C_CLKCON_HCLK_3DSE (1<<31) +#define S3C_CLKCON_HCLK_UHOST (1<<29) +#define S3C_CLKCON_HCLK_SECUR (1<<28) +#define S3C_CLKCON_HCLK_SDMA1 (1<<27) +#define S3C_CLKCON_HCLK_SDMA0 (1<<26) +#define S3C_CLKCON_HCLK_IROM (1<<25) +#define S3C_CLKCON_HCLK_DDR1 (1<<24) +#define S3C_CLKCON_HCLK_DDR0 (1<<23) +#define S3C_CLKCON_HCLK_MEM1 (1<<22) +#define S3C_CLKCON_HCLK_MEM0 (1<<21) +#define S3C_CLKCON_HCLK_USB (1<<20) +#define S3C_CLKCON_HCLK_HSMMC2 (1<<19) +#define S3C_CLKCON_HCLK_HSMMC1 (1<<18) +#define S3C_CLKCON_HCLK_HSMMC0 (1<<17) +#define S3C_CLKCON_HCLK_MDP (1<<16) +#define S3C_CLKCON_HCLK_DHOST (1<<15) +#define S3C_CLKCON_HCLK_IHOST (1<<14) +#define S3C_CLKCON_HCLK_DMA1 (1<<13) +#define S3C_CLKCON_HCLK_DMA0 (1<<12) +#define S3C_CLKCON_HCLK_JPEG (1<<11) +#define S3C_CLKCON_HCLK_CAMIF (1<<10) +#define S3C_CLKCON_HCLK_SCALER (1<<9) +#define S3C_CLKCON_HCLK_2D (1<<8) +#define S3C_CLKCON_HCLK_TV (1<<7) +#define S3C_CLKCON_HCLK_POST0 (1<<5) +#define S3C_CLKCON_HCLK_ROT (1<<4) +#define S3C_CLKCON_HCLK_LCD (1<<3) +#define S3C_CLKCON_HCLK_TZIC (1<<2) +#define S3C_CLKCON_HCLK_INTC (1<<1) +#define S3C_CLKCON_HCLK_MFC (1<<0) + +/* PCLK GATE Registers */ +#define S3C_CLKCON_PCLK_IIC1 (1<<27) +#define S3C_CLKCON_PCLK_IIS2 (1<<26) +#define S3C_CLKCON_PCLK_SKEY (1<<24) +#define S3C_CLKCON_PCLK_CHIPID (1<<23) +#define S3C_CLKCON_PCLK_SPI1 (1<<22) +#define S3C_CLKCON_PCLK_SPI0 (1<<21) +#define S3C_CLKCON_PCLK_HSIRX (1<<20) +#define S3C_CLKCON_PCLK_HSITX (1<<19) +#define S3C_CLKCON_PCLK_GPIO (1<<18) +#define S3C_CLKCON_PCLK_IIC (1<<17) +#define S3C_CLKCON_PCLK_IIS1 (1<<16) +#define S3C_CLKCON_PCLK_IIS0 (1<<15) +#define S3C_CLKCON_PCLK_AC97 (1<<14) +#define S3C_CLKCON_PCLK_TZPC (1<<13) +#define S3C_CLKCON_PCLK_TSADC (1<<12) +#define S3C_CLKCON_PCLK_KEYPAD (1<<11) +#define S3C_CLKCON_PCLK_IRDA (1<<10) +#define S3C_CLKCON_PCLK_PCM1 (1<<9) +#define S3C_CLKCON_PCLK_PCM0 (1<<8) +#define S3C_CLKCON_PCLK_PWM (1<<7) +#define S3C_CLKCON_PCLK_RTC (1<<6) +#define S3C_CLKCON_PCLK_WDT (1<<5) +#define S3C_CLKCON_PCLK_UART3 (1<<4) +#define S3C_CLKCON_PCLK_UART2 (1<<3) +#define S3C_CLKCON_PCLK_UART1 (1<<2) +#define S3C_CLKCON_PCLK_UART0 (1<<1) +#define S3C_CLKCON_PCLK_MFC (1<<0) + +/* SCLK GATE Registers */ +#define S3C_CLKCON_SCLK_UHOST (1<<30) +#define S3C_CLKCON_SCLK_MMC2_48 (1<<29) +#define S3C_CLKCON_SCLK_MMC1_48 (1<<28) +#define S3C_CLKCON_SCLK_MMC0_48 (1<<27) +#define S3C_CLKCON_SCLK_MMC2 (1<<26) +#define S3C_CLKCON_SCLK_MMC1 (1<<25) +#define S3C_CLKCON_SCLK_MMC0 (1<<24) +#define S3C_CLKCON_SCLK_SPI1_48 (1<<23) +#define S3C_CLKCON_SCLK_SPI0_48 (1<<22) +#define S3C_CLKCON_SCLK_SPI1 (1<<21) +#define S3C_CLKCON_SCLK_SPI0 (1<<20) +#define S3C_CLKCON_SCLK_DAC27 (1<<19) +#define S3C_CLKCON_SCLK_TV27 (1<<18) +#define S3C_CLKCON_SCLK_SCALER27 (1<<17) +#define S3C_CLKCON_SCLK_SCALER (1<<16) +#define S3C_CLKCON_SCLK_LCD27 (1<<15) +#define S3C_CLKCON_SCLK_LCD (1<<14) +#define S3C_CLKCON_SCLK_FIMC (1<<13) +#define S3C_CLKCON_SCLK_POST0_27 (1<<12) +#define S3C_CLKCON_SCLK_AUDIO2 (1<<11) +#define S3C_CLKCON_SCLK_POST0 (1<<10) +#define S3C_CLKCON_SCLK_AUDIO1 (1<<9) +#define S3C_CLKCON_SCLK_AUDIO0 (1<<8) +#define S3C_CLKCON_SCLK_SECUR (1<<7) +#define S3C_CLKCON_SCLK_IRDA (1<<6) +#define S3C_CLKCON_SCLK_UART (1<<5) +#define S3C_CLKCON_SCLK_ONENAND (1<<4) +#define S3C_CLKCON_SCLK_MFC (1<<3) +#define S3C_CLKCON_SCLK_CAM (1<<2) +#define S3C_CLKCON_SCLK_JPEG (1<<1) + +/*OTHERS Resgister */ +#define S3C_OTHERS_USB_SIG_MASK (1<<16) + +/*CLK SRC BITS*/ +#define S3C_CLKSRC_APLL_CLKSEL (1<<0) +#define S3C_CLKSRC_MPLL_CLKSEL (1<<1) +#define S3C_CLKSRC_EPLL_CLKSEL (1<<2) +#define S3C_CLKSRC_UHOST_EPLL (1<<5) +#define S3C_CLKSRC_UHOST_MASK (3<<5) +#if 0 +#define S3C_CLKSRC_CAM_CLKSEL_HCLK (1<<9) +#define S3C_CLKSRC_I2SDIV_CLKSRC (1<<10) +#define S3C_CLKSRC_I2SCLK_CLKSEL (1<<11) +#define S3C_CLKSRC_UARTDIV_CLKSRC (1<<12) +#define S3C_CLKSRC_SPIDIV_CLKSRC (1<<13) +#endif + +/*CLKDIV1 Reg bits */ +#define S3C_CLKDIV1_USBDIV2 (1<<20) + +#define S3C_CLKDIV1_HSMMCDIV2_MASK (0xf<<8) +#define S3C_CLKDIV1_HSMMCDIV2_SHIFT (8) +#define S3C_CLKDIV1_HSMMCDIV1_MASK (0xf<<4) +#define S3C_CLKDIV1_HSMMCDIV1_SHIFT (4) +#define S3C_CLKDIV1_HSMMCDIV0_MASK (0xf<<0) +#define S3C_CLKDIV1_HSMMCDIV0_SHIFT (0) +#define S3C_CLKDIV1_HSMMCDIV_MASK (0xfff<<0) +#define S3C_CLKDIV1_HSMMCDIV_SHIFT (0) + +/*EPLL_CON0 Reg bits */ +#define S3C_EPLL_EN (1<<31) +#define S3C_EPLLVAL(_m,_p,_s) ((_m) << 16 | ((_p) << 8) | ((_s))) + + + +#define S3C_CLKDIVN_APLL_MASK (0xF<<0) +#define S3C_CLKDIVN_MPLL_MASK (0x1<<4) +#define S3C_CLKDIVN_HCLK_MASK (0x1<<8) +#define S3C_CLKDIVN_HCLKX2_MASK (0x7<<9) +#define S3C_CLKDIVN_PCLK_MASK (0xF<<12) +#define S3C_CLKDIVN_UHOST_MASK (0xF<<20) + +static inline unsigned int +s3c6400_get_pll(unsigned long pllval, unsigned long baseclk) +{ + unsigned long mdiv, pdiv, sdiv; + + /* To prevent overflow in calculation -JaeCheol Lee */ + baseclk /= 1000; + + mdiv = (pllval & (0x3ff << 16))>>16; + pdiv = (pllval & (0x3f << 8))>>8; + sdiv = (pllval & (0x03 << 0))>>0; + return (baseclk * (mdiv)) / ((pdiv) << sdiv)*1000; +} + +static inline unsigned int +s3c6400_get_epll(unsigned long baseclk) +{ + unsigned long pllval, mdiv, pdiv, sdiv, kdiv; + + /* To prevent overflow in calculation -JaeCheol Lee */ + baseclk /= 1000; + + pllval = readl(S3C_EPLL_CON0); + mdiv = (pllval & (0x3ff << 16))>>16; + pdiv = (pllval & (0x3f << 8))>>8; + sdiv = (pllval & (0x03 << 0))>>0; + kdiv = readl(S3C_EPLL_CON1) & (0xffff); + + return (baseclk * (mdiv+kdiv/65536) / (pdiv << sdiv))*1000; +} + +#endif /* __ASM_ARM_REGS_CLOCK */ diff --git a/include/asm-arm/arch-s3c2410/reserved_mem.h b/include/asm-arm/arch-s3c2410/reserved_mem.h index 4fa5611..ba921a4 100644 --- a/include/asm-arm/arch-s3c2410/reserved_mem.h +++ b/include/asm-arm/arch-s3c2410/reserved_mem.h @@ -17,7 +17,7 @@ //#define CONFIG_RESERVED_MEM_JPEG //#define CONFIG_RESERVED_MEM_JPEG_POST //#define CONFIG_RESERVED_MEM_MFC -//#define CONFIG_RESERVED_MEM_MFC_POST +#define CONFIG_RESERVED_MEM_MFC_POST //#define CONFIG_RESERVED_MEM_JPEG_MFC_POST //#define CONFIG_RESERVED_MEM_JPEG_CAMERA //#define CONFIG_RESERVED_MEM_JPEG_POST_CAMERA diff --git a/include/asm-arm/arch-s3c2410/system.h b/include/asm-arm/arch-s3c2410/system.h index 91a0d0c..f1c4430 100644 --- a/include/asm-arm/arch-s3c2410/system.h +++ b/include/asm-arm/arch-s3c2410/system.h @@ -89,6 +89,7 @@ arch_reset(char mode) } #else #define S3C6400_SW_RESET_OFF 0x114 +extern int s3c2410wdt_reboot(void); static void arch_reset(char mode) { @@ -101,7 +102,7 @@ arch_reset(char mode) printk("arch_reset: attempting watchdog reset\n"); - __raw_writel(0x6400, S3C24XX_VA_SYSCON + S3C6400_SW_RESET_OFF); + s3c2410wdt_reboot(); /* wait for reset to assert... */ mdelay(5000); diff --git a/include/asm-arm/plat-s3c24xx/devs.h b/include/asm-arm/plat-s3c24xx/devs.h index 5dc27eb..899cc73 100644 --- a/include/asm-arm/plat-s3c24xx/devs.h +++ b/include/asm-arm/plat-s3c24xx/devs.h @@ -75,4 +75,6 @@ extern struct platform_device s3c_device_smc911x; #ifdef CONFIG_PLAT_S3C64XX extern struct platform_device s3c_device_usb_otghcd; +extern struct platform_device s3c_gpio; #endif + diff --git a/include/asm-arm/plat-s3c64xx/hhtech_gpio.h b/include/asm-arm/plat-s3c64xx/hhtech_gpio.h new file mode 100644 index 0000000..8637e5e --- /dev/null +++ b/include/asm-arm/plat-s3c64xx/hhtech_gpio.h @@ -0,0 +1,64 @@ +/**************************************************************** + * $ID: hhtech_gpio.h 三, 18 2月 2009 10:16:56 +0800 wk $ * + * * + * Description: * + * * + * Maintainer: wk@hhcn.com * + * * + * CopyRight (c) 2009 HHTech * + * www.hhcn.com, www.hhcn.org * + * All rights reserved. * + * * + * This file is free software; * + * you are free to modify and/or redistribute it * + * under the terms of the GNU General Public Licence (GPL). * + * * + * Last modified: Fri, 24 Apr 2009 09:25:41 +0800 by root # + ****************************************************************/ + + +/*====================================================================== + * GPIO + */ + +//SD +#define GPIO_SD_WP S3C_GPK0 /* GPK0 ,SD write protect detect,*/ + +//USB +#define GPIO_USB_EN S3C_GPL0 /* GPL0 USB Improving voltage Enable, 1:open 0:off */ +#define GPIO_USB_HOSTPWR_EN S3C_GPL1 /* GPL1 1:5V on 0:5Voff */ +#define GPIO_USB_HOST_STATUS S3C_GPL10 /* GPL10 USB protect status ,0:error*/ +#define GPIO_USB_OTG_STATUS S3C_GPL11 /* GPL11 USB otg Over-current protection status ,0:error*/ +#define GPIO_USB_OTGDRV_EN S3C_GPL8 /* GPL0 USB otg drv Enable, 1:open 0:off */ + +//Headphone Sperker +#define GPIO_HEADPHONE_S S3C_GPL12 /* GPL12 headphone audio detect,0:insert */ +#define GPIO_SPEAKER_EN S3C_GPK12 /* GPK12 Speaker 0:off 1:open */ + +//Backlight +#define GPIO_LCD_BLIGHT_EN S3C_GPM3 /* GPM3,MP1530 LCD backlight,1:enable 0:off */ +#define GPIO_LCD_BLIGHT_S S3C_GPM4 /* GPM4,MP1530 status */ + +//Charging +#define GPIO_DC_DETE S3C_GPL13 /* GPL13 DC insert Detect */ +#define GPIO_CHARG_S1 S3C_GPK4 /* GPK4 ,Charging status 1,*/ +#define GPIO_CHARG_S2 S3C_GPK5 /* GPK5 ,Charging status 2,*/ +#define GPIO_CHARGER_EN S3C_GPK6 /* GPK6 DC 0:200ma 1:860ma */ + +// System Power +#define GPIO_PWR_EN S3C_GPK15 /* GPK15 System power control 0:off 1:open */ +#define GPIO_PWR_HOLD S3C_GPL14 /* GPL14 System power hold over 5 second time,pull up GPK15 */ + +//Vidoe amplifier +#define GPIO_VIDEOAMP_EN S3C_GPK13 /* GPK13,Vidoe amplifier output control,0:off*/ + +#define GPIO_WIFI_EN S3C_GPK1 /* Wifi switch*/ +#define GPIO_WIFI_RESET S3C_GPK2 /* Wifi reset*/ + +#if defined(CONFIG_LCD_4) +#define GPIO_LED1_EN S3C_GPN8 +#define GPIO_LED2_EN S3C_GPN9 +#else +#define GPIO_LED1_EN S3C_GPN9 +#define GPIO_LED2_EN S3C_GPN8 +#endif diff --git a/include/asm-arm/vfp.h b/include/asm-arm/vfp.h index bd6be9d..f4ab34f 100644 --- a/include/asm-arm/vfp.h +++ b/include/asm-arm/vfp.h @@ -1,5 +1,5 @@ /* - * linux/include/asm-arm/vfp.h + * arch/arm/include/asm/vfp.h * * VFP register definitions. * First, the standard VFP set. @@ -7,7 +7,11 @@ #define FPSID cr0 #define FPSCR cr1 +#define MVFR1 cr6 +#define MVFR0 cr7 #define FPEXC cr8 +#define FPINST cr9 +#define FPINST2 cr10 /* FPSID bits */ #define FPSID_IMPLEMENTER_BIT (24) @@ -28,6 +32,19 @@ /* FPEXC bits */ #define FPEXC_EX (1 << 31) #define FPEXC_EN (1 << 30) +#define FPEXC_DEX (1 << 29) +#define FPEXC_FP2V (1 << 28) +#define FPEXC_VV (1 << 27) +#define FPEXC_TFV (1 << 26) +#define FPEXC_LENGTH_BIT (8) +#define FPEXC_LENGTH_MASK (7 << FPEXC_LENGTH_BIT) +#define FPEXC_IDF (1 << 7) +#define FPEXC_IXF (1 << 4) +#define FPEXC_UFF (1 << 3) +#define FPEXC_OFF (1 << 2) +#define FPEXC_DZF (1 << 1) +#define FPEXC_IOF (1 << 0) +#define FPEXC_TRAP_MASK (FPEXC_IDF|FPEXC_IXF|FPEXC_UFF|FPEXC_OFF|FPEXC_DZF|FPEXC_IOF) /* FPSCR bits */ #define FPSCR_DEFAULT_NAN (1<<25) @@ -55,20 +72,9 @@ #define FPSCR_IXC (1<<4) #define FPSCR_IDC (1<<7) -/* - * VFP9-S specific. - */ -#define FPINST cr9 -#define FPINST2 cr10 - -/* FPEXC bits */ -#define FPEXC_FPV2 (1<<28) -#define FPEXC_LENGTH_BIT (8) -#define FPEXC_LENGTH_MASK (7 << FPEXC_LENGTH_BIT) -#define FPEXC_INV (1 << 7) -#define FPEXC_UFC (1 << 3) -#define FPEXC_OFC (1 << 2) -#define FPEXC_IOC (1 << 0) +/* MVFR0 bits */ +#define MVFR0_A_SIMD_BIT (0) +#define MVFR0_A_SIMD_MASK (0xf << MVFR0_A_SIMD_BIT) /* Bit patterns for decoding the packaged operation descriptors */ #define VFPOPDESC_LENGTH_BIT (9) diff --git a/include/asm-arm/vfpmacros.h b/include/asm-arm/vfpmacros.h index 27fe028..422f3cc 100644 --- a/include/asm-arm/vfpmacros.h +++ b/include/asm-arm/vfpmacros.h @@ -1,5 +1,5 @@ /* - * linux/include/asm-arm/vfpmacros.h + * arch/arm/include/asm/vfpmacros.h * * Assembler-only file containing VFP macros and register definitions. */ @@ -15,19 +15,33 @@ .endm @ read all the working registers back into the VFP - .macro VFPFLDMIA, base + .macro VFPFLDMIA, base, tmp #if __LINUX_ARM_ARCH__ < 6 LDC p11, cr0, [\base],#33*4 @ FLDMIAX \base!, {d0-d15} #else LDC p11, cr0, [\base],#32*4 @ FLDMIAD \base!, {d0-d15} #endif +#ifdef CONFIG_VFPv3 + VFPFMRX \tmp, MVFR0 @ Media and VFP Feature Register 0 + and \tmp, \tmp, #MVFR0_A_SIMD_MASK @ A_SIMD field + cmp \tmp, #2 @ 32 x 64bit registers? + ldceql p11, cr0, [\base],#32*4 @ FLDMIAD \base!, {d16-d31} + addne \base, \base, #32*4 @ step over unused register space +#endif .endm @ write all the working registers out of the VFP - .macro VFPFSTMIA, base + .macro VFPFSTMIA, base, tmp #if __LINUX_ARM_ARCH__ < 6 STC p11, cr0, [\base],#33*4 @ FSTMIAX \base!, {d0-d15} #else STC p11, cr0, [\base],#32*4 @ FSTMIAD \base!, {d0-d15} #endif +#ifdef CONFIG_VFPv3 + VFPFMRX \tmp, MVFR0 @ Media and VFP Feature Register 0 + and \tmp, \tmp, #MVFR0_A_SIMD_MASK @ A_SIMD field + cmp \tmp, #2 @ 32 x 64bit registers? + stceql p11, cr0, [\base],#32*4 @ FSTMIAD \base!, {d16-d31} + addne \base, \base, #32*4 @ step over unused register space +#endif .endm diff --git a/include/linux/aufs_type.h b/include/linux/aufs_type.h new file mode 100644 index 0000000..cee8d68 --- /dev/null +++ b/include/linux/aufs_type.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2005-2009 Junjiro Okajima + * + * This program, aufs is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* $Id: aufs_type.h,v 1.134 2009/01/26 06:24:45 sfjro Exp $ */ + +#include + +#ifndef __AUFS_TYPE_H__ +#define __AUFS_TYPE_H__ + +#define AUFS_VERSION "20090126" + +/* move this to linux-2.6.19/include/magic.h */ +#define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's') + +/* ---------------------------------------------------------------------- */ + +#ifdef CONFIG_AUFS_BRANCH_MAX_127 +/* some environments treat 'char' as 'unsigned char' by default */ +typedef signed char aufs_bindex_t; +#define AUFS_BRANCH_MAX 127 +#else +typedef short aufs_bindex_t; +#ifdef CONFIG_AUFS_BRANCH_MAX_511 +#define AUFS_BRANCH_MAX 511 +#elif defined(CONFIG_AUFS_BRANCH_MAX_1023) +#define AUFS_BRANCH_MAX 1023 +#elif defined(CONFIG_AUFS_BRANCH_MAX_32767) +#define AUFS_BRANCH_MAX 32767 +#else +#error unknown CONFIG_AUFS_BRANCH_MAX value +#endif +#endif + +#define AUFS_NAME "aufs" +#define AUFS_FSTYPE AUFS_NAME + +#define AUFS_ROOT_INO 2 +#define AUFS_FIRST_INO 11 + +#define AUFS_WH_PFX ".wh." +#define AUFS_WH_PFX_LEN ((int)sizeof(AUFS_WH_PFX) - 1) +#define AUFS_XINO_FNAME "." AUFS_NAME ".xino" +#define AUFS_XINO_DEFPATH "/tmp/" AUFS_XINO_FNAME +#define AUFS_XINO_TRUNC_INIT 64 /* blocks */ +#define AUFS_XINO_TRUNC_STEP 4 /* blocks */ +#define AUFS_DIRWH_DEF 3 +#define AUFS_RDCACHE_DEF 10 /* seconds */ +#define AUFS_WKQ_NAME AUFS_NAME "d" +#define AUFS_NWKQ_DEF 4 +#define AUFS_MFS_SECOND_DEF 30 /* seconds */ +#define AUFS_PLINK_WARN 100 /* number of plinks */ + +#ifdef CONFIG_AUFS_COMPAT +#define AUFS_DIROPQ_NAME "__dir_opaque" +#else +#define AUFS_DIROPQ_NAME AUFS_WH_PFX ".opq" /* whiteouted doubly */ +#endif +#define AUFS_WH_DIROPQ AUFS_WH_PFX AUFS_DIROPQ_NAME + +#define AUFS_BASE_NAME AUFS_WH_PFX AUFS_NAME +#define AUFS_PLINKDIR_NAME AUFS_WH_PFX "plnk" +#define AUFS_TMPDIR_NAME AUFS_WH_PFX ".tmp" + +/* doubly whiteouted */ +#define AUFS_WH_BASE AUFS_WH_PFX AUFS_BASE_NAME +#define AUFS_WH_PLINKDIR AUFS_WH_PFX AUFS_PLINKDIR_NAME +#define AUFS_WH_TMPDIR AUFS_WH_PFX AUFS_TMPDIR_NAME + +/* ---------------------------------------------------------------------- */ + +/* ioctl */ +#if 0 /* reserved for future use */ +enum { + AuCtlErr, + AuCtlErr_Last +}; +enum { + AuCtl_DIROPQ_GET, AuCtl_DIROPQ_SET, + AuCtl_MOVE, + AuCtl_MVDOWN, + + /* unimplmented */ + AuCtl_REFRESH, AuCtl_REFRESHV, + AuCtl_FLUSH_PLINK, + AuCtl_CPUP, + AuCtl_CPDOWN +}; + +struct aufs_ctl { + int err; + aufs_bindex_t bsrc, bdst; + char *path; +}; + +#define AuCtlType 'A' +#define AUFS_CTL_DIROPQ_GET _IO(AuCtlType, AuCtl_DIROPQ_GET) +#define AUFS_CTL_DIROPQ_SET _IOW(AuCtlType, AuCtl_DIROPQ_SET, aufs_bindex_t) +#define AUFS_CTL_MOVE \ + _IOW(AuCtlType, AuCtl_MVDOWN, aufs_bindex_t) +#define AUFS_CTL_MVDOWN \ + _IOWR(AuCtlType, AuCtl_MVDOWN, struct aufs_ctl) + +#define AUFS_CTL_REFRESH _IO(AuCtlType, AuCtl_REFRESH) +#define AUFS_CTL_REFRESHV _IO(AuCtlType, AuCtl_REFRESHV) +#define AUFS_CTL_FLUSH_PLINK _IOR(AuCtlType, AuCtl_FLUSH_PLINK) +#define AUFS_CTL_CPUP _IOWR(AuCtlType, AuCtl_CPUP, struct aufs_ctl) +#define AUFS_CTL_CPDOWN \ + _IOWR(AuCtlType, AuCtl_CPDOWN, struct aufs_ctl_cp) +#endif + +#endif /* __AUFS_TYPE_H__ */ diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h index c6d3a9d..1289fa7 100644 --- a/include/linux/gpio_keys.h +++ b/include/linux/gpio_keys.h @@ -9,11 +9,13 @@ struct gpio_keys_button { char *desc; int type; /* input event type (EV_KEY, EV_SW) */ int wakeup; /* configure the button as a wake-up source */ + int debounce_interval; /* debounce ticks interval in msecs */ }; struct gpio_keys_platform_data { struct gpio_keys_button *buttons; int nbuttons; + unsigned int rep:1; /* enable input subsystem auto repeat */ }; #endif diff --git a/kernel/power/main.c b/kernel/power/main.c index f71c950..8237545 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include "power.h" @@ -217,7 +220,6 @@ static inline int valid_state(suspend_state_t state) return 1; } - /** * enter_state - Do common work of entering low-power state. * @state: pm_state structure for state we're entering. @@ -231,6 +233,8 @@ static inline int valid_state(suspend_state_t state) static int enter_state(suspend_state_t state) { int error; + int usb_otgdrv_en = 0; + int dc_status; if (!valid_state(state)) return -ENODEV; @@ -238,6 +242,36 @@ static int enter_state(suspend_state_t state) if (!mutex_trylock(&pm_mutex)) return -EBUSY; +#if defined (CONFIG_LCD_4) + dc_status = gpio_get_value(GPIO_DC_DETE) ? 1 : 0; +#else + dc_status = gpio_get_value(GPIO_DC_DETE) ? 0 : 1; +#endif + + if(0 == dc_status) { + extern int current_battery; + extern unsigned long rtc_wakeup_time; + + if(current_battery > 905) // battery volume > 3.8 v + rtc_wakeup_time = 2 * 60 * 60; // 2 hours + else if(current_battery > 881) // battery volume > 3.7 v + rtc_wakeup_time = 60 * 60; // 1 hours + else if(current_battery > 858) // battery volume > 3.6 v + rtc_wakeup_time = 30 * 60; // 30 mins + else if(current_battery > 846) // battery volume > 3.55 v + rtc_wakeup_time = 15 * 60; // 15 mins + else if(current_battery > 834) // battery volume > 3.5 v + rtc_wakeup_time = 5 * 60; // 5 mins + else // battery volume <= 3.5 v + rtc_wakeup_time = 60; // 1 mins + printk("--->enter_state: rtc_wakeup_time = %ld\n", rtc_wakeup_time); + } + + if(gpio_get_value(S3C_GPL8)) { + usb_otgdrv_en = 1; + gpio_set_value(S3C_GPL8, 0); // close the USB_OTGDRV_EN + } + printk("Syncing filesystems ... "); sys_sync(); printk("done.\n"); @@ -249,6 +283,8 @@ static int enter_state(suspend_state_t state) pr_debug("PM: Entering %s sleep\n", pm_states[state]); error = suspend_devices_and_enter(state); + if(usb_otgdrv_en) + gpio_set_value(S3C_GPL8, 1); // open the USB_OTGDRV_EN pr_debug("PM: Finishing wakeup.\n"); suspend_finish(); Unlock: diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7be7510..b5193d8 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -17,7 +17,11 @@ config SND_SOC_WM8750 config SND_SOC_WM8753 tristate depends on SND_SOC - + +config SND_SOC_WM8987 + tristate + depends on SND_SOC + config SND_SOC_WM8990 tristate depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index ca67a02..7559d55 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -14,6 +14,7 @@ snd-soc-wm8974-objs := wm8974.o snd-soc-wm8976-objs := wm8976.o snd-soc-wm8978-objs := wm8978.o snd-soc-wm8980-objs := wm8980.o +snd-soc-wm8987-objs := wm8987.o snd-soc-wm8990-objs := wm8990.o snd-soc-wm8991-objs := wm8991.o snd-soc-wm9713-objs := wm9713.o @@ -35,6 +36,7 @@ obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o obj-$(CONFIG_SND_SOC_WM8976) += snd-soc-wm8976.o obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o obj-$(CONFIG_SND_SOC_WM8980) += snd-soc-wm8980.o +obj-$(CONFIG_SND_SOC_WM8987) += snd-soc-wm8987.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o diff --git a/sound/soc/codecs/wm8987.c b/sound/soc/codecs/wm8987.c new file mode 100644 index 0000000..43028ff --- /dev/null +++ b/sound/soc/codecs/wm8987.c @@ -0,0 +1,1330 @@ +/* + * wm8987.c -- WM8987 ALSA SoC audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on WM8987.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HHBF_FAST_REBOOT +#include +#endif + +#include "wm8987.h" + +#define AUDIO_NAME "WM8987" +#define WM8987_VERSION "v0.12" + +#include +#include +static int volume; +static struct snd_soc_codec *p_codec = NULL; +/* + * Debug + */ + +//#define WM8987_DEBUG 1 + +#ifdef WM8987_DEBUG +#define dbg(format, arg...) \ + printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +/* codec private data */ +#ifndef CONFIG_HHTECH_MINIPMP +struct wm8987_priv { + unsigned int sysclk; +}; +#else +static unsigned wm8987_sysclk; + +void (*hhbf_audio_switch)(int flag) = NULL; +EXPORT_SYMBOL(hhbf_audio_switch); +static unsigned short init_reboot = 0; +#endif//CONFIG_HHTECH_MINIPMP + +/* + * wm8987 register cache + * We can't read the WM8987 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8987_reg[] = { + 0x00b7, 0x0097, 0x0000, 0x0000, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x002a, /* 4 */ + 0x0000, 0x0000, 0x007F, 0x007F, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0080, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00E0, 0x00E0, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0000, 0x0000, 0x0079, /* 40 */ +}; + +/* + * read wm8987 register cache + */ +static inline unsigned int wm8987_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; +#ifndef CONFIG_HHTECH_MINIPMP + if (reg > WM8987_CACHE_REGNUM) +#else// mhfan + if (reg > ARRAY_SIZE(wm8987_reg)) +#endif//CONFIG_HHTECH_MINIPMP + return -1; + return cache[reg]; +} + +/* + * write wm8987 register cache + */ +static inline void wm8987_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; +#ifndef CONFIG_HHTECH_MINIPMP + if (reg > WM8987_CACHE_REGNUM) +#else// mhfan + if (reg > ARRAY_SIZE(wm8987_reg)) +#endif//CONFIG_HHTECH_MINIPMP + return; + cache[reg] = value; +} + +#if 0//def CONFIG_HHTECH_MINIPMP +static int playtvo = 0; +#endif//CONFIG_HHTECH_MINIPMP + +static int wm8987_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + int pin_level = gpio_get_value(S3C_GPL12); + +#if 0 //def CONFIG_SND_SOC_WM8987 + if((10 == reg || 11 == reg) && value == 0x1C2) value = 0x1C4; + if((40 == reg || 41 == reg) && value == 0x1C2) value = 0x1C4; +#endif + +#if 0 /* comment by whg HHTECH */ +#if( !defined(CONFIG_HHBF_I2C_MCU) || (defined(CONFIG_SND_SOC_WM8987))) + if (WM8987_PWR2 == reg) { // XXX: FIXME + u16 tmp, *cache = codec->reg_cache; + if (reg > ARRAY_SIZE(wm8987_reg)) return -EIO; + else tmp = cache[reg]; + #ifdef CONFIG_SND_SOC_WM8987 + if ( (value & 0x100) && !(tmp & 0x100)) value |= 0x198; + if (!(value & 0x080) && (tmp & 0x080)) value &= ~0x198; + if ( (value & 0x010) && !(tmp & 0x010)) value |= 0x198; + if (!(value & 0x008) && (tmp & 0x008)) value &= ~0x198; + if( value == tmp) return 0; + #else + if ( (value & 0x100) && !(tmp & 0x100)) value |= 0x180; + if (!(value & 0x100) && (tmp & 0x100)) value &= ~0x180; + if ( (value & 0x040) && !(tmp & 0x040)) value |= 0x07a; + if (!(value & 0x040) && (tmp & 0x040)) value &= ~0x07a; + if ( (value & ~0x1a) == (tmp & ~0x1a)) return 0; + #endif + value = 0x198; + } +#endif//CONFIG_HHBF_I2C_MCU +#endif /* comment by WangGang */ + + /* data is + * D15..D9 WM8987 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + +////////////////////// HHTECH wk //////////////// + volume = wm8987_read_reg_cache(codec, WM8987_LDAC); + if ((!pin_level)&& (reg==0xa || reg==0xb)){ + data[1] = (data[1] * 95 /100) & 0x00ff; //0x1be:70% 0x1e8:91% 0x1d9:85% 0x1c9:79% + } +////////////////////// HHTECH wk //////////////// +#ifdef WM8987_DEBUG + if (value == wm8987_read_reg_cache(codec, reg)) return 0; + + printk(KERN_INFO AUDIO_NAME ": 0x%02x%02x, R%02d <= 0x%03x\n", + data[0], data[1], reg, value); +#endif//WM8987_DEBUG + + wm8987_write_reg_cache (codec, reg, value); + +#ifdef CONFIG_HHTECH_MINIPMP + if (init_reboot) return 0; +#endif//CONFIG_HHTECH_MINIPMP + + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8987_reset(c) wm8987_write(c, WM8987_RESET, 0) + +/* + * WM8987 Controls + */ +static const char *wm8987_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm8987_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static const char *wm8987_treble[] = {"8kHz", "4kHz"}; +static const char *wm8987_3d_lc[] = {"200Hz", "500Hz"}; +static const char *wm8987_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *wm8987_3d_func[] = {"Capture", "Playback"}; +static const char *wm8987_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8987_ng_type[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static const char *wm8987_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA", + "Differential"}; +static const char *wm8987_pga_sel[] = {"Line 1", "Line 2", "Line 3", + "Differential"}; +static const char *wm8987_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut", + "ROUT1"}; +static const char *wm8987_diff_sel[] = {"Line 1", "Line 2"}; +static const char *wm8987_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static const char *wm8987_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *wm8987_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; + +static const struct soc_enum wm8987_enum[] = { +SOC_ENUM_SINGLE(WM8987_BASS, 7, 2, wm8987_bass), +SOC_ENUM_SINGLE(WM8987_BASS, 6, 2, wm8987_bass_filter), +SOC_ENUM_SINGLE(WM8987_TREBLE, 6, 2, wm8987_treble), +SOC_ENUM_SINGLE(WM8987_3D, 5, 2, wm8987_3d_lc), +SOC_ENUM_SINGLE(WM8987_3D, 6, 2, wm8987_3d_uc), +SOC_ENUM_SINGLE(WM8987_3D, 7, 2, wm8987_3d_func), +SOC_ENUM_SINGLE(WM8987_ALC1, 7, 4, wm8987_alc_func), +SOC_ENUM_SINGLE(WM8987_NGATE, 1, 2, wm8987_ng_type), +SOC_ENUM_SINGLE(WM8987_LOUTM1, 0, 5, wm8987_line_mux), +SOC_ENUM_SINGLE(WM8987_ROUTM1, 0, 5, wm8987_line_mux), +SOC_ENUM_SINGLE(WM8987_LADCIN, 6, 4, wm8987_pga_sel), /* 10 */ +SOC_ENUM_SINGLE(WM8987_RADCIN, 6, 4, wm8987_pga_sel), +SOC_ENUM_SINGLE(WM8987_ADCTL2, 7, 4, wm8987_out3), +SOC_ENUM_SINGLE(WM8987_ADCIN, 8, 2, wm8987_diff_sel), +SOC_ENUM_SINGLE(WM8987_ADCDAC, 5, 4, wm8987_adcpol), +SOC_ENUM_SINGLE(WM8987_ADCDAC, 1, 4, wm8987_deemph), +SOC_ENUM_SINGLE(WM8987_ADCIN, 6, 4, wm8987_mono_mux), /* 16 */ + +}; + +static const struct snd_kcontrol_new wm8987_snd_controls[] = { + +SOC_DOUBLE_R("Capture Volume", WM8987_LINVOL, WM8987_RINVOL, 0, 63, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8987_LINVOL, WM8987_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8987_LINVOL, WM8987_RINVOL, 7, 1, 1), + +#ifndef CONFIG_HHTECH_MINIPMP +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8987_LOUT1V, + WM8987_ROUT1V, 7, 1, 0), +#endif +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8987_LOUT2V, + WM8987_ROUT2V, 7, 1, 0), + +SOC_ENUM("Playback De-emphasis", wm8987_enum[15]), + +SOC_ENUM("Capture Polarity", wm8987_enum[14]), +SOC_SINGLE("Playback 6dB Attenuate", WM8987_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", WM8987_ADCDAC, 8, 1, 0), + +#ifndef CONFIG_HHTECH_MINIPMP +SOC_DOUBLE_R("PCM Volume", WM8987_LDAC, WM8987_RDAC, 0, 255, 0), +#else// mhfan, XXX: +SOC_DOUBLE_R("Master Playback Volume", WM8987_LDAC, WM8987_RDAC, 0, 255, 0), +#endif//CONFIG_HHTECH_MINIPMP + +SOC_ENUM("Bass Boost", wm8987_enum[0]), +SOC_ENUM("Bass Filter", wm8987_enum[1]), +SOC_SINGLE("Bass Volume", WM8987_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8987_TREBLE, 0, 15, 0), +SOC_ENUM("Treble Cut-off", wm8987_enum[2]), + +SOC_SINGLE("3D Switch", WM8987_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", WM8987_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", wm8987_enum[3]), +SOC_ENUM("3D Upper Cut-off", wm8987_enum[4]), +SOC_ENUM("3D Mode", wm8987_enum[5]), + +SOC_SINGLE("ALC Capture Target Volume", WM8987_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8987_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", wm8987_enum[6]), +SOC_SINGLE("ALC Capture ZC Switch", WM8987_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8987_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", WM8987_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", WM8987_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8987_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", wm8987_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", WM8987_NGATE, 0, 1, 0), + +SOC_SINGLE("Left ADC Capture Volume", WM8987_LADC, 0, 255, 0), +SOC_SINGLE("Right ADC Capture Volume", WM8987_RADC, 0, 255, 0), + +SOC_SINGLE("ZC Timeout Switch", WM8987_ADCTL1, 0, 1, 0), +SOC_SINGLE("Playback Invert Switch", WM8987_ADCTL1, 1, 1, 0), + +SOC_SINGLE("Right Speaker Playback Invert Switch", WM8987_ADCTL2, 4, 1, 0), + +/* Unimplemented */ +/* ADCDAC Bit 0 - ADCHPD */ +/* ADCDAC Bit 4 - HPOR */ +/* ADCTL1 Bit 2,3 - DATSEL */ +/* ADCTL1 Bit 4,5 - DMONOMIX */ +/* ADCTL1 Bit 6,7 - VSEL */ +/* ADCTL2 Bit 2 - LRCM */ +/* ADCTL2 Bit 3 - TRI */ +/* ADCTL3 Bit 5 - HPFLREN */ +/* ADCTL3 Bit 6 - VROI */ +/* ADCTL3 Bit 7,8 - ADCLRM */ +/* ADCIN Bit 4 - LDCM */ +/* ADCIN Bit 5 - RDCM */ + +SOC_DOUBLE_R("Mic Boost", WM8987_LADCIN, WM8987_RADCIN, 4, 3, 0), + +SOC_DOUBLE_R("Bypass Left Playback Volume", WM8987_LOUTM1, + WM8987_LOUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Right Playback Volume", WM8987_ROUTM1, + WM8987_ROUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8987_MOUTM1, + WM8987_MOUTM2, 4, 7, 1), + +SOC_SINGLE("Mono Playback ZC Switch", WM8987_MOUTV, 7, 1, 0), + +#ifndef CONFIG_HHTECH_MINIPMP +SOC_DOUBLE_R("Headphone Playback Volume", WM8987_LOUT1V, WM8987_ROUT1V, + 0, 127, 0), +#endif +SOC_DOUBLE_R("Speaker Playback Volume", WM8987_LOUT2V, WM8987_ROUT2V, + 0, 127, 0), + +SOC_SINGLE("Mono Playback Volume", WM8987_MOUTV, 0, 127, 0), + +}; + +/* add non dapm controls */ +static int wm8987_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8987_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8987_snd_controls[i],codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8987_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8987_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8987_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8987_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8987_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8987_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8987_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8987_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8987_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8987_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8987_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8987_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8987_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8987_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8987_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8987_left_line_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8987_right_line_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8987_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8987_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[11]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new wm8987_out3_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[12]); + +/* Differential Mux */ +static const struct snd_kcontrol_new wm8987_diffmux_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[13]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8987_monomux_controls = +SOC_DAPM_ENUM("Route", wm8987_enum[16]); + +static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8987_left_mixer_controls[0], + ARRAY_SIZE(wm8987_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8987_right_mixer_controls[0], + ARRAY_SIZE(wm8987_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8987_PWR2, 2, 0, + &wm8987_mono_mixer_controls[0], + ARRAY_SIZE(wm8987_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8987_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8987_PWR2, 4, 0, NULL, 0), + +// SND_SOC_DAPM_PGA("Right Out 1", WM8987_PWR2, 5, 0, +// wm8987_rout1_ctrls, ARRAY_SIZE(wm8987_rout1_ctrls)), +// SND_SOC_DAPM_PGA("Left Out 1", WM8987_PWR2, 6, 0, +// wm8987_lout1_ctrls, ARRAY_SIZE(wm8987_lout1_ctrls)), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8987_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8987_PWR2, 8, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8987_PWR1, 1, 0), + //SND_SOC_DAPM_MICBIAS("Mic Bias", WM8987_PWR1, 1, 1), //lzcx + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8987_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8987_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8987_PWR1, 5, 0, + &wm8987_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8987_PWR1, 4, 0, + &wm8987_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8987_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8987_right_line_controls), + + SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8987_out3_controls), + SND_SOC_DAPM_PGA("Out 3", WM8987_PWR2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8987_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &wm8987_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8987_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8987_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO"), + SND_SOC_DAPM_OUTPUT("OUT3"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("LINPUT3"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT3"), +}; + +static const char *audio_map[][3] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* out 3 */ + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "ROUT1 + Vol", "ROUT1"}, + {"Out3 Mux", "ROUT1", "Right Mixer"}, + {"Out3 Mux", "MonoOut", "MONO1"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line 1", "LINPUT1"}, + {"Left Line Mux", "Line 2", "LINPUT2"}, + {"Left Line Mux", "Line 3", "LINPUT3"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line 1", "RINPUT1"}, + {"Right Line Mux", "Line 2", "RINPUT2"}, + {"Right Line Mux", "Line 3", "RINPUT3"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line 1", "LINPUT1"}, + {"Left PGA Mux", "Line 2", "LINPUT2"}, + {"Left PGA Mux", "Line 3", "LINPUT3"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line 1", "RINPUT1"}, + {"Right PGA Mux", "Line 2", "RINPUT2"}, + {"Right PGA Mux", "Line 3", "RINPUT3"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line 1", "LINPUT1"}, + {"Differential Mux", "Line 1", "RINPUT1"}, + {"Differential Mux", "Line 2", "LINPUT2"}, + {"Differential Mux", "Line 2", "RINPUT2"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int wm8987_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for(i = 0; i < ARRAY_SIZE(wm8987_dapm_widgets); i++) { + snd_soc_dapm_new_control(codec, &wm8987_dapm_widgets[i]); + } + + /* set up audio path audio_mapnects */ + for(i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + } + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 80182 */ + {11289600, 8018, 1408, 0x16, 0x0}, + {16934400, 8018, 2112, 0x17, 0x0}, + + /* 8.0214k */ + {12000000, 8021, 1496, 0x17, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 12k */ + {12288000, 12000, 1024, 0x8, 0x0}, + {18432000, 12000, 1536, 0x9, 0x0}, + {12000000, 12000, 1000, 0x8, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 24k */ + {12288000, 24000, 512, 0x1c, 0x0}, + {18432000, 24000, 768, 0x1d, 0x0}, + {12000000, 24000, 500, 0x1c, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xc, 0x1}, // mhfan + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + printk(KERN_ERR "wm8987: could not get coeff for mclk %d @ rate %d\n", + mclk, rate); + return -EINVAL; +} + +#if 1 //lzcx +static int wm8987_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + return 0; //lzcx + + //printk("id is %d div is 0x%x setdaiclkdiv\n",div_id,div); + reg = wm8987_read_reg_cache(codec, WM8987_SRATE) & 0x003f; + wm8987_write(codec, WM8987_SRATE, reg | div); +#if 0 + switch (div_id) { + case WM8987_PCMDIV: + reg = wm8987_read_reg_cache(codec, WM8987_CLOCK) & 0x003f; + wm8987_write(codec, WM8987_CLOCK, reg | div); + break; + case WM8987_BCLKDIV: + reg = wm8987_read_reg_cache(codec, WM8987_SRATE2) & 0x01c7; + wm8987_write(codec, WM8987_SRATE2, reg | div); + break; + case WM8987_VXCLKDIV: + reg = wm8987_read_reg_cache(codec, WM8987_SRATE2) & 0x003f; + wm8987_write(codec, WM8987_SRATE2, reg | div); + break; + default: + return -EINVAL; + } +#endif + return 0; +} + +#endif +static int wm8987_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ +#ifndef CONFIG_HHTECH_MINIPMP + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8987_priv *wm8987 = codec->private_data; +#endif//CONFIG_HHTECH_MINIPMP + //printk("freq is %ld \n",freq); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: +#ifndef CONFIG_HHTECH_MINIPMP + wm8987->sysclk = freq; +#else// mhfan + case 24576000: + wm8987_sysclk = freq; +#endif//CONFIG_HHTECH_MINIPMP + return 0; + } + return -EINVAL; +} + +static int wm8987_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + +#if 1 +#ifdef CONFIG_HHTECH_MINIPMP + iface |= 0x20; // XXX: mhfan +#endif//CONFIG_HHTECH_MINIPMP +#endif //lzcx + + if(iface != wm8987_read_reg_cache(codec, WM8987_IFACE)) + wm8987_write(codec, WM8987_IFACE, iface); + return 0; +} + +static int wm8987_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + static int ifirst = 1; + u16 valface, valrate; + u16 iface = (valface = wm8987_read_reg_cache(codec, WM8987_IFACE)) & 0x1f3; + u16 srate = (valrate = wm8987_read_reg_cache(codec, WM8987_SRATE)) & 0x1c0; + +#ifndef CONFIG_HHTECH_MINIPMP + struct wm8987_priv *wm8987 = codec->private_data; + int coeff = get_coeff(wm8987->sysclk, params_rate(params)); +#else// mhfan + int coeff = get_coeff(wm8987_sysclk, params_rate(params)); +#endif//CONFIG_HHTECH_MINIPMP + + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + if(1 == ifirst || valface != iface) + wm8987_write(codec, WM8987_IFACE, iface); + if(coeff >= 0) { + srate = srate | ((coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + //printk("state write is 0x%x\n",srate); + if(valrate != srate || 1 == ifirst) + wm8987_write(codec, WM8987_SRATE, srate); + } + ifirst = 0; + + return 0; +} + +static int wm8987_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8987_read_reg_cache(codec, WM8987_ADCDAC) & 0xfff7; + +#if 0 /* comment by mhfan */ + if (mute) + wm8987_write(codec, WM8987_ADCDAC, mute_reg | 0x8); + else + wm8987_write(codec, WM8987_ADCDAC, mute_reg); +#else +// static int _mute_flag = -1; + +// if(mute == _mute_flag) return 0; +// _mute_flag = mute; + + if (mute) { + wm8987_write(codec, WM8987_ADCDAC, mute_reg | 0x8); + if (hhbf_audio_switch) hhbf_audio_switch(0); + } else {if (hhbf_audio_switch) hhbf_audio_switch(1); + wm8987_write(codec, WM8987_ADCDAC, mute_reg); + } +#endif /* comment by mhfan */ + return 0; +} + +static int wm8987_dapm_event(struct snd_soc_codec *codec, int event) +{ + u16 pwr_reg = wm8987_read_reg_cache(codec, WM8987_PWR1) & 0xfe3e; + pwr_reg |= 0x2;//lzcx micbias on + + switch (event) { + case SNDRV_CTL_POWER_D0: /* full On */ + /* set vmid to 50k and unmute dac */ + wm8987_write(codec, WM8987_PWR1, pwr_reg | 0x00c0); + break; + case SNDRV_CTL_POWER_D1: /* partial On */ + wm8987_write(codec, WM8987_PWR1, pwr_reg | 0x01c0); + break; + case SNDRV_CTL_POWER_D2: /* partial On */ + /* set vmid to 5k for quick power up */ + wm8987_write(codec, WM8987_PWR1, pwr_reg | 0x01c1); + break; + case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + /* mute dac and set vmid to 500k, enable VREF */ + wm8987_write(codec, WM8987_PWR1, pwr_reg | 0x0141); + break; + case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + wm8987_write(codec, WM8987_PWR1, 0x0001); + break; + } + codec->dapm_state = event; + return 0; +} + +#define WM8987_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define WM8987_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_codec_dai wm8987_dai = { + .name = "WM8987", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8987_RATES, + .formats = WM8987_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8987_RATES, + .formats = WM8987_FORMATS,}, + .ops = { + .hw_params = wm8987_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8987_mute, + .set_fmt = wm8987_set_dai_fmt, + .set_sysclk = wm8987_set_dai_sysclk, + .set_clkdiv = wm8987_set_dai_clkdiv, + }, +}; +EXPORT_SYMBOL_GPL(wm8987_dai); + +static void wm8987_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = +//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +// (struct snd_soc_codec*)work; // XXX: +//#else// XXX: mhfan + container_of(work, struct snd_soc_codec, delayed_work.work); +//#endif//LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + wm8987_dapm_event(codec, codec->dapm_state); +} + +static int wm8987_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8987_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int wm8987_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8987_reg); i++) { + if (i == WM8987_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8987_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + /* charge wm8987 caps */ + if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { + wm8987_dapm_event(codec, SNDRV_CTL_POWER_D2); + codec->dapm_state = SNDRV_CTL_POWER_D0; + schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); + } + + return 0; +} + +void headp_update_volume(struct work_struct* work) +{ + if (p_codec == NULL) + return; + wm8987_write(p_codec, WM8987_LDAC, volume ); + // mdelay(500); + wm8987_write(p_codec, WM8987_RDAC, volume ); +} +EXPORT_SYMBOL(headp_update_volume); + + +/* + * initialise the WM8987 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8987_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8987"; + codec->owner = THIS_MODULE; + codec->read = wm8987_read_reg_cache; + codec->write = wm8987_write; + codec->dapm_event = wm8987_dapm_event; + codec->dai = &wm8987_dai; + codec->num_dai = 1; +#ifndef CONFIG_HHTECH_MINIPMP + //codec->reg_cache_size = ARRAY_SIZE(wm8987_reg); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(wm8987_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + memcpy(codec->reg_cache, wm8987_reg, + sizeof(u16) * ARRAY_SIZE(wm8987_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8987_reg); +#else// XXX: mhfan + codec->reg_cache = (void*)wm8987_reg; + codec->reg_cache_size = ARRAY_SIZE(wm8987_reg); + +#ifdef CONFIG_HHBF_FAST_REBOOT + if (_bfin_swrst & FAST_REBOOT_FLAG) init_reboot = 1; + // XXX: read register values from codec? +#endif +#endif//CONFIG_HHTECH_MINIPMP + + wm8987_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8987: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + wm8987_dapm_event(codec, SNDRV_CTL_POWER_D1); // XXX: + codec->dapm_state = SNDRV_CTL_POWER_D3hot; + schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); + +#ifdef CONFIG_HHTECH_MINIPMP + // init first mutes + reg = wm8987_read_reg_cache(codec, WM8987_ADCDAC) & 0xfff7; + wm8987_write(codec, WM8987_ADCDAC, reg | 0x008); +// wm8987_write(codec, WM8987_PWR2, 0x180); // reboot + + //wm8987_mute(codec->dai, 1); // XXX: + reg = wm8987_read_reg_cache(codec, WM8987_LOUTM1); + wm8987_write(codec, WM8987_LOUTM1, reg | 0x0100); + reg = wm8987_read_reg_cache(codec, WM8987_ROUTM2); + wm8987_write(codec, WM8987_ROUTM2, reg | 0x0100); + + reg = wm8987_read_reg_cache(codec, WM8987_ADCTL1); + wm8987_write(codec, WM8987_ADCTL1, reg | 0x0005); // XXX: + reg = wm8987_read_reg_cache(codec, WM8987_ADCTL2); + + reg = wm8987_read_reg_cache(codec, WM8987_LADCIN); + wm8987_write(codec, WM8987_LADCIN, reg | 0x0060); // XXX: LINP2 + + reg = wm8987_read_reg_cache(codec, WM8987_LADC); + wm8987_write(codec, WM8987_LADC, reg | 0x0100); + reg = wm8987_read_reg_cache(codec, WM8987_RADC); + wm8987_write(codec, WM8987_RADC, reg | 0x0100); +#endif//CONFIG_HHTECH_MINIPMP + + /* set the update bits */ + reg = wm8987_read_reg_cache(codec, WM8987_LDAC); + //wm8987_write(codec, WM8987_LDAC, 0x0100); + wm8987_write(codec, WM8987_LDAC, 0x01c7); + reg = wm8987_read_reg_cache(codec, WM8987_RDAC); + //wm8987_write(codec, WM8987_RDAC, 0x0100); + wm8987_write(codec, WM8987_RDAC, 0x01c7); + + reg = wm8987_read_reg_cache(codec, WM8987_LINVOL); + wm8987_write(codec, WM8987_LINVOL, (reg & ~0x080) | 0x0140); + //wm8987_write(codec, WM8987_LINVOL, (reg & ~0x080) | 0x080); //lzcx + reg = wm8987_read_reg_cache(codec, WM8987_RINVOL); + wm8987_write(codec, WM8987_RINVOL, (reg & ~0x080) | 0x0140); + //wm8987_write(codec, WM8987_RINVOL, (reg & ~0x080) | 0x080); //lzcx + reg = wm8987_read_reg_cache(codec, WM8987_3D); + wm8987_write(codec, WM8987_3D, (reg | 0x80)); + wm8987_write(codec, WM8987_LOUT2V, 0x079); //lzcx +// wm8987_write(codec, WM8987_LOUT2V, 0x0100); +// wm8987_write(codec, WM8987_ROUT2V, 0x0100); + wm8987_write(codec, WM8987_ROUT2V, 0x17a); + + + wm8987_add_controls(codec); + wm8987_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8987: failed to register card\n"); + goto card_err; + } + +#ifdef CONFIG_HHTECH_MINIPMP + init_reboot = 0; +#endif//CONFIG_HHTECH_MINIPMP + + volume = wm8987_read_reg_cache(codec, WM8987_LDAC); //HHTECH wk + p_codec = codec ; + headp_update_volume(NULL); + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: +#ifndef CONFIG_HHTECH_MINIPMP + kfree(codec->reg_cache); +#endif//CONFIG_HHTECH_MINIPMP + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8987_socdev; + +#ifdef CONFIG_HHTECH_MINIPMP +void hhbf_audio_close(void) +{ + if (!wm8987_socdev) return; + cancel_delayed_work(&wm8987_socdev->delayed_work); + wm8987_reset(wm8987_socdev->codec); +} +EXPORT_SYMBOL(hhbf_audio_close); +#endif//CONFIG_HHTECH_MINIPMP + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) +/* + * WM8731 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8987_i2c_driver; +static struct i2c_client client_template; + +static int wm8987_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8987_socdev; + struct wm8987_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + +#ifndef CONFIG_HHTECH_MINIPMP + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + memcpy(i2c, &client_template, sizeof(struct i2c_client)); +#else// mhfan + i2c = &client_template; +#endif//CONFIG_HHTECH_MINIPMP + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8987_init(socdev); + if (ret < 0) { + err("failed to initialise WM8987\n"); + goto err; + } + //printk("init WM8987 OK\n"); + return ret; + +err: + kfree(codec); +#ifndef CONFIG_HHTECH_MINIPMP + kfree(i2c); +#endif//CONFIG_HHTECH_MINIPMP + return ret; +} + +static int wm8987_i2c_detach(struct i2c_client *client) +{ +#ifndef CONFIG_HHTECH_MINIPMP + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); +#else// mhfan + i2c_detach_client(client); +#endif//CONFIG_HHTECH_MINIPMP + return 0; +} + +static int wm8987_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8987_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8987_i2c_driver = { + .driver = { + .name = "WM8987 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8753, + .attach_adapter = wm8987_i2c_attach, + .detach_client = wm8987_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8987", + .driver = &wm8987_i2c_driver, +}; +#endif + +static int wm8987_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8987_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec; + //struct wm8987_priv *wm8987; // mhfan + int ret = 0; + + info("Audio Codec Driver %s", WM8987_VERSION); + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + +#ifndef CONFIG_HHTECH_MINIPMP + wm8987 = kzalloc(sizeof(struct wm8987_priv), GFP_KERNEL); + if (wm8987 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8987; +#endif//CONFIG_HHTECH_MINIPMP + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8987_socdev = socdev; +//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +// INIT_WORK(&codec->delayed_work, wm8987_work, codec); +//#else// XXX: mhfan + INIT_DELAYED_WORK(&codec->delayed_work, wm8987_work); +//#endif// LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8987_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + + return ret; +} + +/* + * This function forces any delayed work to be queued and run. + */ +static int run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(dwork); + + /* if there was any work waiting then we run it now and + * wait for it's completion */ + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + return ret; +} + +/* power down chip */ +static int wm8987_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8987_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + run_delayed_work(&codec->delayed_work); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_del_driver(&wm8987_i2c_driver); +#endif +#ifndef CONFIG_HHTECH_MINIPMP + kfree(codec->private_data); +#endif//CONFIG_HHTECH_MINIPMP + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8987 = { + .probe = wm8987_probe, + .remove = wm8987_remove, + .suspend = wm8987_suspend, + .resume = wm8987_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8987); + +MODULE_DESCRIPTION("ASoC WM8987 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8987.h b/sound/soc/codecs/wm8987.h new file mode 100644 index 0000000..fa9820b --- /dev/null +++ b/sound/soc/codecs/wm8987.h @@ -0,0 +1,110 @@ +/* + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on WM8987.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _WM8987_H +#define _WM8987_H + +#define CONFIG_HHTECH_MINIPMP 1 + +/* WM8987 register space */ + +#define WM8987_LINVOL 0x00 +#define WM8987_RINVOL 0x01 +#define WM8987_LOUT1V 0x02 +#define WM8987_ROUT1V 0x03 +#define WM8987_ADCDAC 0x05 +#define WM8987_IFACE 0x07 +#define WM8987_SRATE 0x08 +#define WM8987_LDAC 0x0a +#define WM8987_RDAC 0x0b +#define WM8987_BASS 0x0c +#define WM8987_TREBLE 0x0d +#define WM8987_RESET 0x0f +#define WM8987_3D 0x10 +#define WM8987_ALC1 0x11 +#define WM8987_ALC2 0x12 +#define WM8987_ALC3 0x13 +#define WM8987_NGATE 0x14 +#define WM8987_LADC 0x15 +#define WM8987_RADC 0x16 +#define WM8987_ADCTL1 0x17 +#define WM8987_ADCTL2 0x18 +#define WM8987_PWR1 0x19 +#define WM8987_PWR2 0x1a +#define WM8987_ADCTL3 0x1b +#define WM8987_ADCIN 0x1f +#define WM8987_LADCIN 0x20 +#define WM8987_RADCIN 0x21 +#define WM8987_LOUTM1 0x22 +#define WM8987_LOUTM2 0x23 +#define WM8987_ROUTM1 0x24 +#define WM8987_ROUTM2 0x25 +#define WM8987_MOUTM1 0x26 +#define WM8987_MOUTM2 0x27 +#define WM8987_LOUT2V 0x28 +#define WM8987_ROUT2V 0x29 +#define WM8987_MOUTV 0x2a + +#define WM8987_CACHE_REGNUM 0x2a + +#define WM8987_SYSCLK 0 + +struct wm8987_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai wm8987_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8987; + +#if 1 //lzcx +#define WM8987_PLL1 0 +#define WM8987_PLL2 1 + +/* clock inputs */ +#define WM8987_MCLK 0 +#define WM8987_PCMCLK 1 + +/* clock divider id's */ +#define WM8987_PCMDIV 0 +#define WM8987_BCLKDIV 1 +#define WM8987_VXCLKDIV 2 + +/* PCM clock dividers */ +#define WM8987_PCM_DIV_1 (0 << 6) +#define WM8987_PCM_DIV_3 (2 << 6) +#define WM8987_PCM_DIV_5_5 (3 << 6) +#define WM8987_PCM_DIV_2 (4 << 6) +#define WM8987_PCM_DIV_4 (5 << 6) +#define WM8987_PCM_DIV_6 (6 << 6) +#define WM8987_PCM_DIV_8 (7 << 6) + +/* BCLK clock dividers */ +#define WM8987_BCLK_DIV_1 (0 << 7) +#define WM8987_BCLK_DIV_2 (1 << 7) +#define WM8987_BCLK_DIV_4 (2 << 7) +#define WM8987_BCLK_DIV_8 (3 << 7) + +/* VXCLK clock dividers */ +#define WM8987_VXCLK_DIV_1 (0 << 6) +#define WM8987_VXCLK_DIV_2 (1 << 6) +#define WM8987_VXCLK_DIV_4 (2 << 6) +#define WM8987_VXCLK_DIV_8 (3 << 6) +#define WM8987_VXCLK_DIV_16 (4 << 6) + +#define WM8987_DAI_HIFI 0 +#define WM8987_DAI_VOICE 1 + + +#endif + +#endif diff --git a/sound/soc/s3c64xx/Kconfig b/sound/soc/s3c64xx/Kconfig index 705c677..1faeeae 100644 --- a/sound/soc/s3c64xx/Kconfig +++ b/sound/soc/s3c64xx/Kconfig @@ -17,6 +17,9 @@ config SND_S3C_SOC_I2S config SND_S3C6410_SOC_I2S_V32 tristate +config SND_S3C6410_SOC_I2S_V40 + tristate + config SND_S3C6410_SOC_I2S tristate @@ -38,19 +41,17 @@ choice config SOUND_WM9713_INPUT_STREAM_LINE bool "Input Stream is LINE-IN" - depends on SND_S3C64XX_SOC_SMDK6400_WM9713 || SND_S3C64XX_SOC_SMDK6410_WM9713 help Say Y here to make input stream as LINE-IN. config SOUND_WM9713_INPUT_STREAM_MIC bool "Input Stream is MIC" - depends on SND_S3C64XX_SOC_SMDK6400_WM9713 || SND_S3C64XX_SOC_SMDK6410_WM9713 help Say Y here to make input stream as MIC. endchoice config SND_S3C6410_SOC_AC97 - tristate + tristate "AC97 support for SMDK6410" select AC97_BUS select SND_AC97_CODEC select SND_SOC_AC97_BUS @@ -70,17 +71,29 @@ choice config SOUND_WM8580_INPUT_STREAM_LINE bool "Input Stream is LINE-IN" - depends on SND_S3C6410_SOC_SMDK6410_WM8580 || SND_S3C24XX_SOC_SMDK2450_WM8580 help Say Y here to make input stream as LINE-IN. config SOUND_WM8580_INPUT_STREAM_MIC bool "Input Stream is MIC" - depends on SND_S3C6410_SOC_SMDK6410_WM8580 || SND_S3C24XX_SOC_SMDK2450_WM8580 help Say Y here to make input stream as MIC. endchoice +config SND_S3C64XX_SOC_SMDK6410_WM8987 + tristate "SoC I2S Audio support for SMDK6410 - WM8987" + depends on SND_S3C_SOC && (MACH_SMDK6410) + select SND_S3C6410_SOC_I2S_V32 + select SND_SOC_WM8987 + help + Say Y if you want to add support for SoC audio on smdk6410 + with the WM8987. + +config AUDIO_CODEC_PROCFS + bool "Proc-FS interface for audio codec control" + depends on SND_S3C64XX_SOC_SMDK6410_WM8987 && PROC_FS + default y + config SND_S3C64XX_SOC_SMDK6410_WM8990 tristate "SoC I2S Audio support for SMDK6410 - WM8990" depends on SND_S3C_SOC && (MACH_SMDK6410) @@ -96,13 +109,11 @@ choice config SOUND_WM8990_INPUT_STREAM_LINE_IN bool "Input Stream is LINE-IN" - depends on SND_S3C64XX_SOC_SMDK6410_WM8990 || SND_S3C64XX_SOC_SMDK6400_WM8990 help Say Y here to make input stream as LINE-IN. config SOUND_WM8990_INPUT_STREAM_MIC_IN bool "Input Stream is MIC" - depends on SND_S3C64XX_SOC_SMDK6410_WM8990 || SND_S3C64XX_SOC_SMDK6400_WM8990 help Say Y here to make input stream as MIC. endchoice diff --git a/sound/soc/s3c64xx/Makefile b/sound/soc/s3c64xx/Makefile index 2865391..85e848d 100644 --- a/sound/soc/s3c64xx/Makefile +++ b/sound/soc/s3c64xx/Makefile @@ -11,8 +11,10 @@ obj-$(CONFIG_SND_S3C6410_SOC_I2S_V32) += snd-soc-s3c6410-i2s-v32.o snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o snd-soc-smdk64xx-wm9713-objs := smdk64xx_wm9713.o snd-soc-smdk6410-wm8580-objs := smdk6410_wm8580.o +snd-soc-smdk6410-wm8987-objs := smdk6410_wm8987.o snd-soc-smdk6410-wm8990-objs := smdk6410_wm8990.o obj-$(CONFIG_SND_S3C64XX_SOC_SMDK6410_WM9713) += snd-soc-smdk64xx-wm9713.o obj-$(CONFIG_SND_S3C6410_SOC_SMDK6410_WM8580) += snd-soc-smdk6410-wm8580.o +obj-$(CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8987) += snd-soc-smdk6410-wm8987.o obj-$(CONFIG_SND_S3C64XX_SOC_SMDK6410_WM8990) += snd-soc-smdk6410-wm8990.o diff --git a/sound/soc/s3c64xx/s3c6410-i2s-v32.c b/sound/soc/s3c64xx/s3c6410-i2s-v32.c index 6c7446e..7a08bc8 100644 --- a/sound/soc/s3c64xx/s3c6410-i2s-v32.c +++ b/sound/soc/s3c64xx/s3c6410-i2s-v32.c @@ -265,8 +265,8 @@ static int s3c_i2s_hw_params(struct snd_pcm_substream *substream, /*Set I2C port to controll WM8753 codec*/ s3c_gpio_pullup(S3C_GPB5, 0); s3c_gpio_pullup(S3C_GPB6, 0); - s3c_gpio_cfgpin(S3C_GPB5, S3C_GPB5_I2C_SCL); - s3c_gpio_cfgpin(S3C_GPB6, S3C_GPB6_I2C_SDA); + s3c_gpio_cfgpin(S3C_GPB5, S3C_GPB5_I2C_SCL0); + s3c_gpio_cfgpin(S3C_GPB6, S3C_GPB6_I2C_SDA0); s3c24xx_i2s.master = 1; diff --git a/sound/soc/s3c64xx/smdk6410_wm8987.c b/sound/soc/s3c64xx/smdk6410_wm8987.c new file mode 100644 index 0000000..c862d0f --- /dev/null +++ b/sound/soc/s3c64xx/smdk6410_wm8987.c @@ -0,0 +1,623 @@ +/* + * smdk64xx_wm8987.c -- SoC audio for Neo1973 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 20th Jan 2007 Initial version. + * 05th Feb 2007 Rename all to Neo1973 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include //lzcx +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef CONFIG_CPU_S3C6400 +#include +#elif defined CONFIG_CPU_S3C6410 +#include +#else + +#endif + +#include "../codecs/wm8987.h" +#include "../s3c24xx/s3c-pcm.h" +#include "../s3c24xx/s3c-i2s.h" + +/* define the scenarios */ +#define SMDK6400_AUDIO_OFF 0 +#define SMDK6400_CAPTURE_MIC1 3 +#define SMDK6400_STEREO_TO_HEADPHONES 2 +#define SMDK6400_CAPTURE_LINE_IN 1 + +#ifdef CONFIG_SND_DEBUG +#define s3cdbg(x...) printk(x) +#else +#define s3cdbg(x...) +#endif + +static struct snd_soc_machine smdk6400; + +static int smdk6400_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned int iispsr, iismod; + unsigned int prescaler = 4; + unsigned long regs = ioremap(S3C6400_PA_IIS, 0x100); + s3cdbg("Entered %s, rate = %d\n", __FUNCTION__, params_rate(params)); + + /*PCLK & SCLK gating enable*/ + writel(readl(S3C_PCLK_GATE)|S3C_CLKCON_PCLK_IIS0, S3C_PCLK_GATE); + writel(readl(S3C_SCLK_GATE)|S3C_CLKCON_SCLK_AUDIO0, S3C_SCLK_GATE); + + iismod = readl((regs + S3C64XX_IIS0MOD)); + iismod &=~(0x3<<3); + + /*Clear I2S prescaler value [13:8] and disable prescaler*/ + iispsr = readl((regs + S3C64XX_IIS0PSR)); + iispsr &=~((0x3f<<8)|(1<<15)); + writel(iispsr, (regs+S3C64XX_IIS0PSR)); + + switch (params_rate(params)) { + case 16000: + case 32000: + writel(0, S3C_EPLL_CON1); + writel((1<<31)|(128<<16)|(25<<8)|(0<<0) ,S3C_EPLL_CON0); + break; + case 8000: + prescaler = 0xe; + case 48000: + writel(0, S3C_EPLL_CON1); + writel((1<<31)|(192<<16)|(25<<8)|(0<<0) ,S3C_EPLL_CON0); + break; + case 11025: + prescaler = 9; + case 22050: + case 44100: + writel(0, S3C_EPLL_CON1); + writel((1<<31)|(254<<16)|(9<<8)|(2<<0) ,S3C_EPLL_CON0); + break; + default: + break; + } + + s3cdbg("%s, IISCON: %x IISMOD: %x,IISFIC: %x,IISPSR: %x", + __FUNCTION__ , readl(S3C_IIS0CON), readl(S3C_IIS0MOD), + readl(S3C_IIS0FIC), readl(S3C_IIS0PSR)); + + while(!(__raw_readl(S3C_EPLL_CON0)&(1<<30))); + + /* MUXepll : FOUTepll */ + writel(readl(S3C_CLK_SRC)|S3C_CLKSRC_EPLL_CLKSEL, S3C_CLK_SRC); + /* AUDIO0 sel : FOUTepll */ + //writel((readl(S3C_CLK_SRC)&~(0x7<<7))|(0<<7), S3C_CLK_SRC); + writel((readl(S3C_CLK_SRC)&~(0x7<<7))|(0x0<<7), S3C_CLK_SRC); //lzcx + + /* CLK_DIV2 setting */ + writel(0x0,S3C_CLK_DIV2); + + switch (params_rate(params)) { + case 8000: + iismod |= S3C64XX_IIS0MOD_768FS; + pll_out = 12288000; + //pll_out = 6144000; //lzcx + break; + case 11025: + iismod |= S3C64XX_IIS0MOD_768FS; + //bclk = WM8987_BCLK_DIV_16; + bclk = WM8987_BCLK_DIV_8 | (1<<6); //lzcx + pll_out = 16934400; + break; + case 16000: + iismod |= S3C64XX_IIS0MOD_768FS; + bclk = WM8987_BCLK_DIV_2; + pll_out = 12288000; + break; + case 22050: + iismod |= S3C64XX_IIS0MOD_768FS; + bclk = WM8987_BCLK_DIV_8; + pll_out = 16934400; + break; + case 96000: //lzcx add + case 32000: + iismod |= S3C64XX_IIS0MOD_384FS; + bclk = WM8987_BCLK_DIV_2; + pll_out = 12288000; + break; + case 44100: + iismod |= S3C64XX_IIS0MOD_384FS; + bclk = WM8987_BCLK_DIV_4; + pll_out = 16934400; + break; + case 48000: + iismod |= S3C64XX_IIS0MOD_384FS; + bclk = WM8987_BCLK_DIV_4; + pll_out = 18432000; + break; + } + + writel(iismod , (regs+S3C64XX_IIS0MOD)); + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS ); + if (ret < 0) + { printk("aaa\n"); + return ret; + } + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS ); + if (ret < 0) + { printk("aaa1\n"); + return ret; + } + + /* set the codec system clock for DAC and ADC */ + ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8987_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + { printk("aaa2 pll_out %ld \n",pll_out); + return ret; + } + + /* set MCLK division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C64XX_IISMOD_32FS ); + if (ret < 0) + { printk("aaa3\n"); + return ret; + } + + /* set codec BCLK division for sample rate */ + ret = codec_dai->dai_ops.set_clkdiv(codec_dai, WM8987_BCLKDIV, bclk); + if (ret < 0) + { printk("aaa4\n"); + return ret; + } + + /* set prescaler division for sample rate */ + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + (prescaler << 0x8)); + if (ret < 0) + { printk("aaa5\n"); + return ret; + } + + return 0; +} + +static int smdk6400_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + + /* disable the PLL */ + return 0;//lzcx codec_dai->dai_ops.set_pll(codec_dai, WM8987_PLL1, 0, 0); + //return codec_dai->dai_ops.set_pll(codec_dai, WM8987_PLL1, 0, 0); +} + +/* + * Neo1973 WM8987 HiFi DAI opserations. + */ +static struct snd_soc_ops smdk6400_hifi_ops = { + .hw_params = smdk6400_hifi_hw_params, + .hw_free = smdk6400_hifi_hw_free, +}; + +static int smdk6400_scenario = 0; + +static int smdk6400_get_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = smdk6400_scenario; + return 0; +} + +static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) +{ + switch(smdk6400_scenario) { + printk("smdk6400_scenario is %d\n",smdk6400_scenario);//lzcx + case SMDK6400_AUDIO_OFF: + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); + snd_soc_dapm_set_endpoint(codec, "Mic Bias", 0); + snd_soc_dapm_set_endpoint(codec, "Line In Jack", 0); + break; + case SMDK6400_STEREO_TO_HEADPHONES: + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); + snd_soc_dapm_set_endpoint(codec, "Mic Bias", 0); + snd_soc_dapm_set_endpoint(codec, "Line In Jack", 0); + break; + case SMDK6400_CAPTURE_MIC1: + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); + snd_soc_dapm_set_endpoint(codec, "Mic Bias", 1); + snd_soc_dapm_set_endpoint(codec, "Line In Jack", 0); + break; + case SMDK6400_CAPTURE_LINE_IN: + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0); + snd_soc_dapm_set_endpoint(codec, "Mic Bias", 0); + snd_soc_dapm_set_endpoint(codec, "Line In Jack", 1); + break; + default: + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); + snd_soc_dapm_set_endpoint(codec, "Mic Bias", 1); + snd_soc_dapm_set_endpoint(codec, "Line In Jack", 1); + break; + } + + snd_soc_dapm_sync_endpoints(codec); + + return 0; +} + +static int smdk6400_set_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (smdk6400_scenario == ucontrol->value.integer.value[0]) + return 0; + + smdk6400_scenario = ucontrol->value.integer.value[0]; + set_scenario_endpoints(codec, smdk6400_scenario); + return 1; +} + +static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Bias", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + + +/* example machine audio_mapnections */ +static const char* audio_map[][3] = { + + {"Headphone Jack", NULL, "LOUT2"}, + {"Headphone Jack", NULL, "ROUT2"}, //lzcx 1 + +// mic is connected to line2 //lzcx + + { "LINPUT2", NULL, "Mic Bias" }, + { "Mic Bias", NULL, "Mic Jack" }, //lzcx + + {"LINPUT1", NULL, "Line In Jack"}, + {"RINPUT1", NULL, "Line In Jack"}, + +#if 0 + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, +#endif + + {NULL, NULL, NULL}, +}; + +static const char *smdk_scenarios[] = { + "Off", + "Capture Line In", + "Headphones", + "Capture Mic1", +}; + +static const struct soc_enum smdk_scenario_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(smdk_scenarios),smdk_scenarios), +}; + +static const struct snd_kcontrol_new wm8987_smdk6400_controls[] = { + SOC_ENUM_EXT("SMDK Mode", smdk_scenario_enum[0], + smdk6400_get_scenario, smdk6400_set_scenario), +}; + +/* + * This is an example machine initialisation for a wm8987 connected to a + * smdk6400. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int smdk6400_wm8987_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set endpoints to default mode */ + set_scenario_endpoints(codec, SMDK6400_AUDIO_OFF); + + /* Add smdk6400 specific widgets */ + for (i = 0; i < ARRAY_SIZE(wm8987_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &wm8987_dapm_widgets[i]); + + /* add smdk6400 specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8987_smdk6400_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8987_smdk6400_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + /* set up smdk6400 specific audio path audio_mapnects */ + for (i = 0; audio_map[i][0] != NULL; i++) { + snd_soc_dapm_connect_input(codec, audio_map[i][0], + audio_map[i][1], audio_map[i][2]); + } + + /* always connected */ + snd_soc_dapm_set_endpoint(codec, "Mic Bias", 1); + snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1); + snd_soc_dapm_set_endpoint(codec, "Line In Jack", 1); + + snd_soc_dapm_sync_endpoints(codec); + return 0; +} + +static int smdk6400_probe(struct platform_device *pdev) +{ + /*Set I2C port to controll WM8987 codec*/ + s3c_gpio_pullup(S3C_GPB5, 0); + s3c_gpio_pullup(S3C_GPB6, 0); + s3c_gpio_cfgpin(S3C_GPB5, S3C_GPB5_I2C_SCL0); + s3c_gpio_cfgpin(S3C_GPB6, S3C_GPB6_I2C_SDA0); + return 0; +} + +static struct snd_soc_dai_link smdk6400_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8987", + .stream_name = "WM8987 HiFi", + .cpu_dai = &s3c_i2s_dai, + //.codec_dai = &wm8987_dai[WM8987_DAI_HIFI], + .codec_dai = &wm8987_dai, + .init = smdk6400_wm8987_init, + .ops = &smdk6400_hifi_ops, +}, +}; + +static struct snd_soc_machine smdk6400 = { + .name = "smdk6400", + .probe = smdk6400_probe, + .dai_link = smdk6400_dai, + .num_links = ARRAY_SIZE(smdk6400_dai), +}; + +static struct wm8987_setup_data smdk6400_wm8987_setup = { + .i2c_address = 0x1a, +}; + +static struct snd_soc_device smdk6400_snd_devdata = { + .machine = &smdk6400, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8987, + .codec_data = &smdk6400_wm8987_setup, +}; + +static struct platform_device *smdk6400_snd_device; + +#ifdef CONFIG_AUDIO_CODEC_PROCFS + +// This function forces any delayed work to be queued and run. +static int proc_run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + // cancel any work waiting to be queued. + ret = cancel_delayed_work(dwork); + + // if there was any work waiting then we run it now and + // wait for it's completion. + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + + return ret; +} + +static int aud_proc_read(char* page, char** start, off_t off, int count, + int* eof, void* data) +{ + struct snd_soc_codec* codec = smdk6400_snd_devdata.codec; + + if (!codec || !codec->read) return count; + + if (off) return 0; data = (void*)page; + + page += sprintf(page, "%s registers cached settings: ", codec->name); + for (count=0; count < codec->reg_cache_size; ++count) { + if (!(count % 16)) page += sprintf(page, "\n R%02x: ", count); + page += sprintf(page, "%03x ", codec->read(codec, count)); + if ((count % 8) == 7) page += sprintf(page, " "); + } + + return ((page += sprintf(page, "\n")) - (char*)data); +} + + +static int aud_proc_write(struct file* file, const char* buffer, + unsigned long count, void* data) +{ +#define MAX_BUFLEN 16 + u8 reg; + u16 val = MAX_BUFLEN - 1; + char *ptr, tmp_buf[MAX_BUFLEN]; + struct snd_soc_codec* codec = smdk6400_snd_devdata.codec; + + if (!codec || !codec->write) return count; + + if (count < MAX_BUFLEN) val = count - 1; tmp_buf[val] = 0; + if (copy_from_user(tmp_buf, buffer, val)) return -EFAULT; + + for (ptr = tmp_buf; isspace(*ptr); ++ptr) ; + +#if 0 + if (strncasecmp(ptr, "FmOn", val) == 0) { // enter fmmode + ptr += 4; + proc_run_delayed_work(&smdk6400_snd_devdata.delayed_work); + cancel_delayed_work(&codec->delayed_work); + codec->write(codec, 0x1A, 0x018); + codec->write(codec, 0x22, 0x0D0); + codec->write(codec, 0x25, 0x0D0); + return count; + } else + if (strncasecmp(ptr, "FmOff", val) == 0) { // leave fmmode + ptr += 5; + codec->write(codec, 0x1A, 0x000); + codec->write(codec, 0x22, 0x150); + codec->write(codec, 0x25, 0x150); + return count; + } else + if (strncasecmp(ptr, "FmRecOn", val) == 0) { // record fm +#if 0 + struct snd_soc_dapm_path *p; + list_for_each_entry(p, &codec->dapm_paths, list) { + if(strncmp(p->sink->name, "Left PGA Mux", 12)) continue; + if(!p->connect && !strcmp(p->source->name, "LINPUT1")) + p->connect = 1; + if(p->connect && !strnmp(p->source->name, "LINPUT2")) + p->connect = 0; + } +#endif + proc_run_delayed_work(&smdk6400_snd_devdata.delayed_work); + cancel_delayed_work_sync(&codec->delayed_work); + codec->write(codec, 0x20, 0x000); + //smdk6400_snd_devdata.s_rec = 1; + ptr += 7; + return count; + } else + if (strncasecmp(ptr, "FmRecOff", val) == 0) { // leave record fm + codec->write(codec, 0x20, 0x060); + //smdk6400_snd_devdata.s_rec = 0; + ptr += 8; + return count; + } +#endif //lzcx + + reg = simple_strtoul(ptr, &ptr, 16); + + if (!(reg < codec->reg_cache_size)) { + printk(KERN_DEBUG "wrong register no %d, max %d\n", + reg, codec->reg_cache_size); + return count; + } + + while (isspace(*ptr)) ++ptr; + val = simple_strtoul(ptr, &ptr, 16); + + if (codec->write(codec, reg, val)) ; + + return count; +} + +#if 0 /* comment by mhfan */ +/* + * Initial: + * R00: 157 157 1f9 1f9 000 008 000 00a 000 000 1ff 1ff 00f 00f 000 000 + * R10: 000 07b 000 032 000 1c3 1c3 0c5 050 141 000 000 000 000 000 000 + * R20: 0a0 000 150 050 050 150 050 050 1f9 1f9 079 + * + * Playback: + * R00: 157 157 1f9 1f9 000 000 000 042 023 000 1ff 1ff 00f 00f 000 000 + * R10: 000 07b 000 032 000 1c3 1c3 0c5 050 0c0 1fa 000 000 000 000 000 + * R20: 0a0 000 150 050 050 150 050 050 1f9 1f9 079 (44.1 KHz) + * + * R00: 157 157 1f9 1f9 000 000 000 042 037 000 1ff 1ff 00f 00f 000 000 + * R10: 000 07b 000 032 000 1c3 1c3 0c5 050 0c0 1fa 000 000 000 000 000 + * R20: 0a0 000 150 050 050 150 050 050 1f9 1f9 079 (22050 Hz) + * + * Capture: + */ +#endif /* comment by mhfan */ + +#define AUD_PROC_ENTRY "driver/audregs" + +static int __init aud_proc_init(void) +{ + struct proc_dir_entry* aud_entry; + + if (!(aud_entry = create_proc_entry(AUD_PROC_ENTRY, + S_IRUGO | S_IWUSR, NULL))) return -ENOMEM; + + printk(KERN_INFO "Proc-FS interface for audio codec\n"); + + aud_entry->owner = THIS_MODULE; + aud_entry->write_proc = aud_proc_write; + aud_entry->read_proc = aud_proc_read; + aud_entry->data = NULL; + + return 0; +} + +static void __exit aud_proc_exit(void) +{ + remove_proc_entry(AUD_PROC_ENTRY, NULL); +} +#endif//CONFIG_AUDIO_CODEC_PROCFS + + +static int __init smdk6400_init(void) +{ + int ret; + + smdk6400_snd_device = platform_device_alloc("soc-audio", -1); + if (!smdk6400_snd_device) + return -ENOMEM; + + platform_set_drvdata(smdk6400_snd_device, &smdk6400_snd_devdata); + smdk6400_snd_devdata.dev = &smdk6400_snd_device->dev; + ret = platform_device_add(smdk6400_snd_device); + + if (ret) + platform_device_put(smdk6400_snd_device); +#ifdef CONFIG_AUDIO_CODEC_PROCFS + if (aud_proc_init()) ; +#endif//CONFIG_AUDIO_CODEC_PROCFS + + + return ret; +} + +static void __exit smdk6400_exit(void) +{ + platform_device_unregister(smdk6400_snd_device); +} + +module_init(smdk6400_init); +module_exit(smdk6400_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM8987 Neo1973"); +MODULE_LICENSE("GPL"); -- 1.6.2.4