diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 639411f73ca957..22f6d00600a11e 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -880,6 +880,8 @@ source "arch/arm/mach-meson/Kconfig" source "arch/arm/mach-moxart/Kconfig" +source "arch/arm/mach-aspeed/Kconfig" + source "arch/arm/mach-mv78xx0/Kconfig" source "arch/arm/mach-imx/Kconfig" diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 0cfd7f947f6b99..19de31a3242ec8 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -1209,6 +1209,7 @@ choice config DEBUG_LL_UART_8250 bool "Kernel low-level debugging via 8250 UART" + select DEBUG_UART_8250 help Say Y here if you wish the debug print routes to direct their output to an 8250 UART. You can use this option diff --git a/arch/arm/Makefile b/arch/arm/Makefile index 2c2b28ee481197..90c32f58e191a9 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -184,6 +184,8 @@ machine-$(CONFIG_ARCH_LPC32XX) += lpc32xx machine-$(CONFIG_ARCH_MESON) += meson machine-$(CONFIG_ARCH_MMP) += mmp machine-$(CONFIG_ARCH_MOXART) += moxart +machine-$(CONFIG_ARCH_ASPEED) += aspeed +machine-$(CONFIG_ARCH_MSM) += msm machine-$(CONFIG_ARCH_MV78XX0) += mv78xx0 machine-$(CONFIG_ARCH_MVEBU) += mvebu machine-$(CONFIG_ARCH_MXC) += imx @@ -258,13 +260,11 @@ endif machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y)) platdirs := $(patsubst %,arch/arm/plat-%/,$(sort $(plat-y))) -ifneq ($(CONFIG_ARCH_MULTIPLATFORM),y) ifeq ($(KBUILD_SRC),) KBUILD_CPPFLAGS += $(patsubst %,-I%include,$(machdirs) $(platdirs)) else KBUILD_CPPFLAGS += $(patsubst %,-I$(srctree)/%include,$(machdirs) $(platdirs)) endif -endif export TEXT_OFFSET GZFLAGS MMUEXT diff --git a/arch/arm/boot/compressed/atags_to_fdt.c b/arch/arm/boot/compressed/atags_to_fdt.c index 9448aa0c66869d..d3131ee9efb489 100644 --- a/arch/arm/boot/compressed/atags_to_fdt.c +++ b/arch/arm/boot/compressed/atags_to_fdt.c @@ -1,6 +1,8 @@ #include #include +#include CONFIG_UNCOMPRESS_INCLUDE + #if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND) #define do_extend_cmdline 1 #else @@ -105,6 +107,34 @@ static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline) * = 1 -> bad ATAG (may retry with another possible ATAG pointer) * < 0 -> error from libfdt */ +void putstr(const char *ptr) +{ + char c; + + while ((c = *ptr++) != '\0') { + if (c == '\n') + putc('\r'); + putc(c); + } + + flush(); +} + +static void puthex(unsigned long val) +{ + int i, nibbles = sizeof(val)*2; + char buf[sizeof(val)*2+1]; + + for (i = nibbles-1; i >= 0; i--) { + buf[i] = (val & 0xf) + '0'; + if (buf[i] > '9') + buf[i] += ('a'-'0'-10); + val >>= 4; + } + buf[nibbles] = '\0'; + putstr(buf); +} + int atags_to_fdt(void *atag_list, void *fdt, int total_space) { struct tag *atag = atag_list; @@ -114,6 +144,12 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space) int memcount = 0; int ret, memsize; + putstr("atags_to_fdt\natags="); + puthex((unsigned long)atag_list); + putstr("\nfdt="); + puthex((unsigned long)fdt); + putstr("\n"); + /* make sure we've got an aligned pointer */ if ((u32)atag_list & 0x3) return 1; @@ -141,6 +177,9 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space) * the device tree and in the tags, the one from the * tags will be chosen. */ + putstr("cmdline="); + putstr(atag->u.cmdline.cmdline); + putstr("\n"); if (do_extend_cmdline) merge_fdt_bootargs(fdt, atag->u.cmdline.cmdline); @@ -164,6 +203,11 @@ int atags_to_fdt(void *atag_list, void *fdt, int total_space) cpu_to_fdt64(atag->u.mem.start); mem_reg_prop64[memcount++] = cpu_to_fdt64(atag->u.mem.size); + putstr("mem="); + puthex(atag->u.mem.start); + putstr("/"); + puthex(atag->u.mem.size); + putstr("\n"); } else { mem_reg_property[memcount++] = cpu_to_fdt32(atag->u.mem.start); diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index bb8fa023d5741d..7bf59ad2d4230e 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -738,6 +738,7 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += \ mt8127-moose.dtb \ mt8135-evbp1.dtb dtb-$(CONFIG_ARCH_ZX) += zx296702-ad1.dtb +dtb-$(CONFIG_MACH_OPP_PALMETTO_BMC) += aspeed-bmc-opp-palmetto.dtb endif always := $(dtb-y) diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts b/arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts new file mode 100644 index 00000000000000..b663dd84da426b --- /dev/null +++ b/arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts @@ -0,0 +1,42 @@ +/dts-v1/; + +#include "ast2400.dtsi" + +/ { + model = "Barrelye BMC"; + compatible = "rackspace,barreleye-bmc", "aspeed,ast2400"; + ahb { + apb { + i2c: i2c@1e78a040 { + i2c0: i2c-bus@0x40 { + eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + pagesize = <64>; + }; + rtc@68 { + compatible = "dallas,ds3231"; + reg = <0x68>; + // interrupts = + }; + lm75@4a { + compatible = "national,lm75"; + reg = <0x4a>; + }; + }; + + i2c6: i2c-bus@0x1C0 { + nct7904@2d { + compatible = "nuvoton,nct7904"; + reg = <0x2d>; + }; + nct7904@2e { + compatible = "nuvoton,nct7904"; + reg = <0x2e>; + }; + }; + }; + }; + }; +}; + diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts new file mode 100644 index 00000000000000..5b25fdad1ff949 --- /dev/null +++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts @@ -0,0 +1,34 @@ +/dts-v1/; + +#include "ast2400.dtsi" + +/ { + model = "Palmetto BMC"; + compatible = "tyan,palmetto-bmc", "aspeed,ast2400"; + + ahb { + apb { + i2c: i2c@1e78a040 { + i2c0: i2c-bus@0x40 { + eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + pagesize = <64>; + }; + rtc@68 { + compatible = "dallas,ds3231"; + reg = <0x68>; + // interrupts = + }; + }; + + i2c2: i2c-bus@0xC0 { + tmp423@4c { + compatible = "ti,tmp423"; + reg = <0x4c>; + }; + }; + }; + }; + }; +}; diff --git a/arch/arm/boot/dts/ast2400.dtsi b/arch/arm/boot/dts/ast2400.dtsi new file mode 100644 index 00000000000000..6a091252474a66 --- /dev/null +++ b/arch/arm/boot/dts/ast2400.dtsi @@ -0,0 +1,350 @@ +#include "skeleton.dtsi" + +/ { + model = "Palmetto BMC"; + compatible = "tyan,palmetto-bmc", "aspeed,ast2400"; + #address-cells = <1>; + #size-cells = <1>; + interrupt-parent = <&vic>; + + aliases { + serial0 = &uart5; + }; + + chosen { + stdout-path = &uart5; + bootargs = "console=ttyS4,38400"; + }; + + memory { + reg = < 0x40000000 0x10000000 >; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + compatible = "arm,arm926ej-s"; + device_type = "cpu"; + reg = <0>; + }; + }; + + // FIXME + clocks { + // Do a proper driver... for now, we know the straps + // and uboot config on palmetto are: + // - CLKIN is 48Mhz + // - HPLL is 384Mhz + // - CPU:AHB is strapped 2:1 + // - PCLK is HPLL/8 = 48Mhz + clk_apb: clk_apb { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <48000000>; + }; + clk_hpll: clk_hpll { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <384000000>; + }; + }; + + ahb { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + vic: interrupt-controller@1e6c0080 { + compatible = "aspeed,new-vic"; + interrupt-controller; + #interrupt-cells = <1>; + valid-sources = < 0xffffffff 0x0007ffff>; + reg = <0x1e6c0080 0x80>; + }; + + mac0: ethernet@1e660000 { + compatible = "faraday,ftgmac100", "aspeed,ast2400-mac"; + reg = <0x1e660000 0x180>; + interrupts = <2>; + use-nc-si; + no-hw-checksum; + }; + + apb { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + sram@1e72000 { + compatible = "mmio-sram"; + reg = <0x1e72000 0x8000>; // 32K + }; + + ibt@1e789140 { + compatible = "aspeed,bt-host"; + reg = <0x1e789140 0x18>; + interrupts = <8>; + }; + + i2c: i2c@1e78a040 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "aspeed,ast2400-i2c-common"; + reg = <0x1e78a000 0x40>; + ranges = <0 0x1e78a000 0x1000>; + interrupts = <12>; + clocks = <&clk_apb>; + + i2c0: i2c-bus@0x40 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x40 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <0>; + clock-frequency = <100000>; + status = "okay"; + eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + pagesize = <64>; + }; + rtc@68 { + compatible = "dallas,ds3231"; + reg = <0x68>; + // interrupts = + }; + }; + + i2c1: i2c-bus@0x80 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x80 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <1>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c2: i2c-bus@0xC0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0xC0 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <2>; + clock-frequency = <100000>; + status = "okay"; + tmp423@4c { + compatible = "ti,tmp423"; + reg = <0x4c>; + }; + }; + + i2c3: i2c-bus@0x100 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x100 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <3>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c4: i2c-bus@0x140 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x140 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <4>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c5: i2c-bus@0x180 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x180 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <5>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c6: i2c-bus@0x1C0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x1C0 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <6>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c7: i2c-bus@0x300 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x300 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <7>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c8: i2c-bus@0x340 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x340 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <8>; + clock-frequency = <100000>; + status = "okay"; + }; + + i2c9: i2c-bus@0x380 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x380 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <9>; + clock-frequency = <100000>; + status = "disabled"; + }; + + i2c10: i2c-bus@0x3C0 { + reg = <0x380 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <10>; + clock-frequency = <100000>; + status = "disabled"; + }; + + i2c11: i2c-bus@0x400 { + reg = <0x400 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <11>; + clock-frequency = <100000>; + status = "disabled"; + }; + + i2c12: i2c-bus@0x440 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x400 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <12>; + clock-frequency = <100000>; + status = "disabled"; + }; + + i2c13: i2c-bus@0x480 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0x480 0x40>; + compatible = "aspeed,ast2400-i2c-bus"; + bus = <13>; + clock-frequency = <100000>; + status = "disabled"; + }; + + }; + + syscon: syscon@1e6e2000 { + compatible = "aspeed,syscon", "syscon"; + reg = <0x1e6e2000 0x1a8>; + interrupts = <19>; + clocks = <&clk_apb>; + status = "okay"; + }; + + wdt: wdt@1e785000 { + compatible = "aspeed,wdt", "wdt"; + reg = <0x1e785000 0x1c4>; + interrupts = <27>; + clocks = <&clk_apb>; + }; + + rtc: rtc@1e781000 { + compatible = "aspeed,rtc"; + reg = <0x1e781000 0x18>; + }; + + timer: timer@98400000 { + compatible = "aspeed,timer"; + reg = <0x1e782000 0x90>; + // The moxart_timer driver registers only one interrupt + // and assumes it's for timer 1 + //interrupts = <16 17 18 35 36 37 38 39>; + interrupts = <16>; + clocks = <&clk_apb>; + }; + + gpio: gpio@1e780000 { + compatible = "aspeed,ast2400-gpio"; + reg = <0x1e780000 0x1000>; + interrupts = <20>; + }; + + uart1: serial@1e783000 { + compatible = "ns16550a"; + reg = <0x1e783000 0x1000>; + reg-shift = <2>; + interrupts = <9>; + clock-frequency = < 1843200 >; + no-loopback-test; + }; + uart2: serial@1e78d000 { + compatible = "ns16550a"; + reg = <0x1e78d000 0x1000>; + reg-shift = <2>; + interrupts = <32>; + clock-frequency = < 1843200 >; + no-loopback-test; + }; + /* APSS UART */ + uart3: serial@1e78e000 { + compatible = "ns16550a"; + reg = <0x1e78e000 0x1000>; + reg-shift = <2>; + interrupts = <33>; + clock-frequency = < 1843200 >; + no-loopback-test; + }; + + /* Host UART */ + uart4: serial@1e78f000 { + compatible = "ns16550a"; + reg = <0x1e78f000 0x1000>; + reg-shift = <2>; + interrupts = <34>; + clock-frequency = < 1843200 >; + current-speed = < 115200 >; + no-loopback-test; + }; +#if 1 + /* BMC UART */ + uart5: serial@1e784000 { + compatible = "ns16550a"; + reg = <0x1e784000 0x1000>; + reg-shift = <2>; + interrupts = <10>; + clock-frequency = < 1843200 >; + current-speed = < 38400 >; + no-loopback-test; + }; +#endif + + uart6: serial@1e787000 { + compatible = "ns16550a"; + reg = <0x1e787000 0x1000>; + reg-shift = <2>; + interrupts = <10>; + clock-frequency = < 1843200 >; + current-speed = < 38400 >; + no-loopback-test; + }; + }; + }; +}; diff --git a/arch/arm/configs/aspeed_defconfig b/arch/arm/configs/aspeed_defconfig new file mode 100644 index 00000000000000..a83da9163c0d7f --- /dev/null +++ b/arch/arm/configs/aspeed_defconfig @@ -0,0 +1,199 @@ +CONFIG_KERNEL_XZ=y +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_FHANDLE=y +CONFIG_IRQ_DOMAIN_DEBUG=y +CONFIG_LOG_BUF_SHIFT=14 +CONFIG_CGROUPS=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_LBDAF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_IOSCHED_CFQ is not set +# CONFIG_ARCH_MULTI_V7 is not set +CONFIG_ARCH_ASPEED=y +CONFIG_MACH_OPP_PALMETTO_BMC=y +CONFIG_AEABI=y +CONFIG_UACCESS_WITH_MEMCPY=y +# CONFIG_ATAGS is not set +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ATAG_DTB_COMPAT=y +CONFIG_KEXEC=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +# 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_DIAG is not set +# CONFIG_IPV6 is not set +CONFIG_NETFILTER=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_NET_NCSI=y +# CONFIG_WIRELESS is not set +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_PREVENT_FIRMWARE_BUILD is not set +CONFIG_MTD=y +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_BLOCK=y +CONFIG_MTD_PARTITIONED_MASTER=y +CONFIG_MTD_DATAFLASH=y +CONFIG_MTD_NAND=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_GLUEBI=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=4 +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_ASPEED_BT_IPMI_HOST=y +CONFIG_EEPROM_AT24=y +CONFIG_EEPROM_93CX6=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +# CONFIG_SCSI_LOWLEVEL is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_CADENCE is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +CONFIG_FTMAC100=y +CONFIG_FTGMAC100=y +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +CONFIG_MARVELL_PHY=y +CONFIG_DAVICOM_PHY=y +CONFIG_BROADCOM_PHY=y +CONFIG_MICREL_PHY=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT_LEDS is not set +CONFIG_INPUT_POLLDEV=y +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_MOUSEDEV_SCREEN_X=480 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=272 +CONFIG_INPUT_JOYDEV=y +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_QT1070=y +CONFIG_KEYBOARD_GPIO=y +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=y +# CONFIG_SERIO is not set +# CONFIG_VT is not set +CONFIG_LEGACY_PTY_COUNT=4 +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=6 +CONFIG_SERIAL_8250_RUNTIME_UARTS=6 +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_HW_RANDOM=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_GPIO=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_DEBUG_PINCTRL=y +CONFIG_GPIO_ASPEED=y +CONFIG_SENSORS_TMP421=y +CONFIG_WATCHDOG=y +CONFIG_ASPEED_24xx_WATCHDOG=y +CONFIG_SSB=y +CONFIG_FB=y +CONFIG_FB_MODE_HELPERS=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_LCD_CLASS_DEVICE is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_GENERIC is not set +CONFIG_LOGO=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_GADGET=y +CONFIG_USB_G_SERIAL=y +CONFIG_MMC=y +CONFIG_MMC_SPI=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PWM=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_DS1307=y +CONFIG_RTC_DRV_RV3029C2=y +CONFIG_DMADEVICES=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_PWM=y +CONFIG_FIRMWARE_MEMMAP=y +CONFIG_EXT4_FS=y +CONFIG_FANOTIFY=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_SUMMARY=y +CONFIG_SQUASHFS=y +CONFIG_NFS_FS=y +CONFIG_ROOT_NFS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_850=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_UTF8=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_STRIP_ASM_SYMS=y +CONFIG_DEBUG_FS=y +# CONFIG_SCHED_DEBUG is not set +# CONFIG_DEBUG_BUGVERBOSE is not set +# CONFIG_FTRACE is not set +CONFIG_DEBUG_USER=y +CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y +CONFIG_DEBUG_UART_PHYS=0x1e784000 +CONFIG_DEBUG_UART_VIRT=0xe8784000 +# CONFIG_CRYPTO_ECHAINIV is not set +CONFIG_CRYPTO_CTR=y +CONFIG_CRYPTO_GHASH=y +# CONFIG_CRYPTO_HW is not set +CONFIG_CRC_CCITT=y diff --git a/arch/arm/mach-aspeed/Kconfig b/arch/arm/mach-aspeed/Kconfig new file mode 100644 index 00000000000000..5ff74d7df1ef82 --- /dev/null +++ b/arch/arm/mach-aspeed/Kconfig @@ -0,0 +1,29 @@ +menuconfig ARCH_ASPEED + bool "ASpeed BMC SoCs" if ARCH_MULTI_V5 + select CPU_ARM926T + select CLKSRC_MMIO + select GENERIC_IRQ_CHIP + select ARCH_REQUIRE_GPIOLIB + select PINCTRL + select PINCTRL_ASPEED + select I2C_ASPEED + select PHYLIB if NETDEVICES + select MFD_SYSCON + select SRAM + help + Say Y here if you want to run your kernel on hardware with an + ASpeed BMC SoC. Tested on AST2400 + +if ARCH_ASPEED + +config MACH_OPP_PALMETTO_BMC + bool "OpenPower Palmetto" + depends on ARCH_ASPEED + select RTC_DRV_ASPEED + select ASPEED_GPIO + select GPIO_SYSFS + help + Say Y here if you intend to run this kernel on the BMC + of an OpenPower "Palmetto" eval board + +endif diff --git a/arch/arm/mach-aspeed/Makefile b/arch/arm/mach-aspeed/Makefile new file mode 100644 index 00000000000000..3a4f025dd520e9 --- /dev/null +++ b/arch/arm/mach-aspeed/Makefile @@ -0,0 +1,3 @@ +# Object file lists. + +obj-$(CONFIG_ARCH_ASPEED) += aspeed.o diff --git a/arch/arm/mach-aspeed/aspeed.c b/arch/arm/mach-aspeed/aspeed.c new file mode 100644 index 00000000000000..9d106b857fbd03 --- /dev/null +++ b/arch/arm/mach-aspeed/aspeed.c @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// XXX TEMP HACKERY +// +// To be replaced by proper clock, pinmux and syscon drivers operating +// from DT parameters + +static void __init aspeed_dt_init(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); +} + +static const struct of_device_id aspeed_clk_match[] __initconst = { + { + .compatible = "fixed-clock", + .data = of_fixed_clk_setup, + }, + {} +}; + +void __init aspeed_clk_init(void __iomem *base) +{ + of_clk_init(aspeed_clk_match); +} + +#define AST_IO_VA 0xf0000000 +#define AST_IO_PA 0x1e600000 +#define AST_IO_SZ 0x00200000 + +#define AST_IO(__pa) ((void __iomem *)(((__pa) & 0x001fffff) | AST_IO_VA)) + +static struct map_desc aspeed_io_desc[] __initdata __maybe_unused = { + { + .virtual = AST_IO_VA, + .pfn = __phys_to_pfn(AST_IO_PA), + .length = AST_IO_SZ, + .type = MT_DEVICE + }, +}; + + +#define UART_RBR 0 +#define UART_IER 1 +#define UART_FCR 2 +#define UART_LCR 3 +#define UART_MCR 4 +#define UART_LSR 5 +#define UART_MSR 6 +#define UART_SCR 7 +#define UART_THR UART_RBR +#define UART_IIR UART_FCR +#define UART_DLL UART_RBR +#define UART_DLM UART_IER +#define UART_DLAB UART_LCR + +#define LSR_DR 0x01 /* Data ready */ +#define LSR_OE 0x02 /* Overrun */ +#define LSR_PE 0x04 /* Parity error */ +#define LSR_FE 0x08 /* Framing error */ +#define LSR_BI 0x10 /* Break */ +#define LSR_THRE 0x20 /* Xmit holding register empty */ +#define LSR_TEMT 0x40 /* Xmitter empty */ +#define LSR_ERR 0x80 /* Error */ + +#define LCR_DLAB 0x80 + +static void udbg_uart_out(unsigned int reg, u8 data) +{ + writeb(data, AST_IO(0x1E78F000 + reg * 4)); +} + +static u8 udbg_uart_in(unsigned int reg) +{ + return readb(AST_IO(0x1E78F000 + reg * 4)) & 0xff; +} + +static void udbg_uart_setup(unsigned int speed, unsigned int clock) +{ + unsigned int dll, base_bauds; + + if (clock == 0) + clock = 1843200; + if (speed == 0) + speed = 9600; + + base_bauds = clock / 16; + dll = base_bauds / speed; + + printk("DLL=%d\n", dll); + udbg_uart_out(UART_LCR, 0x00); + udbg_uart_out(UART_IER, 0xff); + udbg_uart_out(UART_IER, 0x00); + udbg_uart_out(UART_LCR, LCR_DLAB); + udbg_uart_out(UART_DLL, dll & 0xff); + udbg_uart_out(UART_DLM, dll >> 8); + /* 8 data, 1 stop, no parity */ + udbg_uart_out(UART_LCR, 0x3); + /* RTS/DTR */ + udbg_uart_out(UART_MCR, 0x3); + /* Clear & enable FIFOs */ + udbg_uart_out(UART_FCR, 0x7); +} + +static void udbg_uart_flush(void) +{ + /* wait for idle */ + while ((udbg_uart_in(UART_LSR) & LSR_THRE) == 0) + ; +} + +static void udbg_uart_putc(char c) +{ + if (c == '\n') + udbg_uart_putc('\r'); + udbg_uart_flush(); + udbg_uart_out(UART_THR, c); +} + +#define SCU_PASSWORD 0x1688A8A8 + +static void __init aspeed_init_early(void) +{ + u32 reg; + + // XXX UART stuff to fix to pinmux & co + printk("UART IO MUX...\n"); + writel(0x02010023, AST_IO(AST_BASE_LPC | 0x9c)); + printk("UART PIN MUX...\n"); + writel(SCU_PASSWORD, AST_IO(AST_BASE_SCU)); // UNLOCK SCU + printk("SCU LOCK: %08x\n", readl(AST_IO(AST_BASE_SCU))); + writel(0xcb000000, AST_IO(AST_BASE_SCU | 0x80)); + writel(0x00fff0c0, AST_IO(AST_BASE_SCU | 0x84)); + writel(0x10CC5E80, AST_IO(AST_BASE_SCU | 0x0c)); + + /* We enable the UART clock divisor in the SCU's misc control + * register, as the baud rates in aspeed.dtb all assume that the + * divisor is active + */ + reg = readl(AST_IO(AST_BASE_SCU | 0x2c)); + writel(reg | 0x00001000, AST_IO(AST_BASE_SCU | 0x2c)); + + printk("DONE, MUX=%08x %08x\n", readl(AST_IO(AST_BASE_SCU | 0x80)), + readl(AST_IO(AST_BASE_SCU | 0x84))); + printk("CLOCK_CTRL=%08x\n", readl(AST_IO(AST_BASE_SCU))); + printk("WDT0C=%08x\n", readl(AST_IO(AST_BASE_WDT | 0x0c))); + writel(0, AST_IO(AST_BASE_WDT | 0x0c)); + printk("WDT2C=%08x\n", readl(AST_IO(AST_BASE_WDT | 0x2c))); + writel(0, AST_IO(AST_BASE_WDT | 0x2c)); + udbg_uart_setup(115200,0); + udbg_uart_putc('F'); + udbg_uart_putc('O'); + udbg_uart_putc('O'); + udbg_uart_putc('\n'); +} + +static void __init aspeed_map_io(void) +{ + iotable_init(aspeed_io_desc, ARRAY_SIZE(aspeed_io_desc)); + debug_ll_io_init(); + + printk("SOC Rev: %08x\n", readl(AST_IO(AST_BASE_SCU | 0x7c))); +} + +static const char *const aspeed_dt_match[] __initconst = { + "aspeed,ast2400", + NULL, +}; + +DT_MACHINE_START(aspeed_dt, "ASpeed SoC") + .map_io = aspeed_map_io, + .init_early = aspeed_init_early, + .init_machine = aspeed_dt_init, + .dt_compat = aspeed_dt_match, +MACHINE_END diff --git a/arch/arm/mach-aspeed/include/mach/ast2400.h b/arch/arm/mach-aspeed/include/mach/ast2400.h new file mode 100644 index 00000000000000..1daf85cab5d6a7 --- /dev/null +++ b/arch/arm/mach-aspeed/include/mach/ast2400.h @@ -0,0 +1,117 @@ +/* + * Copyright 2015 IBM Corp. + * + * 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. + * + */ + +#ifndef AST2400_H +#define AST2400_H + +/* Periperhal base addresses */ +#define AST_BASE_LEGACY_SRAM 0x10000000 /* Legacy BMC Static Memory */ +#define AST_BASE_LEGACY_SMC 0x16000000 /* Legacy BMC Static Memory Controller (SMC) */ +#define AST_BASE_APBC 0x1E600000 /* AHB Bus Controller (AHBC) */ +#define AST_BASE_FMC 0x1E620000 /* New BMC Static Memory Controller (FMC) */ +#define AST_BASE_SPI 0x1E630000 /* SPI Memory Controller */ +#define AST_BASE_MIC 0x1E640000 /* Memory Integrity Check Controller (MIC) */ +#define AST_BASE_MAC1 0x1E660000 /* Fast Ethernet MAC Controller #1 (MAC1) */ +#define AST_BASE_MAC2 0x1E680000 /* Fast Ethernet MAC Controller #2 (MAC2) */ +#define AST_BASE_USB2HUB 0x1E6A0000 /* USB2.0 Hub Controller */ +#define AST_BASE_USB2HC 0x1E6A1000 /* USB2.0 Host Controller */ +#define AST_BASE_USB1HC 0x1E6B0000 /* USB1.1 Host Controller */ +#define AST_BASE_VIC 0x1E6C0000 /* Interrupt Controller (VIC) */ +#define AST_BASE_MMC 0x1E6E0000 /* SDRAM Controller (MMC) */ +#define AST_BASE_USB1 0x1E6E1000 /* USB1.1 Controller */ +#define AST_BASE_SCU 0x1E6E2000 /* System Control Unit (SCU) */ +#define AST_BASE_HACE 0x1E6E3000 /* Hash & Crypto Engine (HACE) */ +#define AST_BASE_JTAG 0x1E6E4000 /* JTAG Master */ +#define AST_BASE_CRT 0x1E6E6000 /* Graphics Display Controller (CRT) */ +#define AST_BASE_DMA 0x1E6E7000 /* X-DMA Controller */ +#define AST_BASE_MCTP 0x1E6E8000 /* MCTP Controller */ +#define AST_BASE_ADC 0x1E6E9000 /* ADC Voltage Monitor */ +#define AST_BASE_LPCPLUS 0x1E6EC000 /* LPC+ Controller */ +#define AST_BASE_VIDEO 0x1E700000 /* Video Engine */ +#define AST_BASE_SRAM 0x1E720000 /* 32KB SRAM */ +#define AST_BASE_SDIO 0x1E740000 /* SD/SDIO Controller */ +#define AST_BASE_2D 0x1E760000 /* 2D Engine */ +#define AST_BASE_GPIO 0x1E780000 /* GPIO Controller */ +#define AST_BASE_RTC 0x1E781000 /* Real-Time Clock (RTC) */ +#define AST_BASE_TIMER 0x1E782000 /* Timer #1 ∼ #8 Controller */ +#define AST_BASE_UART1 0x1E783000 /* UART - #1 (LPC UART1) */ +#define AST_BASE_UART5 0x1E784000 /* UART - #5 (BMC Debug) */ +#define AST_BASE_WDT 0x1E785000 /* Watchdog Timer (WDT) */ +#define AST_BASE_PWM 0x1E786000 /* PWM & Fan Tacho Controller */ +#define AST_BASE_VUART 0x1E787000 /* Virtual UART (VUART) */ +#define AST_BASE_PUART 0x1E788000 /* Pass Through UART (PUART) */ +#define AST_BASE_LPC 0x1E789000 /* LPC Controller */ +#define AST_BASE_I2C 0x1E78A000 /* I2C/SMBus Controller */ +#define AST_BASE_PECI 0x1E78B000 /* PECI Controller */ +#define AST_BASE_UART2 0x1E78D000 /* UART - #2 (LPC UART2) */ +#define AST_BASE_UART3 0x1E78E000 /* UART - #3 */ +#define AST_BASE_UART4 0x1E78F000 /* UART - #4 */ + +/* Memory */ +#define AST_BASE_BMCSRAM 0x20000000 /* BMC Static Memory */ +#define AST_BASE_SPIMEM 0x30000000 /* SPI Flash Memory */ +#define AST_BASE_SDRAM 0x40000000 /* SDRAM */ +#define AST_BASE_LPCBRIDGE 0x60000000 /* AHB Bus to LPC Bus Bridge */ +#define AST_BASE_LPCPBRIDGE 0x70000000 /* AHB Bus to LPC+ Bus Bridge */ + +/* BMC interrupt sources */ +#define AST_ID_SDRAM 0 /* SDRAM interrupt */ +#define AST_ID_MIC 1 /* MIC interrupt */ +#define AST_ID_MAC1 2 /* MAC1 interrupt */ +#define AST_ID_MAC2 3 /* MAC2 interrupt */ +#define AST_ID_CRYPTO 4 /* Crypto interrupt */ +#define AST_ID_USB2 5 /* USB 2.0 Hub/Host interrupt */ +#define AST_ID_XDMA 6 /* X-DMA interrupt */ +#define AST_ID_VIDEO 7 /* Video Engine interrupt */ +#define AST_ID_LPC 8 /* LPC interrupt */ +#define AST_ID_UART1 9 /* UART1 interrupt */ +#define AST_ID_UART5 10 /* UART5 interrupt */ +#define AST_ID_11 11 /* Reserved */ +#define AST_ID_I2C 12 /* I2C/SMBus interrupt */ +#define AST_ID_USB1HID 13 /* USB 1.1 HID interrupt */ +#define AST_ID_USB1HOST 14 /* USB 1.1 Host interrupt */ +#define AST_ID_PECI 15 /* PECI interrupt */ +#define AST_ID_TIMER1 16 /* Timer 1 interrupt */ +#define AST_ID_TIMER2 17 /* Timer 2 interrupt */ +#define AST_ID_TIMER3 18 /* Timer 3 interrupt */ +#define AST_ID_SMC 19 /* SMC interrupt */ +#define AST_ID_GPIO 20 /* GPIO interrupt */ +#define AST_ID_SCU 21 /* SCU interrupt */ +#define AST_ID_RTC 22 /* RTC alarm interrupt */ +#define AST_ID_23 23 /* Reserved */ +#define AST_ID_24 24 /* Reserved */ +#define AST_ID_GRAPHICS 25 /* Graphics CRT interrupt */ +#define AST_ID_SDIO 26 /* SD/SDIO interrupt */ +#define AST_ID_WDT 27 /* WDT alarm interrupt */ +#define AST_ID_PWM 28 /* PWM/Tachometer interrupt */ +#define AST_ID_2D 29 /* Graphics 2D interrupt */ +#define AST_ID_WAKEUP 30 /* System Wakeup Control */ +#define AST_ID_ADC 31 /* ADC interrupt */ +#define AST_ID_UART2 32 /* UART2 interrupt */ +#define AST_ID_UART3 33 /* UART3 interrupt */ +#define AST_ID_UART4 34 /* UART4 interrupt */ +#define AST_ID_TIMER4 35 /* Timer 4 interrupt */ +#define AST_ID_TIMER5 36 /* Timer 5 interrupt */ +#define AST_ID_TIMER6 37 /* Timer 6 interrupt */ +#define AST_ID_TIMER38 38 /* Timer 7 interrupt */ +#define AST_ID_TIMER39 39 /* Timer 8 interrupt */ +#define AST_ID_SGPIOMASTER 40 /* SGPIO Master interrupt */ +#define AST_ID_SGPIOSLAVE 41 /* SGPIO Slave interrupt */ +#define AST_ID_MCTP 42 /* MCTP interrupt */ +#define AST_ID_JTAG 43 /* JTAG Master interrupt */ +#define AST_ID_44 44 /* Reserved */ +#define AST_ID_COPRO 45 /* Coprocessor interrupt */ +#define AST_ID_MAILBOX 46 /* MailBox interrupt */ +#define AST_ID_GPIOL1 47 /* GPIOL1 direct input */ +#define AST_ID_GPIOL3 48 /* GPIOL3 direct input */ +#define AST_ID_GPIOM1 49 /* GPIOM1 direct input */ +#define AST_ID_GPIOM3 50 /* GPIOM3 direct input */ + +#endif /*AST2400_H*/ diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 5c00863c3e33ad..471ae0a1ee0eeb 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o obj-$(CONFIG_ARCH_CLPS711X) += clps711x-timer.o obj-$(CONFIG_ARCH_ATLAS7) += timer-atlas7.o obj-$(CONFIG_ARCH_MOXART) += moxart_timer.o +obj-$(CONFIG_ARCH_ASPEED) += moxart_timer.o obj-$(CONFIG_ARCH_MXS) += mxs_timer.o obj-$(CONFIG_CLKSRC_PXA) += pxa_timer.o obj-$(CONFIG_ARCH_PRIMA2) += timer-prima2.o diff --git a/drivers/clocksource/moxart_timer.c b/drivers/clocksource/moxart_timer.c index 19857af651c1cf..bd8dbf388e24e7 100644 --- a/drivers/clocksource/moxart_timer.c +++ b/drivers/clocksource/moxart_timer.c @@ -36,45 +36,66 @@ #define TIMER_INTR_MASK 0x38 /* - * TIMER_CR flags: + * Moxart TIMER_CR flags: * * TIMEREG_CR_*_CLOCK 0: PCLK, 1: EXT1CLK * TIMEREG_CR_*_INT overflow interrupt enable bit */ -#define TIMEREG_CR_1_ENABLE BIT(0) -#define TIMEREG_CR_1_CLOCK BIT(1) -#define TIMEREG_CR_1_INT BIT(2) -#define TIMEREG_CR_2_ENABLE BIT(3) -#define TIMEREG_CR_2_CLOCK BIT(4) -#define TIMEREG_CR_2_INT BIT(5) -#define TIMEREG_CR_3_ENABLE BIT(6) -#define TIMEREG_CR_3_CLOCK BIT(7) -#define TIMEREG_CR_3_INT BIT(8) -#define TIMEREG_CR_COUNT_UP BIT(9) - -#define TIMER1_ENABLE (TIMEREG_CR_2_ENABLE | TIMEREG_CR_1_ENABLE) -#define TIMER1_DISABLE (TIMEREG_CR_2_ENABLE) +#define MOXART_CR_1_ENABLE BIT(0) +#define MOXART_CR_1_CLOCK BIT(1) +#define MOXART_CR_1_INT BIT(2) +#define MOXART_CR_2_ENABLE BIT(3) +#define MOXART_CR_2_CLOCK BIT(4) +#define MOXART_CR_2_INT BIT(5) +#define MOXART_CR_3_ENABLE BIT(6) +#define MOXART_CR_3_CLOCK BIT(7) +#define MOXART_CR_3_INT BIT(8) +#define MOXART_CR_COUNT_UP BIT(9) + +#define MOXART_TIMER1_ENABLE (MOXART_CR_2_ENABLE | MOXART_CR_1_ENABLE) +#define MOXART_TIMER1_DISABLE (MOXART_CR_2_ENABLE) + +/* + * The ASpeed variant of the IP block has a different layout + * for the control register + */ +#define ASPEED_CR_1_ENABLE BIT(0) +#define ASPEED_CR_1_CLOCK BIT(1) +#define ASPEED_CR_1_INT BIT(2) +#define ASPEED_CR_2_ENABLE BIT(4) +#define ASPEED_CR_2_CLOCK BIT(5) +#define ASPEED_CR_2_INT BIT(6) +#define ASPEED_CR_3_ENABLE BIT(8) +#define ASPEED_CR_3_CLOCK BIT(9) +#define ASPEED_CR_3_INT BIT(10) + +#define ASPEED_TIMER1_ENABLE (ASPEED_CR_2_ENABLE | ASPEED_CR_1_ENABLE) +#define ASPEED_TIMER1_DISABLE (ASPEED_CR_2_ENABLE) static void __iomem *base; static unsigned int clock_count_per_tick; +static unsigned int t1_disable_val, t1_enable_val; static int moxart_shutdown(struct clock_event_device *evt) { - writel(TIMER1_DISABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); return 0; } static int moxart_set_oneshot(struct clock_event_device *evt) { - writel(TIMER1_DISABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); writel(~0, base + TIMER1_BASE + REG_LOAD); return 0; } static int moxart_set_periodic(struct clock_event_device *evt) { + writel(t1_disable_val, base + TIMER_CR); writel(clock_count_per_tick, base + TIMER1_BASE + REG_LOAD); - writel(TIMER1_ENABLE, base + TIMER_CR); + writel(0, base + TIMER1_BASE + REG_MATCH1); + writel(t1_enable_val, base + TIMER_CR); + return 0; } @@ -83,12 +104,12 @@ static int moxart_clkevt_next_event(unsigned long cycles, { u32 u; - writel(TIMER1_DISABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); u = readl(base + TIMER1_BASE + REG_COUNT) - cycles; writel(u, base + TIMER1_BASE + REG_MATCH1); - writel(TIMER1_ENABLE, base + TIMER_CR); + writel(t1_enable_val, base + TIMER_CR); return 0; } @@ -119,7 +140,7 @@ static struct irqaction moxart_timer_irq = { .dev_id = &moxart_clockevent, }; -static void __init moxart_timer_init(struct device_node *node) +static void __init __moxart_timer_init(struct device_node *node) { int ret, irq; unsigned long pclk; @@ -150,8 +171,21 @@ static void __init moxart_timer_init(struct device_node *node) clock_count_per_tick = DIV_ROUND_CLOSEST(pclk, HZ); + /* Clean up match registers we will still get an occasional interrupt + * from timer 2 but I haven't enabled it for now + */ + writel(0, base + TIMER1_BASE + REG_MATCH1); + writel(0, base + TIMER1_BASE + REG_MATCH2); + writel(0, base + TIMER2_BASE + REG_MATCH1); + writel(0, base + TIMER2_BASE + REG_MATCH2); + + /* Start timer 2 rolling as our main wall clock source, keep timer 1 + * disabled + */ + writel(0, base + TIMER_CR); writel(~0, base + TIMER2_BASE + REG_LOAD); - writel(TIMEREG_CR_2_ENABLE, base + TIMER_CR); + writel(t1_disable_val, base + TIMER_CR); + moxart_clockevent.cpumask = cpumask_of(0); moxart_clockevent.irq = irq; @@ -165,4 +199,20 @@ static void __init moxart_timer_init(struct device_node *node) clockevents_config_and_register(&moxart_clockevent, pclk, 0x4, 0xfffffffe); } + +static void __init moxart_timer_init(struct device_node *node) +{ + t1_enable_val = MOXART_TIMER1_ENABLE; + t1_disable_val = MOXART_TIMER1_DISABLE; + __moxart_timer_init(node); +} + +static void __init aspeed_timer_init(struct device_node *node) +{ + t1_enable_val = ASPEED_TIMER1_ENABLE; + t1_disable_val = ASPEED_TIMER1_DISABLE; + __moxart_timer_init(node); +} + CLOCKSOURCE_OF_DECLARE(moxart, "moxa,moxart-timer", moxart_timer_init); +CLOCKSOURCE_OF_DECLARE(aspeed, "aspeed,timer", aspeed_timer_init); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 8949b3f6f74d20..5a71acd4d1afe0 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -119,6 +119,13 @@ config GPIO_ALTERA If driver is built as a module it will be called gpio-altera. +config GPIO_ASPEED + bool "Aspeed AST2400 GPIO support" + depends on ARCH_ASPEED && OF + select GENERIC_IRQ_CHIP + help + Say Y here to support Aspeed AST2400 GPIO. + config GPIO_BCM_KONA bool "Broadcom Kona GPIO" depends on OF_GPIO && (ARCH_BCM_MOBILE || COMPILE_TEST) @@ -968,7 +975,6 @@ config GPIO_SODAVILLE select GENERIC_IRQ_CHIP help Say Y here to support Intel Sodaville GPIO. - endmenu menu "SPI GPIO expanders" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index f79a7c482a993c..0c0bd4a9a3542a 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_GPIO_ALTERA) += gpio-altera.o obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o obj-$(CONFIG_ATH79) += gpio-ath79.o +obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c new file mode 100644 index 00000000000000..87e159a75e8fc2 --- /dev/null +++ b/drivers/gpio/gpio-aspeed.c @@ -0,0 +1,436 @@ +/* + * Copyright 2015 IBM Corp. + * + * Joel Stanley + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct aspeed_gpio { + struct gpio_chip chip; + spinlock_t lock; + void __iomem *base; + int irq; + struct irq_chip irq_chip; + struct irq_domain *irq_domain; +}; + +struct aspeed_gpio_bank { + uint16_t val_regs; + uint16_t irq_regs; + const char names[4]; +}; + +static const struct aspeed_gpio_bank aspeed_gpio_banks[] = { + { + .val_regs = 0x0000, + .irq_regs = 0x0008, + .names = { 'A', 'B', 'C', 'D' }, + }, + { + .val_regs = 0x0020, + .irq_regs = 0x0028, + .names = { 'E', 'F', 'G', 'H' }, + }, + { + .val_regs = 0x0070, + .irq_regs = 0x0098, + .names = { 'I', 'J', 'K', 'L' }, + }, + { + .val_regs = 0x0078, + .irq_regs = 0x00e8, + .names = { 'M', 'N', 'O', 'P' }, + }, + { + .val_regs = 0x0080, + .irq_regs = 0x0118, + .names = { 'Q', 'R', 'S', 'T' }, + }, + { + .val_regs = 0x0088, + .irq_regs = 0x0148, + .names = { 'U', 'V', 'W', 'X' }, + }, +}; + +#define GPIO_BANK(x) ((x) >> 5) +#define GPIO_OFFSET(x) ((x) & 0x1f) +#define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) + +#define GPIO_DATA 0x00 +#define GPIO_DIR 0x04 + +#define GPIO_IRQ_ENABLE 0x00 +#define GPIO_IRQ_TYPE0 0x04 +#define GPIO_IRQ_TYPE1 0x08 +#define GPIO_IRQ_TYPE2 0x0c +#define GPIO_IRQ_STATUS 0x10 + +static inline struct aspeed_gpio *to_aspeed_gpio(struct gpio_chip *chip) +{ + return container_of(chip, struct aspeed_gpio, chip); +} + +static const struct aspeed_gpio_bank *to_bank(unsigned int offset) +{ + unsigned int bank = GPIO_BANK(offset); + WARN_ON(bank > ARRAY_SIZE(aspeed_gpio_banks)); + return &aspeed_gpio_banks[bank]; +} + +static void *bank_val_reg(struct aspeed_gpio *gpio, + const struct aspeed_gpio_bank *bank, + unsigned int reg) +{ + return gpio->base + bank->val_regs + reg; +} + +static void *bank_irq_reg(struct aspeed_gpio *gpio, + const struct aspeed_gpio_bank *bank, + unsigned int reg) +{ + return gpio->base + bank->irq_regs + reg; +} + +static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + + return !!(ioread32(bank_val_reg(gpio, bank, GPIO_DATA)) + & GPIO_BIT(offset)); +} + +static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, + int val) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(bank_val_reg(gpio, bank, GPIO_DATA)); + if (val) + reg |= GPIO_BIT(offset); + else + reg &= ~GPIO_BIT(offset); + + iowrite32(reg, bank_val_reg(gpio, bank, GPIO_DATA)); + + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)); + iowrite32(reg & ~GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR)); + + spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} + +static int aspeed_gpio_dir_out(struct gpio_chip *gc, + unsigned int offset, int val) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(gc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)); + iowrite32(reg | GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR)); + + spin_unlock_irqrestore(&gpio->lock, flags); + + return 0; +} + +static inline int irqd_to_aspeed_gpio_data(struct irq_data *d, + struct aspeed_gpio **gpio, + const struct aspeed_gpio_bank **bank, + u32 *bit) +{ + int offset; + + offset = irqd_to_hwirq(d); + + *gpio = irq_data_get_irq_chip_data(d); + *bank = to_bank(offset); + *bit = GPIO_BIT(offset); + + return 0; +} + +static void aspeed_gpio_irq_ack(struct irq_data *d) +{ + const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; + unsigned long flags; + void *status_addr; + u32 bit; + int rc; + + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + if (rc) + return; + + status_addr = bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS); + + spin_lock_irqsave(&gpio->lock, flags); + iowrite32(bit, status_addr); + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void __aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) +{ + const struct aspeed_gpio_bank *bank; + struct aspeed_gpio *gpio; + unsigned long flags; + u32 reg, bit; + void *addr; + int rc; + + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + if (rc) + return; + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_ENABLE); + + spin_lock_irqsave(&gpio->lock, flags); + + reg = ioread32(addr); + if (set) + reg |= bit; + else + reg &= bit; + iowrite32(reg, addr); + + spin_unlock_irqrestore(&gpio->lock, flags); +} + +static void aspeed_gpio_irq_mask(struct irq_data *d) +{ + __aspeed_gpio_irq_set_mask(d, false); +} + +static void aspeed_gpio_irq_unmask(struct irq_data *d) +{ + __aspeed_gpio_irq_set_mask(d, true); +} + +static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) +{ + u32 type0, type1, type2, bit, reg; + const struct aspeed_gpio_bank *bank; + irq_flow_handler_t handler; + struct aspeed_gpio *gpio; + unsigned long flags; + void *addr; + int rc; + + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + if (rc) + return -EINVAL; + + type0 = type1 = type2 = 0; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_BOTH: + type2 |= bit; + case IRQ_TYPE_EDGE_RISING: + type0 |= bit; + case IRQ_TYPE_EDGE_FALLING: + handler = handle_edge_irq; + break; + case IRQ_TYPE_LEVEL_HIGH: + type0 |= bit; + case IRQ_TYPE_LEVEL_LOW: + type1 |= bit; + handler = handle_level_irq; + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&gpio->lock, flags); + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0); + reg = ioread32(addr); + reg = (reg & ~bit) | type0; + iowrite32(reg, addr); + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1); + reg = ioread32(addr); + reg = (reg & ~bit) | type1; + iowrite32(reg, addr); + + addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2); + reg = ioread32(addr); + reg = (reg & ~bit) | type2; + iowrite32(reg, addr); + + spin_unlock_irqrestore(&gpio->lock, flags); + + irq_set_handler_locked(d, handler); + + return 0; +} + +static void aspeed_gpio_irq_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct aspeed_gpio *gpio = irq_desc_get_chip_data(desc); + unsigned int i, p, girq; + unsigned long reg; + + chained_irq_enter(chip, desc); + + for (i = 0; i < ARRAY_SIZE(aspeed_gpio_banks); i++) { + const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; + + reg = ioread32(bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS)); + + for_each_set_bit(p, ®, 32) { + girq = irq_find_mapping(gpio->irq_domain, i * 32 + p); + generic_handle_irq(girq); + } + + } + + chained_irq_exit(chip, desc); +} + +static struct irq_chip aspeed_gpio_irqchip = { + .name = "aspeed-gpio", + .irq_ack = aspeed_gpio_irq_ack, + .irq_mask = aspeed_gpio_irq_mask, + .irq_unmask = aspeed_gpio_irq_unmask, + .irq_set_type = aspeed_gpio_set_type, +}; + +static int aspeed_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct aspeed_gpio *gpio = to_aspeed_gpio(chip); + return irq_find_mapping(gpio->irq_domain, offset); +} + +static void aspeed_gpio_setup_irqs(struct aspeed_gpio *gpio, + struct platform_device *pdev) +{ + int i, irq; + + /* request our upstream IRQ */ + gpio->irq = platform_get_irq(pdev, 0); + if (gpio->irq < 0) + return; + + /* establish our irq domain to provide IRQs for each extended bank */ + gpio->irq_domain = irq_domain_add_linear(pdev->dev.of_node, + gpio->chip.ngpio, &irq_domain_simple_ops, NULL); + if (!gpio->irq_domain) + return; + + for (i = 0; i < gpio->chip.ngpio; i++) { + irq = irq_create_mapping(gpio->irq_domain, i); + irq_set_chip_data(irq, gpio); + irq_set_chip_and_handler(irq, &aspeed_gpio_irqchip, + handle_simple_irq); + irq_set_probe(irq); + } + + irq_set_chained_handler_and_data(gpio->irq, + aspeed_gpio_irq_handler, gpio); +} + + +static int __init aspeed_gpio_probe(struct platform_device *pdev) +{ + struct aspeed_gpio *gpio; + struct resource *res; + int rc; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + gpio->base = devm_ioremap_resource(&pdev->dev, res); + if (!gpio->base) + return -ENOMEM; + + spin_lock_init(&gpio->lock); + + gpio->chip.ngpio = ARRAY_SIZE(aspeed_gpio_banks) * 32; + + gpio->chip.dev = &pdev->dev; + gpio->chip.direction_input = aspeed_gpio_dir_in; + gpio->chip.direction_output = aspeed_gpio_dir_out; + gpio->chip.get = aspeed_gpio_get; + gpio->chip.set = aspeed_gpio_set; + gpio->chip.to_irq = aspeed_gpio_to_irq; + gpio->chip.label = dev_name(&pdev->dev); + gpio->chip.base = -1; + + platform_set_drvdata(pdev, gpio); + + rc = gpiochip_add(&gpio->chip); + if (rc < 0) + return rc; + + aspeed_gpio_setup_irqs(gpio, pdev); + + return 0; +} + +static int aspeed_gpio_remove(struct platform_device *pdev) +{ + struct aspeed_gpio *gpio = platform_get_drvdata(pdev); + + gpiochip_remove(&gpio->chip); + return 0; +} + +static const struct of_device_id aspeed_gpio_of_table[] = { + { .compatible = "aspeed,ast2400-gpio" }, + {} +}; +MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table); + +static struct platform_driver aspeed_gpio_driver = { + .remove = aspeed_gpio_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_gpio_of_table, + }, +}; + +module_platform_driver_probe(aspeed_gpio_driver, aspeed_gpio_probe); + +MODULE_DESCRIPTION("Aspeed AST2400 GPIO Driver"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 08b86178e8fba9..c2800bf04773c7 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -974,6 +974,17 @@ config I2C_RCAR This driver can also be built as a module. If so, the module will be called i2c-rcar. +config I2C_ASPEED + tristate "Aspeed AST2xxx SoC I2C Controller" + depends on ARCH_ASPEED + select I2C_SLAVE + help + If you say yes to this option, support will be included for the + Aspeed AST2xxx SoC I2C controller. + + This driver can also be built as a module. If so, the module + will be called i2c-aspeed. + comment "External I2C/SMBus adapter drivers" config I2C_DIOLAN_U2C diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 6df3b303bd092b..619c0f2bd353d9 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -94,6 +94,7 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o obj-$(CONFIG_I2C_XLR) += i2c-xlr.o obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o +obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c new file mode 100644 index 00000000000000..b68f426c5c2f4e --- /dev/null +++ b/drivers/i2c/busses/i2c-aspeed.c @@ -0,0 +1,886 @@ +/* + * I2C adapter for the ASPEED I2C bus access. + * + * Copyright (C) 2012-2020 ASPEED Technology Inc. + * Copyright 2015 IBM Corporation + * + * 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. + * + * History: + * 2012.07.26: Initial version [Ryan Chen] + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#if defined(CONFIG_COLDFIRE) +#include +#include +#else +//#include +//#include +#endif + +#define BYTE_MODE 0 +#define BUFF_MODE 1 +#define DEC_DMA_MODE 2 +#define INC_DMA_MODE 3 + +/* I2C Register */ +#define I2C_FUN_CTRL_REG 0x00 +#define I2C_AC_TIMING_REG1 0x04 +#define I2C_AC_TIMING_REG2 0x08 +#define I2C_INTR_CTRL_REG 0x0c +#define I2C_INTR_STS_REG 0x10 +#define I2C_CMD_REG 0x14 +#define I2C_DEV_ADDR_REG 0x18 +#define I2C_BUF_CTRL_REG 0x1c +#define I2C_BYTE_BUF_REG 0x20 +#define I2C_DMA_BASE_REG 0x24 +#define I2C_DMA_LEN_REG 0x28 + +#define AST_I2C_DMA_SIZE 0 +#define AST_I2C_PAGE_SIZE 256 +#define MASTER_XFER_MODE BUFF_MODE +#define SLAVE_XFER_MODE BYTE_MODE +#define NUM_BUS 14 + +/*AST I2C Register Definition */ +// if defined(AST_SOC_G4) +#define AST_I2C_POOL_BUFF_2048 +#define AST_I2C_GLOBAL_REG 0x00 +#define AST_I2C_DEVICE1 0x40 +#define AST_I2C_DEVICE2 0x80 +#define AST_I2C_DEVICE3 0xc0 +#define AST_I2C_DEVICE4 0x100 +#define AST_I2C_DEVICE5 0x140 +#define AST_I2C_DEVICE6 0x180 +#define AST_I2C_DEVICE7 0x1c0 +#define AST_I2C_BUFFER_POOL2 0x200 +#define AST_I2C_DEVICE8 0x300 +#define AST_I2C_DEVICE9 0x340 +#define AST_I2C_DEVICE10 0x380 +#define AST_I2C_DEVICE11 0x3c0 +#define AST_I2C_DEVICE12 0x400 +#define AST_I2C_DEVICE13 0x440 +#define AST_I2C_DEVICE14 0x480 +#define AST_I2C_BUFFER_POOL1 0x800 + +/* Gloable Register Definition */ +/* 0x00 : I2C Interrupt Status Register */ +/* 0x08 : I2C Interrupt Target Assignment */ + +/* Device Register Definition */ +/* 0x00 : I2CD Function Control Register */ +#define AST_I2CD_BUFF_SEL_MASK (0x7 << 20) +#define AST_I2CD_BUFF_SEL(x) (x << 20) // page 0 ~ 7 +#define AST_I2CD_M_SDA_LOCK_EN (0x1 << 16) +#define AST_I2CD_MULTI_MASTER_DIS (0x1 << 15) +#define AST_I2CD_M_SCL_DRIVE_EN (0x1 << 14) +#define AST_I2CD_MSB_STS (0x1 << 9) +#define AST_I2CD_SDA_DRIVE_1T_EN (0x1 << 8) +#define AST_I2CD_M_SDA_DRIVE_1T_EN (0x1 << 7) +#define AST_I2CD_M_HIGH_SPEED_EN (0x1 << 6) +#define AST_I2CD_DEF_ADDR_EN (0x1 << 5) +#define AST_I2CD_DEF_ALERT_EN (0x1 << 4) +#define AST_I2CD_DEF_ARP_EN (0x1 << 3) +#define AST_I2CD_DEF_GCALL_EN (0x1 << 2) +#define AST_I2CD_SLAVE_EN (0x1 << 1) +#define AST_I2CD_MASTER_EN (0x1 ) + +/* 0x04 : I2CD Clock and AC Timing Control Register #1 */ +#define AST_I2CD_tBUF (0x1 << 28) // 0~7 +#define AST_I2CD_tHDSTA (0x1 << 24) // 0~7 +#define AST_I2CD_tACST (0x1 << 20) // 0~7 +#define AST_I2CD_tCKHIGH (0x1 << 16) // 0~7 +#define AST_I2CD_tCKLOW (0x1 << 12) // 0~7 +#define AST_I2CD_tHDDAT (0x1 << 10) // 0~7 +#define AST_I2CD_CLK_TO_BASE_DIV (0x1 << 8) // 0~3 +#define AST_I2CD_CLK_BASE_DIV (0x1 ) // 0~0xf + +/* 0x08 : I2CD Clock and AC Timing Control Register #2 */ +#define AST_I2CD_tTIMEOUT (0x1 ) // 0~7 +#define AST_NO_TIMEOUT_CTRL 0x0 + + +/* 0x0c : I2CD Interrupt Control Register & + * 0x10 : I2CD Interrupt Status Register + * + * These share bit definitions, so use the same values for the enable & + * status bits. + */ +#define AST_I2CD_INTR_SDA_DL_TIMEOUT (0x1 << 14) +#define AST_I2CD_INTR_BUS_RECOVER_DONE (0x1 << 13) +#define AST_I2CD_INTR_SMBUS_ALERT (0x1 << 12) +#define AST_I2CD_INTR_SMBUS_ARP_ADDR (0x1 << 11) +#define AST_I2CD_INTR_SMBUS_DEV_ALERT_ADDR (0x1 << 10) +#define AST_I2CD_INTR_SMBUS_DEF_ADDR (0x1 << 9) +#define AST_I2CD_INTR_GCALL_ADDR (0x1 << 8) +#define AST_I2CD_INTR_SLAVE_MATCH (0x1 << 7) +#define AST_I2CD_INTR_SCL_TIMEOUT (0x1 << 6) +#define AST_I2CD_INTR_ABNORMAL (0x1 << 5) +#define AST_I2CD_INTR_NORMAL_STOP (0x1 << 4) +#define AST_I2CD_INTR_ARBIT_LOSS (0x1 << 3) +#define AST_I2CD_INTR_RX_DONE (0x1 << 2) +#define AST_I2CD_INTR_TX_NAK (0x1 << 1) +#define AST_I2CD_INTR_TX_ACK (0x1 << 0) + +/* 0x14 : I2CD Command/Status Register */ +#define AST_I2CD_SDA_OE (0x1 << 28) +#define AST_I2CD_SDA_O (0x1 << 27) +#define AST_I2CD_SCL_OE (0x1 << 26) +#define AST_I2CD_SCL_O (0x1 << 25) +#define AST_I2CD_TX_TIMING (0x1 << 24) // 0 ~3 +#define AST_I2CD_TX_STATUS (0x1 << 23) +// Tx State Machine +#define AST_I2CD_IDLE 0x0 +#define AST_I2CD_MACTIVE 0x8 +#define AST_I2CD_MSTART 0x9 +#define AST_I2CD_MSTARTR 0xa +#define AST_I2CD_MSTOP 0xb +#define AST_I2CD_MTXD 0xc +#define AST_I2CD_MRXACK 0xd +#define AST_I2CD_MRXD 0xe +#define AST_I2CD_MTXACK 0xf +#define AST_I2CD_SWAIT 0x1 +#define AST_I2CD_SRXD 0x4 +#define AST_I2CD_STXACK 0x5 +#define AST_I2CD_STXD 0x6 +#define AST_I2CD_SRXACK 0x7 +#define AST_I2CD_RECOVER 0x3 + +#define AST_I2CD_SCL_LINE_STS (0x1 << 18) +#define AST_I2CD_SDA_LINE_STS (0x1 << 17) +#define AST_I2CD_BUS_BUSY_STS (0x1 << 16) +#define AST_I2CD_SDA_OE_OUT_DIR (0x1 << 15) +#define AST_I2CD_SDA_O_OUT_DIR (0x1 << 14) +#define AST_I2CD_SCL_OE_OUT_DIR (0x1 << 13) +#define AST_I2CD_SCL_O_OUT_DIR (0x1 << 12) +#define AST_I2CD_BUS_RECOVER_CMD_EN (0x1 << 11) +#define AST_I2CD_S_ALT_EN (0x1 << 10) +// 0 : DMA Buffer, 1: Pool Buffer +//AST1070 DMA register +#define AST_I2CD_RX_DMA_ENABLE (0x1 << 9) +#define AST_I2CD_TX_DMA_ENABLE (0x1 << 8) + +/* Command Bit */ +#define AST_I2CD_RX_BUFF_ENABLE (0x1 << 7) +#define AST_I2CD_TX_BUFF_ENABLE (0x1 << 6) +#define AST_I2CD_M_STOP_CMD (0x1 << 5) +#define AST_I2CD_M_S_RX_CMD_LAST (0x1 << 4) +#define AST_I2CD_M_RX_CMD (0x1 << 3) +#define AST_I2CD_S_TX_CMD (0x1 << 2) +#define AST_I2CD_M_TX_CMD (0x1 << 1) +#define AST_I2CD_M_START_CMD (0x1 ) + +/* 0x18 : I2CD Slave Device Address Register */ + +/* 0x1C : I2CD Pool Buffer Control Register */ +#define AST_I2CD_RX_BUF_ADDR_GET(x) ((x>> 24)& 0xff) +#define AST_I2CD_RX_BUF_END_ADDR_SET(x) (x << 16) +#define AST_I2CD_TX_DATA_BUF_END_SET(x) ((x&0xff) << 8) +#define AST_I2CD_TX_DATA_BUF_GET(x) ((x >>8) & 0xff) +#define AST_I2CD_BUF_BASE_ADDR_SET(x) (x & 0x3f) + +/* 0x20 : I2CD Transmit/Receive Byte Buffer Register */ +#define AST_I2CD_GET_MODE(x) ((x >> 8) & 0x1) + +#define AST_I2CD_RX_BYTE_BUFFER (0xff << 8) +#define AST_I2CD_TX_BYTE_BUFFER (0xff ) + +//1. usage flag , 2 size, 3. request address +/* Use platform_data instead of module parameters */ +/* Fast Mode = 400 kHz, Standard = 100 kHz */ +//static int clock = 100; /* Default: 100 kHz */ + +#define AST_I2CD_CMDS (AST_I2CD_BUS_RECOVER_CMD_EN | \ + AST_I2CD_M_STOP_CMD | \ + AST_I2CD_M_S_RX_CMD_LAST | \ + AST_I2CD_M_RX_CMD | \ + AST_I2CD_M_TX_CMD | \ + AST_I2CD_M_START_CMD) + + +struct aspeed_i2c_bus { + /* TODO: find a better way to do this */ + struct ast_i2c_dev *i2c_dev; + struct device *dev; + + void __iomem *base; /* virtual */ + u32 state; //I2C xfer mode state matchine + struct i2c_adapter adap; + u32 bus_clk; + + /* i2c transfer state. this is accessed from both process and IRQ + * context, so is protected by cmd_lock */ + spinlock_t cmd_lock; + bool send_start; + bool send_stop; /* last message of an xfer? */ + bool query_len; + struct i2c_msg *msg; /* current tx/rx message */ + int msg_pos; /* current byte position in message */ + + struct completion cmd_complete; + u32 cmd_sent; + u32 cmd_pending; + u32 cmd_err; +}; + +struct ast_i2c_dev { + struct device *dev; + void __iomem *reg_gr; + struct clk *pclk; + struct aspeed_i2c_bus buses[14]; + int irq; //I2C IRQ number +}; + +static inline void ast_i2c_write(struct aspeed_i2c_bus *bus, u32 val, u32 reg) +{ + writel(val, bus->base + reg); +} + +static inline u32 ast_i2c_read(struct aspeed_i2c_bus *bus, u32 reg) +{ + return readl(bus->base + reg); +} + +static u32 select_i2c_clock(struct aspeed_i2c_bus *bus) +{ + unsigned int inc = 0, div, divider_ratio; + u32 SCL_Low, SCL_High, data; + + divider_ratio = clk_get_rate(bus->i2c_dev->pclk) / bus->bus_clk; + for (div = 0; divider_ratio >= 16; div++) { + inc |= (divider_ratio & 1); + divider_ratio >>= 1; + } + divider_ratio += inc; + SCL_Low = (divider_ratio >> 1) - 1; + SCL_High = divider_ratio - SCL_Low - 2; + data = 0x77700300 | (SCL_High << 16) | (SCL_Low << 12) | div; + return data; +} + +static void ast_i2c_dev_init(struct aspeed_i2c_bus *bus) +{ + /* reset device: disable master & slave functions */ + ast_i2c_write(bus, 0, I2C_FUN_CTRL_REG); + + dev_dbg(bus->dev, "bus_clk %u, pclk %lu\n", + bus->bus_clk, clk_get_rate(bus->i2c_dev->pclk)); + + /* Set AC Timing */ + if(bus->bus_clk / 1000 > 400) { + ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) | + AST_I2CD_M_HIGH_SPEED_EN | + AST_I2CD_M_SDA_DRIVE_1T_EN | + AST_I2CD_SDA_DRIVE_1T_EN, + I2C_FUN_CTRL_REG); + + ast_i2c_write(bus, 0x3, I2C_AC_TIMING_REG2); + ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1); + } else { + ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1); + ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, I2C_AC_TIMING_REG2); + } + + dev_dbg(bus->dev, "reg1: %x, reg2: %x, fun_ctrl: %x\n", + ast_i2c_read(bus, I2C_AC_TIMING_REG1), + ast_i2c_read(bus, I2C_AC_TIMING_REG2), + ast_i2c_read(bus, I2C_FUN_CTRL_REG)); + + /* Enable Master Mode */ + ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) + | AST_I2CD_MASTER_EN, I2C_FUN_CTRL_REG); + + + /* Set interrupt generation of I2C controller */ + ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT | + AST_I2CD_INTR_BUS_RECOVER_DONE | + AST_I2CD_INTR_SMBUS_ALERT | + AST_I2CD_INTR_SCL_TIMEOUT | + AST_I2CD_INTR_ABNORMAL | + AST_I2CD_INTR_NORMAL_STOP | + AST_I2CD_INTR_ARBIT_LOSS | + AST_I2CD_INTR_RX_DONE | + AST_I2CD_INTR_TX_NAK | + AST_I2CD_INTR_TX_ACK, + I2C_INTR_CTRL_REG); + +} + +static void ast_i2c_issue_cmd(struct aspeed_i2c_bus *bus, u32 cmd) +{ + dev_dbg(bus->dev, "issuing cmd: %x\n", cmd); + bus->cmd_err = 0; + bus->cmd_sent = bus->cmd_pending = cmd & AST_I2CD_CMDS; + ast_i2c_write(bus, cmd, I2C_CMD_REG); +} + +static int ast_i2c_issue_oob_command(struct aspeed_i2c_bus *bus, u32 cmd) +{ + spin_lock_irq(&bus->cmd_lock); + init_completion(&bus->cmd_complete); + ast_i2c_issue_cmd(bus, cmd); + spin_unlock_irq(&bus->cmd_lock); + return wait_for_completion_interruptible_timeout(&bus->cmd_complete, + bus->adap.timeout*HZ); +} + +static u8 ast_i2c_bus_error_recover(struct aspeed_i2c_bus *bus) +{ + unsigned long flags; + u32 sts; + int r; + u32 i = 0; + + //Check 0x14's SDA and SCL status + sts = ast_i2c_read(bus,I2C_CMD_REG); + + if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) { + //Means bus is idle. + dev_dbg(bus->dev, + "I2C bus is idle. I2C slave doesn't exist?!\n"); + return -1; + } + + dev_dbg(bus->dev, "I2C bus hung (status %x), attempting recovery\n", + sts); + + if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) { + //if SDA == 1 and SCL == 0, it means the master is locking the bus. + //Send a stop command to unlock the bus. + dev_dbg(bus->dev, "I2C's master is locking the bus, try to stop it.\n"); + + init_completion(&bus->cmd_complete); + + ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, I2C_CMD_REG); + + r = wait_for_completion_interruptible_timeout(&bus->cmd_complete, + bus->adap.timeout*HZ); + + if (bus->cmd_err) { + dev_dbg(bus->dev, "recovery error \n"); + return -1; + } + + if (r == 0) { + dev_dbg(bus->dev, "recovery timed out\n"); + return -1; + } else { + dev_dbg(bus->dev, "Recovery successfully\n"); + return 0; + } + + } else if (!(sts & AST_I2CD_SDA_LINE_STS)) { + //else if SDA == 0, the device is dead. We need to reset the bus + //And do the recovery command. + dev_dbg(bus->dev, "I2C's slave is dead, try to recover it\n"); + for (i = 0; i < 2; i++) { + ast_i2c_dev_init(bus); + ast_i2c_issue_oob_command(bus, + AST_I2CD_BUS_RECOVER_CMD_EN); + if (bus->cmd_err != 0) { + dev_dbg(bus->dev, "ERROR!! Failed to do recovery command(0x%08x)\n", bus->cmd_err); + return -1; + } + //Check 0x14's SDA and SCL status + sts = ast_i2c_read(bus,I2C_CMD_REG); + if (sts & AST_I2CD_SDA_LINE_STS) //Recover OK + break; + } + if (i == 2) { + dev_dbg(bus->dev, "ERROR!! recover failed\n"); + return -1; + } + } else { + dev_dbg(bus->dev, "Don't know how to handle this case?!\n"); + return -1; + } + dev_dbg(bus->dev, "Recovery successfully\n"); + return 0; +} + +static int ast_i2c_wait_bus_not_busy(struct aspeed_i2c_bus *bus) +{ + int timeout = 2; //TODO number + + while (ast_i2c_read(bus, I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) { + ast_i2c_bus_error_recover(bus); + if(timeout <= 0) + break; + timeout--; + msleep(2); + } + + return timeout <= 0 ? EAGAIN : 0; +} + +static bool ast_i2c_do_byte_xfer(struct aspeed_i2c_bus *bus) +{ + u32 cmd, data; + + if (bus->send_start) { + dev_dbg(bus->dev, "%s %c: addr %x start, len %d\n", __func__, + bus->msg->flags & I2C_M_RD ? 'R' : 'W', + bus->msg->addr, bus->msg->len); + + data = bus->msg->addr << 1; + if (bus->msg->flags & I2C_M_RD) + data |= 0x1; + + cmd = AST_I2CD_M_TX_CMD | AST_I2CD_M_START_CMD; + if (bus->send_stop && bus->msg->len == 0) + cmd |= AST_I2CD_M_STOP_CMD; + + ast_i2c_write(bus, data, I2C_BYTE_BUF_REG); + ast_i2c_issue_cmd(bus, cmd); + + } else if (bus->msg_pos < bus->msg->len){ + bool is_last = bus->msg_pos + 1 == bus->msg->len; + + dev_dbg(bus->dev, "%s %c%c: addr %x xfer %d, len %d\n", + __func__, + bus->msg->flags & I2C_M_RD ? 'R' : 'W', + bus->send_stop && is_last ? 'T' : '-', + bus->msg->addr, + bus->msg_pos, bus->msg->len); + + if (bus->msg->flags & I2C_M_RD) { + cmd = AST_I2CD_M_RX_CMD; + if (bus->send_stop && is_last) + cmd |= AST_I2CD_M_S_RX_CMD_LAST | + AST_I2CD_M_STOP_CMD; + + } else { + cmd = AST_I2CD_M_TX_CMD; + ast_i2c_write(bus, bus->msg->buf[bus->msg_pos], + I2C_BYTE_BUF_REG); + + if (bus->send_stop && is_last) + cmd |= AST_I2CD_M_STOP_CMD; + } + ast_i2c_issue_cmd(bus, cmd); + + } else { + return false; + } + + return true; +} + +//TX/Rx Done +static void ast_i2c_master_xfer_done(struct aspeed_i2c_bus *bus) +{ + bool next_msg_queued; + + dev_dbg(bus->dev, "%s xfer %d%c\n", __func__, + bus->msg_pos, + bus->send_start ? 'S' : ' '); + + if (bus->send_start) { + bus->send_start = false; + } else { + + if (bus->msg->flags & I2C_M_RD) { + uint8_t data; + + data = (ast_i2c_read(bus, I2C_BYTE_BUF_REG) & + AST_I2CD_RX_BYTE_BUFFER) >> 8; + + if (bus->query_len) { + bus->msg->len += data; + bus->query_len = false; + dev_dbg(bus->dev, "got rx len: %d\n", + bus->msg->len -1); + } + bus->msg->buf[bus->msg_pos] = data; + } + bus->msg_pos++; + } + + /* queue the next message. If there's none left, we notify the + * waiter */ + next_msg_queued = ast_i2c_do_byte_xfer(bus); + if (!next_msg_queued) + complete(&bus->cmd_complete); +} + +static irqreturn_t aspeed_i2c_bus_irq_handle(struct aspeed_i2c_bus *bus) +{ + const u32 errs = AST_I2CD_INTR_ARBIT_LOSS | + AST_I2CD_INTR_ABNORMAL | + AST_I2CD_INTR_SCL_TIMEOUT | + AST_I2CD_INTR_SDA_DL_TIMEOUT | + AST_I2CD_INTR_TX_NAK; + u32 sts, cmd; + + spin_lock(&bus->cmd_lock); + + cmd = ast_i2c_read(bus, I2C_CMD_REG); + sts = ast_i2c_read(bus, I2C_INTR_STS_REG); + + dev_dbg(bus->dev, "irq! status 0x%08x, cmd 0x%08x\n", sts, cmd); + + sts &= 0x7fff; + bus->state = cmd >> 19 & 0xf; + + /* ack everything */ + ast_i2c_write(bus, sts, I2C_INTR_STS_REG); + + bus->cmd_err |= sts & errs; + bus->cmd_pending = cmd & AST_I2CD_CMDS; + + /* if we've seen an error, notify our waiter */ + if (bus->cmd_err) { + complete(&bus->cmd_complete); + + /* We have a transfer in progress */ + } else if (bus->msg && !bus->cmd_pending) { + ast_i2c_master_xfer_done(bus); + + /* Other message queued: recovery, error stop. Notify waiters. */ + } else if (bus->cmd_sent && !bus->cmd_pending) { + complete(&bus->cmd_complete); + + } else { + dev_err(bus->dev, "Invalid state (msg %p, pending %x)?", + bus->msg, bus->cmd_pending); + } + + spin_unlock(&bus->cmd_lock); + + return IRQ_HANDLED; +} + +#if 0 + + if (AST_I2CD_INTR_SMBUS_ALERT & sts) { + ast_master_alert_recv(bus); + sts &= ~AST_I2CD_INTR_SMBUS_ALERT; + } + + switch(sts) { + case AST_I2CD_INTR_TX_ACK: + ast_i2c_write(bus, AST_I2CD_INTR_TX_ACK, I2C_INTR_STS_REG); + break; + case AST_I2CD_INTR_TX_ACK | AST_I2CD_INTR_NORMAL_STOP: + if (bus->send_stop) { + ast_i2c_write(bus, AST_I2CD_INTR_TX_ACK | AST_I2CD_INTR_NORMAL_STOP, I2C_INTR_STS_REG); + ast_i2c_master_xfer_done(bus); + } else { + dev_dbg(bus->dev,"TODO ...\n"); + } + break; + + case AST_I2CD_INTR_TX_NAK: + ast_i2c_write(bus, AST_I2CD_INTR_TX_NAK, I2C_INTR_STS_REG); + if (bus->msg->flags == I2C_M_IGNORE_NAK) { + dev_dbg(bus->dev, "I2C_M_IGNORE_NAK next send\n"); + bus->cmd_err = 0; + } else { + dev_dbg(bus->dev, "NAK error\n"); + bus->cmd_err = AST_I2CD_INTR_TX_NAK; + } + break; + + case AST_I2CD_INTR_TX_NAK | AST_I2CD_INTR_NORMAL_STOP: + ast_i2c_write(bus, AST_I2CD_INTR_TX_NAK | AST_I2CD_INTR_NORMAL_STOP, I2C_INTR_STS_REG); + dev_dbg(bus->dev, "M TX NAK | NORMAL STOP \n"); + bus->cmd_err = AST_I2CD_INTR_TX_NAK | AST_I2CD_INTR_NORMAL_STOP; + complete(&bus->cmd_complete); + break; + + //Issue : Workaround for I2C slave mode + case AST_I2CD_INTR_TX_NAK | AST_I2CD_INTR_SLAVE_MATCH: + dev_err(bus->dev,"error: TX_NAK | SLAVE_MATCH\n"); + break; + case AST_I2CD_INTR_RX_DONE | AST_I2CD_INTR_SLAVE_MATCH: + dev_err(bus->dev,"error: RX_DONE | SLAVE_MATCH\n"); + ast_i2c_write(bus, AST_I2CD_INTR_RX_DONE | AST_I2CD_INTR_SLAVE_MATCH, I2C_INTR_STS_REG); + break; + case AST_I2CD_INTR_RX_DONE: + ast_i2c_write(bus, AST_I2CD_INTR_RX_DONE, I2C_INTR_STS_REG); + ast_i2c_master_xfer_done(bus); + break; + case AST_I2CD_INTR_NORMAL_STOP: + ast_i2c_write(bus, AST_I2CD_INTR_NORMAL_STOP, I2C_INTR_STS_REG); + bus->cmd_err = 0; + complete(&bus->cmd_complete); + break; + case (AST_I2CD_INTR_RX_DONE | AST_I2CD_INTR_NORMAL_STOP): + if (bus->send_stop) { + ast_i2c_write(bus, AST_I2CD_INTR_RX_DONE | AST_I2CD_INTR_NORMAL_STOP, I2C_INTR_STS_REG); + ast_i2c_master_xfer_done(bus); + } else { + dev_dbg(bus->dev,"TODO: RX_DONE | NORMAL_STOP\n"); + } + break; + default: + dev_err(bus->dev, "GR %p : No one care : %x, bus_id %d\n", + bus->i2c_dev->reg_gr, sts, bus->bus_id); + BUG(); + return IRQ_NONE; + } + + return IRQ_HANDLED; + +} +#endif + + +static irqreturn_t i2c_ast_handler(int this_irq, void *data) +{ + unsigned int p; + unsigned long isr_sts; + struct ast_i2c_dev *i2c = data; + + isr_sts = readl(i2c->reg_gr); + + for_each_set_bit(p, &isr_sts, 14) + aspeed_i2c_bus_irq_handle(&i2c->buses[p]); + + /* TODO: use a proper irq chip so we can return the status from each handler? */ + return IRQ_HANDLED; + +} +static int ast_i2c_do_msgs_xfer(struct aspeed_i2c_bus *bus, + struct i2c_msg *msgs, int num) +{ + unsigned long flags; + int i, ret = 0; + u32 err, cmd; + + for (i = 0; i < num; i++) { + + spin_lock_irqsave(&bus->cmd_lock, flags); + bus->msg = &msgs[i]; + bus->msg_pos = 0; + bus->query_len = bus->msg->flags & I2C_M_RECV_LEN; + bus->send_start = !(bus->msg->flags & I2C_M_NOSTART); + bus->send_stop = !!(num == i+1); + init_completion(&bus->cmd_complete); + + ast_i2c_do_byte_xfer(bus); + spin_unlock_irqrestore(&bus->cmd_lock, flags); + + ret = wait_for_completion_interruptible_timeout( + &bus->cmd_complete, + bus->adap.timeout * HZ); + + spin_lock_irqsave(&bus->cmd_lock, flags); + err = bus->cmd_err; + cmd = bus->cmd_sent; + bus->cmd_sent = 0; + bus->msg = NULL; + spin_unlock_irqrestore(&bus->cmd_lock, flags); + + if (!ret) { + dev_dbg(bus->dev, "controller timed out\n"); + return -EIO; + } + + if (err != 0) { + if (cmd & AST_I2CD_M_STOP_CMD) { + return -ETIMEDOUT; + } else { + dev_dbg(bus->dev, "send stop\n"); + ast_i2c_issue_oob_command(bus, + AST_I2CD_M_STOP_CMD); + return -EAGAIN; + } + } + } + + return num; +} + +static int ast_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct aspeed_i2c_bus *bus = adap->algo_data; + int ret, i; + int sts; + + sts = ast_i2c_read(bus, I2C_CMD_REG); + dev_dbg(bus->dev, "state[%x], SCL[%d], SDA[%d], BUS[%d]\n", + (sts >> 19) & 0xf, + (sts >> 18) & 0x1, + (sts >> 17) & 0x1, + (sts >> 16) & 1); + /* + * Wait for the bus to become free. + */ + + ret = ast_i2c_wait_bus_not_busy(bus); + if (ret) { + dev_err(&adap->dev, "i2c_ast: timeout waiting for bus free\n"); + goto out; + } + + for (i = adap->retries; i >= 0; i--) { + if (i != 0) + dev_dbg(&adap->dev, "Do retrying transmission [%d]\n",i); + + ret = ast_i2c_do_msgs_xfer(bus, msgs, num); + if (ret != -EAGAIN) + goto out; + + udelay(100); + } + + ret = -EREMOTEIO; +out: + + return ret; +} + +static u32 ast_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm i2c_ast_algorithm = { + .master_xfer = ast_i2c_xfer, + .functionality = ast_i2c_functionality, +}; + +static int aspeed_i2c_add_bus(struct device_node *np, + struct ast_i2c_dev *i2c, + struct platform_device *pdev) +{ + struct aspeed_i2c_bus *bus; + struct resource res; + int ret, bus_num; + + ret = of_property_read_u32(np, "bus", &bus_num); + if (ret || bus_num >= ARRAY_SIZE(i2c->buses)) + return -ENXIO; + + bus = &i2c->buses[bus_num]; + + ret = of_address_to_resource(np, 0, &res); + if (ret < 0) + return -ENXIO; + bus->base = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(bus->base)) + return PTR_ERR(bus->base); + + /* Initialize the I2C adapter */ + bus->adap.nr = bus_num; + bus->adap.owner = THIS_MODULE; + bus->adap.retries = 0; + bus->adap.timeout = 5; + bus->adap.algo = &i2c_ast_algorithm; + bus->adap.algo_data = bus; + bus->adap.dev.parent = &pdev->dev; + bus->adap.dev.of_node = np; + snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c at %p", + bus->base); + + bus->dev = &bus->adap.dev; + bus->i2c_dev = i2c; + + ret = of_property_read_u32(np, "clock-frequency", &bus->bus_clk); + if (ret < 0) { + dev_err(&pdev->dev, + "Could not read clock-frequency property\n"); + bus->bus_clk = 100000; + } + + ast_i2c_dev_init(bus); + + ret = i2c_add_numbered_adapter(&bus->adap); + if (ret < 0) + return -ENXIO; + + return 0; +} + +static int ast_i2c_probe(struct platform_device *pdev) +{ + struct ast_i2c_dev *dev; + struct resource *res; + struct device_node *np; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->reg_gr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->reg_gr)) + return PTR_ERR(dev->reg_gr); + + dev->irq = platform_get_irq(pdev, 0); + if (dev->irq < 0) + return -ENXIO; + + ret = request_irq(dev->irq, i2c_ast_handler, IRQF_SHARED, + "Apseed i2c", dev); + if (ret) + return -ENXIO; + + dev->dev = &pdev->dev; + + dev->pclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->pclk)) { + dev_err(&pdev->dev, "cannot get pclk for i2c\n"); + return PTR_ERR(dev->pclk); + } + + for_each_available_child_of_node(pdev->dev.of_node, np) { + if (!of_device_is_compatible(np, "aspeed,ast2400-i2c-bus")) + continue; + + ret = aspeed_i2c_add_bus(np, dev, pdev); + if (ret < 0) + dev_err(&pdev->dev, "faield to add i2c bus %s\n", np->name); + } + + platform_set_drvdata(pdev, dev); + + return 0; +} + +static const struct of_device_id aspeed_i2c_of_table[] = { + { .compatible = "aspeed,ast2400-i2c-common", }, + { }, +}; +MODULE_DEVICE_TABLE(of, aspeed_i2c_of_table); + +static struct platform_driver i2c_ast_driver = { + .probe = ast_i2c_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_i2c_of_table, + }, +}; + +module_platform_driver(i2c_ast_driver); + +MODULE_AUTHOR("Ryan Chen "); +MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ast_i2c"); diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index a59c3111f7fb98..98f88bdeabe535 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -501,7 +501,6 @@ static int i2c_device_uevent(struct device *dev, struct kobj_uevent_env *env) if (add_uevent_var(env, "MODALIAS=%s%s", I2C_MODULE_PREFIX, client->name)) return -ENOMEM; - dev_dbg(dev, "uevent\n"); return 0; } diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index bb3048f00e647d..630d9830f1dbf0 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -55,3 +55,4 @@ obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o +obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o diff --git a/drivers/irqchip/irq-aspeed-vic.c b/drivers/irqchip/irq-aspeed-vic.c new file mode 100644 index 00000000000000..ce029cc75177c1 --- /dev/null +++ b/drivers/irqchip/irq-aspeed-vic.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2015 - Ben Herrenschmidt, IBM Corp. + * + * Driver for Aspeed "new" VIC as found in SoC generation 3 and later + * + * Based on irq-vic.c: + * + * Copyright (C) 1999 - 2003 ARM Limited + * Copyright (C) 2000 Deep Blue Solutions Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +//#define DBG(fmt...) do { printk("AVIC " fmt); } while(0) +#define DBG(fmt...) do { } while(0) + +/* These definitions correspond to the "new mapping" of the + * register set that interleaves "high" and "low". The offsets + * below are for the "low" register, add 4 to get to the high one + */ +#define AVIC_IRQ_STATUS 0x00 +#define AVIC_FIQ_STATUS 0x08 +#define AVIC_RAW_STATUS 0x10 +#define AVIC_INT_SELECT 0x18 +#define AVIC_INT_ENABLE 0x20 +#define AVIC_INT_ENABLE_CLR 0x28 +#define AVIC_INT_TRIGGER 0x30 +#define AVIC_INT_TRIGGER_CLR 0x38 +#define AVIC_INT_SENSE 0x40 +#define AVIC_INT_DUAL_EDGE 0x48 +#define AVIC_INT_EVENT 0x50 +#define AVIC_EDGE_CLR 0x58 +#define AVIC_EDGE_STATUS 0x60 + +struct aspeed_vic { + void __iomem *base; + u32 valid_sources[2]; + u32 edge_sources[2]; + struct irq_domain *dom; +}; +static struct aspeed_vic *system_avic; + +static void vic_init_hw(struct aspeed_vic *vic) +{ + u32 sense; + + /* Disable all interrupts */ + writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR); + writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR + 4); + + /* Make sure no soft trigger is on */ + writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR); + writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR + 4); + + /* Set everything to be IRQ */ + writel(0, vic->base + AVIC_INT_SELECT); + writel(0, vic->base + AVIC_INT_SELECT + 4); + + /* Some interrupts have a programable high/low level trigger + * (4 GPIO direct inputs), for now we assume this was configured + * by firmware. We read which ones are edge now. + */ + sense = readl(vic->base + AVIC_INT_SENSE); + vic->edge_sources[0] = ~sense; + sense = readl(vic->base + AVIC_INT_SENSE + 4); + vic->edge_sources[1] = ~sense; + + /* Clear edge detection latches */ + writel(0xffffffff, vic->base + AVIC_EDGE_CLR); + writel(0xffffffff, vic->base + AVIC_EDGE_CLR + 4); +} + +static void __exception_irq_entry avic_handle_irq(struct pt_regs *regs) +{ + struct aspeed_vic *vic = system_avic; + u32 stat, irq; + u32 loops = 0; + + /* We handle interrupts in a loop, is that necessary ? TBD */ + for (;;) { + irq = 0; + stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS); + if (!stat) { + stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS + 4); + irq = 32; + } + if (stat == 0) + break; + irq += ffs(stat) - 1; + if (irq != 16) + DBG("irq=%d\n", irq); + handle_domain_irq(vic->dom, irq, regs); + loops++; + } + if (loops == 0) + DBG("S!\n"); +} + +static void avic_ack_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("ACK %ld\n", d->hwirq); + /* Clear edge latch for edge interrupts, nop for level */ + if (vic->edge_sources[sidx] & sbit) + writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4); +} + +static void avic_mask_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("MASK %ld\n", d->hwirq); + writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4); +} + +static void avic_unmask_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("UNMASK %ld\n", d->hwirq); + writel(sbit, vic->base + AVIC_INT_ENABLE + sidx * 4); +} + +/* For level irq, faster than going through a nop "ack" and mask */ +static void avic_mask_ack_irq(struct irq_data *d) +{ + struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); + unsigned int sidx = d->hwirq >> 5; + unsigned int sbit = 1u << (d->hwirq & 0x1f); + + if (d->hwirq != 16) + DBG("MASK_ACK %ld\n", d->hwirq); + + /* First mask */ + writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4); + + /* Then clear edge latch for edge interrupts */ + if (vic->edge_sources[sidx] & sbit) + writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4); +} + +static struct irq_chip avic_chip = { + .name = "AVIC", + .irq_ack = avic_ack_irq, + .irq_mask = avic_mask_irq, + .irq_unmask = avic_unmask_irq, + .irq_mask_ack = avic_mask_ack_irq, +}; + +static int avic_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct aspeed_vic *vic = d->host_data; + unsigned int sidx = hwirq >> 5; + unsigned int sbit = 1u << (hwirq & 0x1f); + + /* Check if interrupt exists */ + if (sidx > 1 || !(vic->valid_sources[sidx] & sbit)) + return -EPERM; + DBG("MAP %d edge %d\n", hwirq, !!(vic->edge_sources[sidx] & sbit)); + if (vic->edge_sources[sidx] & sbit) + irq_set_chip_and_handler(irq, &avic_chip, handle_edge_irq); + else + irq_set_chip_and_handler(irq, &avic_chip, handle_level_irq); + irq_set_chip_data(irq, vic); + irq_set_probe(irq); + return 0; +} + +static struct irq_domain_ops avic_dom_ops = { + .map = avic_map, + .xlate = irq_domain_xlate_onetwocell, +}; + +static int __init avic_of_init(struct device_node *node, + struct device_node *parent) +{ + void __iomem *regs; + struct aspeed_vic *vic; + + if (WARN(parent, "non-root Aspeed VIC not supported")) + return -EINVAL; + if (WARN(system_avic, "duplicate Aspeed VIC not supported")) + return -EINVAL; + + regs = of_iomap(node, 0); + if (WARN_ON(!regs)) + return -EIO; + + vic = kzalloc(sizeof(struct aspeed_vic), GFP_KERNEL); + if (WARN_ON(!vic)) { + iounmap(regs); + return -ENOMEM; + } + vic->base = regs; + + of_property_read_u32_index(node, "valid-sources", 0, + &vic->valid_sources[0]); + of_property_read_u32_index(node, "valid-sources", 1, + &vic->valid_sources[1]); + + /* Initialize soures, all masked */ + vic_init_hw(vic); + + /* Ready to receive interrupts */ + system_avic = vic; + set_handle_irq(avic_handle_irq); + + /* Register our domain. XXX Count valid sources */ + vic->dom = irq_domain_add_simple(node, 64, 0, + &avic_dom_ops, vic); + + pr_info("Aspeed VIC Initiallized\n"); + + return 0; +} + +IRQCHIP_DECLARE(aspeed_new_vic, "aspeed,new-vic", avic_of_init); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ccccc2943f2fde..47313a82278384 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -525,6 +525,11 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config ASPEED_BT_IPMI_HOST + tristate "BT IPMI host driver" + help + Support for the Aspeed BT ipmi host. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 537d7f3b78da9a..019bb2601e020d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE) += genwqe/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_ASPEED_BT_IPMI_HOST) += bt-host.o diff --git a/drivers/misc/bt-host.c b/drivers/misc/bt-host.c new file mode 100644 index 00000000000000..105d3fc664a454 --- /dev/null +++ b/drivers/misc/bt-host.c @@ -0,0 +1,420 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVICE_NAME "bt-host" + +#define BT_IO_BASE 0xe4 +#define BT_IRQ 10 + +#define BT_CR0 0x0 +#define BT_CR0_IO_BASE 16 +#define BT_CR0_IRQ 12 +#define BT_CR0_EN_CLR_SLV_RDP 0x8 +#define BT_CR0_EN_CLR_SLV_WRP 0x4 +#define BT_CR0_ENABLE_IBT 0x1 +#define BT_CR1 0x4 +#define BT_CR1_IRQ_H2B 0x01 +#define BT_CR1_IRQ_HBUSY 0x40 +#define BT_CR2 0x8 +#define BT_CR2_IRQ_H2B 0x01 +#define BT_CR2_IRQ_HBUSY 0x40 +#define BT_CR3 0xc +#define BT_CTRL 0x10 +#define BT_CTRL_B_BUSY 0x80 +#define BT_CTRL_H_BUSY 0x40 +#define BT_CTRL_OEM0 0x20 +#define BT_CTRL_SMS_ATN 0x10 +#define BT_CTRL_B2H_ATN 0x08 +#define BT_CTRL_H2B_ATN 0x04 +#define BT_CTRL_CLR_RD_PTR 0x02 +#define BT_CTRL_CLR_WR_PTR 0x01 +#define BT_BMC2HOST 0x14 +#define BT_INTMASK 0x18 +#define BT_INTMASK_B2H_IRQEN 0x01 +#define BT_INTMASK_B2H_IRQ 0x02 +#define BT_INTMASK_BMC_HWRST 0x80 + +struct bt_host { + struct device dev; + struct miscdevice miscdev; + void *base; + int open_count; + int irq; + wait_queue_head_t queue; + struct timer_list poll_timer; +}; + +static u8 bt_inb(struct bt_host *bt_host, int reg) +{ + return ioread8(bt_host->base + reg); +} + +static void bt_outb(struct bt_host *bt_host, u8 data, int reg) +{ + iowrite8(data, bt_host->base + reg); +} + +static void clr_rd_ptr(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_CLR_RD_PTR, BT_CTRL); +} + +static void clr_wr_ptr(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_CLR_WR_PTR, BT_CTRL); +} + +static void clr_h2b_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_H2B_ATN, BT_CTRL); +} + +static void set_b_busy(struct bt_host *bt_host) +{ + if (!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY)) + bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL); +} + +static void clr_b_busy(struct bt_host *bt_host) +{ + if (bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY) + bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL); +} + +static void set_b2h_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_B2H_ATN, BT_CTRL); +} + +static u8 bt_read(struct bt_host *bt_host) +{ + return bt_inb(bt_host, BT_BMC2HOST); +} + +static void bt_write(struct bt_host *bt_host, u8 c) +{ + bt_outb(bt_host, c, BT_BMC2HOST); +} + +static void set_sms_atn(struct bt_host *bt_host) +{ + bt_outb(bt_host, BT_CTRL_SMS_ATN, BT_CTRL); +} + +static struct bt_host *file_bt_host(struct file *file) +{ + return container_of(file->private_data, struct bt_host, miscdev); +} + +static int bt_host_open(struct inode *inode, struct file *file) +{ + struct bt_host *bt_host = file_bt_host(file); + + clr_b_busy(bt_host); + + return 0; +} + +static ssize_t bt_host_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct bt_host *bt_host = file_bt_host(file); + char __user *p = buf; + u8 len; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + WARN_ON(*ppos); + + if (wait_event_interruptible(bt_host->queue, + bt_inb(bt_host, BT_CTRL) & BT_CTRL_H2B_ATN)) + return -ERESTARTSYS; + + set_b_busy(bt_host); + clr_h2b_atn(bt_host); + clr_rd_ptr(bt_host); + + len = bt_read(bt_host); + __put_user(len, p++); + + /* We pass the length back as well */ + if (len + 1 > count) + len = count - 1; + + while(len) { + if (__put_user(bt_read(bt_host), p)) + return -EFAULT; + len--; p++; + } + + clr_b_busy(bt_host); + + return p - buf; +} + +static ssize_t bt_host_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct bt_host *bt_host = file_bt_host(file); + const char __user *p = buf; + u8 c; + + if (!access_ok(VERIFY_READ, buf, count)) + return -EFAULT; + + WARN_ON(*ppos); + + /* There's no interrupt for clearing host busy so we have to + * poll */ + if (wait_event_interruptible(bt_host->queue, + !(bt_inb(bt_host, BT_CTRL) & + (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))) + return -ERESTARTSYS; + + clr_wr_ptr(bt_host); + + while (count) { + if (__get_user(c, p)) + return -EFAULT; + + bt_write(bt_host, c); + count--; p++; + } + + set_b2h_atn(bt_host); + + return p - buf; +} + +static long bt_host_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + struct bt_host *bt_host = file_bt_host(file); + switch (cmd) { + case BT_HOST_IOCTL_SMS_ATN: + set_sms_atn(bt_host); + return 0; + } + return -EINVAL; +} + +static int bt_host_release(struct inode *inode, struct file *file) +{ + struct bt_host *bt_host = file_bt_host(file); + set_b_busy(bt_host); + return 0; +} + +static unsigned int bt_host_poll(struct file *file, poll_table *wait) +{ + struct bt_host *bt_host = file_bt_host(file); + unsigned int mask = 0; + uint8_t ctrl; + + poll_wait(file, &bt_host->queue, wait); + + ctrl = bt_inb(bt_host, BT_CTRL); + + if (ctrl & BT_CTRL_H2B_ATN) + mask |= POLLIN; + + if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))) + mask |= POLLOUT; + + return mask; +} + +static const struct file_operations bt_host_fops = { + .owner = THIS_MODULE, + .open = bt_host_open, + .read = bt_host_read, + .write = bt_host_write, + .release = bt_host_release, + .poll = bt_host_poll, + .unlocked_ioctl = bt_host_ioctl, +}; + +static void poll_timer(unsigned long data) +{ + struct bt_host *bt_host = (void *)data; + bt_host->poll_timer.expires += msecs_to_jiffies(500); + wake_up(&bt_host->queue); + add_timer(&bt_host->poll_timer); +} + +irqreturn_t bt_host_irq(int irq, void *arg) +{ + struct bt_host *bt_host = arg; + uint32_t reg; + + reg = ioread32(bt_host->base + BT_CR2); + reg &= BT_CR2_IRQ_H2B | BT_CR2_IRQ_HBUSY; + if (!reg) + return IRQ_NONE; + + /* ack pending IRQs */ + iowrite32(reg, bt_host->base + BT_CR2); + + wake_up(&bt_host->queue); + return IRQ_HANDLED; +} + +static int bt_host_config_irq(struct bt_host *bt_host, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + uint32_t reg; + int rc; + + bt_host->irq = irq_of_parse_and_map(dev->of_node, 0); + if (!bt_host->irq) + return -ENODEV; + + rc = devm_request_irq(dev, bt_host->irq, bt_host_irq, IRQF_SHARED, + DEVICE_NAME, bt_host); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", bt_host->irq); + bt_host->irq = 0; + return rc; + } + + /* Configure IRQs on the host clearing the H2B and HBUSY bits; + * H2B will be asserted when the host has data for us; HBUSY + * will be cleared (along with B2H) when we can write the next + * message to the BT buffer */ + reg = ioread32(bt_host->base + BT_CR1); + reg |= BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY; + iowrite32(reg, bt_host->base + BT_CR1); + + return 0; +} + +static int bt_host_probe(struct platform_device *pdev) +{ + struct bt_host *bt_host; + struct device *dev; + struct resource *res; + int rc; + + if (!pdev || !pdev->dev.of_node) + return -ENODEV; + + dev = &pdev->dev; + dev_info(dev, "Found bt host device\n"); + + bt_host = devm_kzalloc(dev, sizeof(*bt_host), GFP_KERNEL); + if (!bt_host) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, bt_host); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Unable to find resources\n"); + rc = -ENXIO; + goto out_free; + } + + bt_host->base = devm_ioremap_resource(&pdev->dev, res); + if (!bt_host->base) { + rc = -ENOMEM; + goto out_free; + } + + init_waitqueue_head(&bt_host->queue); + + bt_host->miscdev.minor = MISC_DYNAMIC_MINOR, + bt_host->miscdev.name = DEVICE_NAME, + bt_host->miscdev.fops = &bt_host_fops, + bt_host->miscdev.parent = dev; + rc = misc_register(&bt_host->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + goto out_unmap; + } + + bt_host_config_irq(bt_host, pdev); + + if (bt_host->irq) { + dev_info(dev, "Using IRQ %d\n", bt_host->irq); + } else { + dev_info(dev, "No IRQ; using timer\n"); + init_timer(&bt_host->poll_timer); + bt_host->poll_timer.function = poll_timer; + bt_host->poll_timer.data = (unsigned long)bt_host; + bt_host->poll_timer.expires = jiffies + msecs_to_jiffies(10); + add_timer(&bt_host->poll_timer); + } + + iowrite32((BT_IO_BASE << BT_CR0_IO_BASE) | + (BT_IRQ << BT_CR0_IRQ) | + BT_CR0_EN_CLR_SLV_RDP | + BT_CR0_EN_CLR_SLV_WRP | + BT_CR0_ENABLE_IBT, + bt_host->base + BT_CR0); + + clr_b_busy(bt_host); + + return 0; + +out_unmap: + devm_iounmap(&pdev->dev, bt_host->base); + +out_free: + devm_kfree(dev, bt_host); + return rc; + +} + +static int bt_host_remove(struct platform_device *pdev) +{ + struct bt_host *bt_host = dev_get_drvdata(&pdev->dev); + misc_deregister(&bt_host->miscdev); + if (!bt_host->irq) + del_timer_sync(&bt_host->poll_timer); + devm_iounmap(&pdev->dev, bt_host->base); + devm_kfree(&pdev->dev, bt_host); + bt_host = NULL; + + return 0; +} + +static const struct of_device_id bt_host_match[] = { + { .compatible = "aspeed,bt-host" }, + { }, +}; + +static struct platform_driver bt_host_driver = { + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + .of_match_table = bt_host_match, + }, + .probe = bt_host_probe, + .remove = bt_host_remove, +}; + +module_platform_driver(bt_host_driver); + +MODULE_DEVICE_TABLE(of, bt_host_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alistair Popple "); +MODULE_DESCRIPTION("Linux device interface to the BT interface"); diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c index 6d0c5d5eea6dd2..8caed357787a17 100644 --- a/drivers/net/ethernet/faraday/ftgmac100.c +++ b/drivers/net/ethernet/faraday/ftgmac100.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "ftgmac100.h" @@ -68,12 +69,15 @@ struct ftgmac100 { struct net_device *netdev; struct device *dev; + struct ncsi_dev *ndev; struct napi_struct napi; struct mii_bus *mii_bus; int phy_irq[PHY_MAX_ADDR]; struct phy_device *phydev; int old_speed; + + bool use_ncsi; }; static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv, @@ -86,7 +90,6 @@ static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv, FTGMAC100_INT_XPKT_ETH | \ FTGMAC100_INT_XPKT_LOST | \ FTGMAC100_INT_AHB_ERR | \ - FTGMAC100_INT_PHYSTS_CHG | \ FTGMAC100_INT_RPKT_BUF | \ FTGMAC100_INT_NO_RXBUF) @@ -134,7 +137,7 @@ static int ftgmac100_reset_hw(struct ftgmac100 *priv) return -EIO; } -static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac) +static void ftgmac100_do_set_mac(struct ftgmac100 *priv, const unsigned char *mac) { unsigned int maddr = mac[0] << 8 | mac[1]; unsigned int laddr = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5]; @@ -143,6 +146,57 @@ static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac) iowrite32(laddr, priv->base + FTGMAC100_OFFSET_MAC_LADR); } +static void ftgmac100_setup_mac(struct ftgmac100 *priv) +{ + unsigned char mac[6]; + unsigned int m; + unsigned int l; + + /* XXX TODO: Read from device-tree if provided */ + + m = ioread32(priv->base + FTGMAC100_OFFSET_MAC_MADR); + l = ioread32(priv->base + FTGMAC100_OFFSET_MAC_LADR); + + mac[0] = (m >> 8) & 0xff; + mac[1] = (m ) & 0xff; + mac[2] = (l >> 24) & 0xff; + mac[3] = (l >> 16) & 0xff; + mac[4] = (l >> 8) & 0xff; + mac[5] = (l ) & 0xff; + + /* XXX Temp workaround for u-boot garbage */ + if (!is_valid_ether_addr(mac)) { + mac[5] = (m >> 8) & 0xff; + mac[4] = (m ) & 0xff; + mac[3] = (l >> 24) & 0xff; + mac[2] = (l >> 16) & 0xff; + mac[1] = (l >> 8) & 0xff; + mac[0] = (l ) & 0xff; + } + + if (!is_valid_ether_addr(mac)) { + eth_hw_addr_random(priv->netdev); + dev_info(priv->dev, "Generated random MAC address %pM\n", + priv->netdev->dev_addr); + } else { + dev_info(priv->dev, "Read MAC address from chip %pM\n", mac); + memcpy(priv->netdev->dev_addr, mac, 6); + } +} + +static int ftgmac100_set_mac_addr(struct net_device *dev, void *p) +{ + struct ftgmac100 *priv = netdev_priv(dev); + + int ret = eth_prepare_mac_addr_change(dev, p); + if (ret < 0) + return ret; + eth_commit_mac_addr_change(dev, p); + ftgmac100_do_set_mac(priv, dev->dev_addr); + + return 0; +} + static void ftgmac100_init_hw(struct ftgmac100 *priv) { /* setup ring buffer base registers */ @@ -157,7 +211,7 @@ static void ftgmac100_init_hw(struct ftgmac100 *priv) iowrite32(FTGMAC100_APTC_RXPOLL_CNT(1), priv->base + FTGMAC100_OFFSET_APTC); - ftgmac100_set_mac(priv, priv->netdev->dev_addr); + ftgmac100_do_set_mac(priv, priv->netdev->dev_addr); } #define MACCR_ENABLE_ALL (FTGMAC100_MACCR_TXDMA_EN | \ @@ -956,6 +1010,8 @@ static int ftgmac100_get_settings(struct net_device *netdev, { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->phydev) + return -EINVAL; return phy_ethtool_gset(priv->phydev, cmd); } @@ -964,6 +1020,8 @@ static int ftgmac100_set_settings(struct net_device *netdev, { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->phydev) + return -EINVAL; return phy_ethtool_sset(priv->phydev, cmd); } @@ -982,7 +1040,11 @@ static irqreturn_t ftgmac100_interrupt(int irq, void *dev_id) struct net_device *netdev = dev_id; struct ftgmac100 *priv = netdev_priv(netdev); - if (likely(netif_running(netdev))) { + /* When running in NCSI mode, the interface should be + * ready to receive or transmit NCSI packet before it's + * opened. + */ + if (likely(priv->use_ncsi || netif_running(netdev))) { /* Disable interrupts for polling */ iowrite32(0, priv->base + FTGMAC100_OFFSET_IER); napi_schedule(&priv->napi); @@ -1036,13 +1098,12 @@ static int ftgmac100_poll(struct napi_struct *napi, int budget) } if (status & (FTGMAC100_INT_NO_RXBUF | FTGMAC100_INT_RPKT_LOST | - FTGMAC100_INT_AHB_ERR | FTGMAC100_INT_PHYSTS_CHG)) { + FTGMAC100_INT_AHB_ERR)) { if (net_ratelimit()) - netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status, + netdev_info(netdev, "[ISR] = 0x%x: %s%s%s\n", status, status & FTGMAC100_INT_NO_RXBUF ? "NO_RXBUF " : "", status & FTGMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "", - status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : "", - status & FTGMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : ""); + status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : ""); if (status & FTGMAC100_INT_NO_RXBUF) { /* RX buffer unavailable */ @@ -1095,17 +1156,30 @@ static int ftgmac100_open(struct net_device *netdev) goto err_hw; ftgmac100_init_hw(priv); - ftgmac100_start_hw(priv, 10); + ftgmac100_start_hw(priv, priv->use_ncsi ? 100 : 10); - phy_start(priv->phydev); + if (priv->phydev) + phy_start(priv->phydev); + else if (priv->use_ncsi) + netif_carrier_on(priv->netdev); napi_enable(&priv->napi); netif_start_queue(netdev); /* enable all interrupts */ iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTGMAC100_OFFSET_IER); + /* Start the NCSI device */ + if (priv->use_ncsi){ + err = ncsi_start_dev(priv->ndev); + if (err) + goto err_ncsi; + } return 0; +err_ncsi: + napi_disable(&priv->napi); + netif_stop_queue(netdev); + iowrite32(0, priv->base + FTGMAC100_OFFSET_IER); err_hw: free_irq(priv->irq, netdev); err_irq: @@ -1114,7 +1188,7 @@ static int ftgmac100_open(struct net_device *netdev) return err; } -static int ftgmac100_stop(struct net_device *netdev) +static int ftgmac100_stop_dev(struct net_device *netdev) { struct ftgmac100 *priv = netdev_priv(netdev); @@ -1123,7 +1197,8 @@ static int ftgmac100_stop(struct net_device *netdev) netif_stop_queue(netdev); napi_disable(&priv->napi); - phy_stop(priv->phydev); + if (priv->phydev) + phy_stop(priv->phydev); ftgmac100_stop_hw(priv); free_irq(priv->irq, netdev); @@ -1132,6 +1207,18 @@ static int ftgmac100_stop(struct net_device *netdev) return 0; } +static int ftgmac100_stop(struct net_device *netdev) +{ + struct ftgmac100 *priv = netdev_priv(netdev); + + /* Stop NCSI device */ + if (priv->use_ncsi) { + ncsi_stop_dev(priv->ndev); + return 0; + } + + return ftgmac100_stop_dev(netdev); +} static int ftgmac100_hard_start_xmit(struct sk_buff *skb, struct net_device *netdev) { @@ -1166,18 +1253,87 @@ static int ftgmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int { struct ftgmac100 *priv = netdev_priv(netdev); + if (!priv->phydev) + return -EINVAL; return phy_mii_ioctl(priv->phydev, ifr, cmd); } +static int ftgmac100_setup_mdio(struct ftgmac100 *priv) +{ + int i, err = 0; + + /* initialize mdio bus */ + priv->mii_bus = mdiobus_alloc(); + if (!priv->mii_bus) { + err = -EIO; + goto err_alloc_mdiobus; + } + + priv->mii_bus->name = "ftgmac100_mdio"; + snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "ftgmac100_mii"); + + priv->mii_bus->priv = priv->netdev; + priv->mii_bus->read = ftgmac100_mdiobus_read; + priv->mii_bus->write = ftgmac100_mdiobus_write; + priv->mii_bus->irq = priv->phy_irq; + + for (i = 0; i < PHY_MAX_ADDR; i++) + priv->mii_bus->irq[i] = PHY_POLL; + + err = mdiobus_register(priv->mii_bus); + if (err) { + dev_err(priv->dev, "Cannot register MDIO bus!\n"); + goto err_register_mdiobus; + } + + err = ftgmac100_mii_probe(priv); + if (err) { + dev_err(priv->dev, "MII Probe failed!\n"); + goto err_mii_probe; + } + return 0; + +err_mii_probe: + mdiobus_unregister(priv->mii_bus); +err_register_mdiobus: + mdiobus_free(priv->mii_bus); +err_alloc_mdiobus: + return err; +} + +static void ftgmac100_destroy_mdio(struct ftgmac100 *priv) +{ + if (!priv->use_ncsi) + return; + phy_disconnect(priv->phydev); + mdiobus_unregister(priv->mii_bus); + mdiobus_free(priv->mii_bus); +} + static const struct net_device_ops ftgmac100_netdev_ops = { .ndo_open = ftgmac100_open, .ndo_stop = ftgmac100_stop, .ndo_start_xmit = ftgmac100_hard_start_xmit, - .ndo_set_mac_address = eth_mac_addr, + .ndo_set_mac_address = ftgmac100_set_mac_addr, .ndo_validate_addr = eth_validate_addr, .ndo_do_ioctl = ftgmac100_do_ioctl, }; +static void ftgmac100_ncsi_handler(struct ncsi_dev *nd) +{ + struct net_device *netdev = nd->nd_dev; + + if (nd->nd_state != ncsi_dev_state_functional) + return; + + if (nd->nd_link_up) { + pr_info("NCSI dev is up\n"); + netif_start_queue(netdev); + } else { + pr_info("NCSI dev is down\n"); + ftgmac100_stop_dev(netdev); + } +} /****************************************************************************** * struct platform_driver functions *****************************************************************************/ @@ -1187,8 +1343,7 @@ static int ftgmac100_probe(struct platform_device *pdev) int irq; struct net_device *netdev; struct ftgmac100 *priv; - int err; - int i; + int err = 0; if (!pdev) return -ENODEV; @@ -1208,16 +1363,29 @@ static int ftgmac100_probe(struct platform_device *pdev) goto err_alloc_etherdev; } + /* Check for NCSI mode */ + priv = netdev_priv(netdev); SET_NETDEV_DEV(netdev, &pdev->dev); + if (pdev->dev.of_node && + of_get_property(pdev->dev.of_node, "use-nc-si", NULL)) { + dev_info(&pdev->dev, "Using NCSI interface\n"); + priv->phydev = NULL; + priv->use_ncsi = true; + } else { + priv->use_ncsi = false; + } netdev->ethtool_ops = &ftgmac100_ethtool_ops; netdev->netdev_ops = &ftgmac100_netdev_ops; - netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO; + if (pdev->dev.of_node && + of_get_property(pdev->dev.of_node, "no-hw-checksum", NULL)) + netdev->features = NETIF_F_GRO; + else + netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO; platform_set_drvdata(pdev, netdev); /* setup private data */ - priv = netdev_priv(netdev); priv->netdev = netdev; priv->dev = &pdev->dev; @@ -1244,60 +1412,41 @@ static int ftgmac100_probe(struct platform_device *pdev) priv->irq = irq; - /* initialize mdio bus */ - priv->mii_bus = mdiobus_alloc(); - if (!priv->mii_bus) { - err = -EIO; - goto err_alloc_mdiobus; - } + /* Read MAC address or setup a new one */ + ftgmac100_setup_mac(priv); - priv->mii_bus->name = "ftgmac100_mdio"; - snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "ftgmac100_mii"); - - priv->mii_bus->priv = netdev; - priv->mii_bus->read = ftgmac100_mdiobus_read; - priv->mii_bus->write = ftgmac100_mdiobus_write; - priv->mii_bus->irq = priv->phy_irq; - - for (i = 0; i < PHY_MAX_ADDR; i++) - priv->mii_bus->irq[i] = PHY_POLL; - - err = mdiobus_register(priv->mii_bus); - if (err) { - dev_err(&pdev->dev, "Cannot register MDIO bus!\n"); - goto err_register_mdiobus; - } + /* Register NCSI device */ + if (priv->use_ncsi) { + priv->ndev = ncsi_register_dev(netdev, ftgmac100_ncsi_handler); + if (!priv->ndev) + goto err_ncsi_dev; + } else { + err = ftgmac100_setup_mdio(priv); - err = ftgmac100_mii_probe(priv); - if (err) { - dev_err(&pdev->dev, "MII Probe failed!\n"); - goto err_mii_probe; + /* Survive PHY probe failure, chances things will work if the + * PHY was setup by the bootloader + */ + if (err) + dev_warn(&pdev->dev, "Error %d setting up MDIO\n", err); } - /* register network device */ + /* Register network device */ err = register_netdev(netdev); if (err) { dev_err(&pdev->dev, "Failed to register netdev\n"); goto err_register_netdev; } - netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base); - - if (!is_valid_ether_addr(netdev->dev_addr)) { - eth_hw_addr_random(netdev); - netdev_info(netdev, "generated random MAC address %pM\n", - netdev->dev_addr); - } + netdev_dbg(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base); return 0; err_register_netdev: - phy_disconnect(priv->phydev); -err_mii_probe: - mdiobus_unregister(priv->mii_bus); -err_register_mdiobus: - mdiobus_free(priv->mii_bus); -err_alloc_mdiobus: + if (!priv->use_ncsi) + ftgmac100_destroy_mdio(priv); + else + ncsi_unregister_dev(priv->ndev); +err_ncsi_dev: iounmap(priv->base); err_ioremap: release_resource(priv->res); @@ -1318,9 +1467,7 @@ static int __exit ftgmac100_remove(struct platform_device *pdev) unregister_netdev(netdev); - phy_disconnect(priv->phydev); - mdiobus_unregister(priv->mii_bus); - mdiobus_free(priv->mii_bus); + ftgmac100_destroy_mdio(priv); iounmap(priv->base); release_resource(priv->res); @@ -1330,11 +1477,18 @@ static int __exit ftgmac100_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id ftgmac100_of_match[] = { + { .compatible = "faraday,ftgmac100" }, + { } +}; +MODULE_DEVICE_TABLE(of, ftgmac100_of_match); + static struct platform_driver ftgmac100_driver = { .probe = ftgmac100_probe, - .remove = __exit_p(ftgmac100_remove), + .remove = ftgmac100_remove, .driver = { - .name = DRV_NAME, + .name = DRV_NAME, + .of_match_table = ftgmac100_of_match, }, }; diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 84dd2ed47a928e..e7aa6615210f9a 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -235,6 +235,14 @@ config PINCTRL_ZYNQ help This selectes the pinctrl driver for Xilinx Zynq. +config PINCTRL_ASPEED + bool "Pinctrl driver for Aspeed BMC SoCs" + depends on ARCH_ASPEED + select PINMUX + select GENERIC_PINCONF + help + This selectes the pinctrl driver for Aspeed BMC SoCs. + source "drivers/pinctrl/bcm/Kconfig" source "drivers/pinctrl/berlin/Kconfig" source "drivers/pinctrl/freescale/Kconfig" diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index cad077c43fb731..8513b01924bbcb 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_PINCTRL_LPC18XX) += pinctrl-lpc18xx.o obj-$(CONFIG_PINCTRL_TB10X) += pinctrl-tb10x.o obj-$(CONFIG_PINCTRL_ST) += pinctrl-st.o obj-$(CONFIG_PINCTRL_ZYNQ) += pinctrl-zynq.o +obj-$(CONFIG_PINCTRL_ASPEED) += pinctrl-aspeed.o obj-$(CONFIG_ARCH_BCM) += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ diff --git a/drivers/pinctrl/pinctrl-aspeed.c b/drivers/pinctrl/pinctrl-aspeed.c new file mode 100644 index 00000000000000..b1a127277f71c3 --- /dev/null +++ b/drivers/pinctrl/pinctrl-aspeed.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include "core.h" + +#define SCU 0x1e6e2000 +#define SCU80 (SCU + 0x80) +#define SCU84 (SCU + 0x84) +#define SCU88 (SCU + 0x88) +#define SCU90 (SCU + 0x90) +#define SCU94 (SCU + 0x94) + +struct ast_pinctrl_desc { + uint32_t *c1_reg; + uint32_t c1_mask; + uint32_t *c2_reg; + uint32_t c2_mask; +}; + +#define DEF_AST_PIN(name, a, b, c, d) \ + static struct ast_pinctrl_desc BALL_##name = {(u32 *) a, b, c, d} + +DEF_AST_PIN(D6, SCU80, BIT_MASK(0), NULL, 0); +DEF_AST_PIN(B5, SCU80, BIT_MASK(1), NULL, 0); +DEF_AST_PIN(A4, SCU80, BIT_MASK(2), NULL, 0); +DEF_AST_PIN(E6, SCU80, BIT_MASK(3), NULL, 0); +DEF_AST_PIN(C5, SCU90, BIT_MASK(22), NULL, 0); +DEF_AST_PIN(B4, SCU90, BIT_MASK(22), NULL, 0); +DEF_AST_PIN(A3, SCU90, BIT_MASK(2), NULL, 0); +DEF_AST_PIN(D5, SCU90, BIT_MASK(2), NULL, 0); + +#define AST_PIN_DESC(n, pinname) \ + { .number = n, .name = "pinname", .drv_data = &BALL_##pinname } + +static const struct pinctrl_pin_desc ast2400_pins[] = { + AST_PIN_DESC(0, D6), + AST_PIN_DESC(1, B5), + AST_PIN_DESC(2, A4), + AST_PIN_DESC(3, E6), + AST_PIN_DESC(4, C5), + AST_PIN_DESC(5, B4), + AST_PIN_DESC(6, A3), + AST_PIN_DESC(7, D5), +}; + +static const unsigned int i2c9_pins[] = { 4, 5 }; + +struct ast_pin_group { + const char *name; + const unsigned int *pins; + const unsigned num_pins; +}; + +static const struct ast_pin_group ast_pin_groups[] = { + { + .name = "i2c9", + .pins = i2c9_pins, + .num_pins = ARRAY_SIZE(i2c9_pins), + }, +}; + +static int ast_get_groups_count(struct pinctrl_dev *pctl) +{ + return ARRAY_SIZE(ast_pin_groups); +} + +static const char *ast_get_group_name(struct pinctrl_dev *pctl, + unsigned selector) +{ + return ast_pin_groups[selector].name; +} + +static int ast_get_group_pins(struct pinctrl_dev *pctl, unsigned selector, + const unsigned **pins, unsigned *num_pins) +{ + *pins = (unsigned *)ast_pin_groups[selector].pins; + *num_pins = ast_pin_groups[selector].num_pins; + return 0; +} + +static struct pinctrl_ops ast_pinctrl_ops = { + .get_groups_count = ast_get_groups_count, + .get_group_name = ast_get_group_name, + .get_group_pins = ast_get_group_pins, +}; + +static int ast_pinconf_get(struct pinctrl_dev *pctldev, + unsigned pin, unsigned long *config) +{ + dev_err(pctldev->dev, "Unimplemented function %s\n", __func__); + + return -ENOTSUPP; +} + +static int ast_pinconf_set(struct pinctrl_dev *pctldev, unsigned pin, + unsigned long *configs, unsigned num_configs) +{ + struct ast_pinctrl_desc *desc; + + if (pin >= pctldev->desc->npins) { + dev_err(pctldev->dev, "pin number %d is too damn high\n", + pin); + return -ERANGE; + } + + desc = pctldev->desc->pins[pin].drv_data; + + dev_err(pctldev->dev, "Unimplemented function %s\n", __func__); + + return -ENOTSUPP; +} + +static struct pinconf_ops ast_pconf_ops = { + .pin_config_get = ast_pinconf_get, + .pin_config_set = ast_pinconf_set, +}; + +struct ast_pinctrl { + struct device *dev; + struct pinctrl_dev *pctl; +}; + +struct pinctrl_desc ast_desc = { + .name = "Aspeed", + .pins = ast2400_pins, + .npins = ARRAY_SIZE(ast2400_pins), + .pctlops = &ast_pinctrl_ops, + .confops = &ast_pconf_ops, + .owner = THIS_MODULE, +}; + +struct aspeed_pinctrl { + struct device *dev; + void __iomem *base; + struct pinctrl_dev *pctl_dev; +}; + +int ast_pinctrl_probe(struct platform_device *pdev) +{ + struct aspeed_pinctrl *pctl; + struct resource *res; + + pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); + if (!pctl) + return -ENOMEM; + + platform_set_drvdata(pdev, pctl); + pctl->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pctl->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pctl->base)) + return PTR_ERR(pctl->base); + + pctl->pctl_dev = pinctrl_register(&ast_desc, &pdev->dev, pctl); + if (!pctl->dev) { + pr_err("could not register apseed pin driver\n"); + return -EIO; + } + + return 0; +} diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 9d4290617cee5a..9b66b880426263 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1603,6 +1603,16 @@ config RTC_DRV_XGENE This driver can also be built as a module, if so, the module will be called "rtc-xgene". +config RTC_DRV_ASPEED + tristate "Aspeed AST24xx RTC" + depends on HAS_IOMEM + help + If you say yes here you get support for the Aspeed AST24xx SoC real + time clocks. + + This driver can also be built as a module, if so, the module + will be called "rtc-aspeed". + comment "HID Sensor RTC drivers" config RTC_DRV_HID_SENSOR_TIME diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index e491eb52443422..cc01f0635064d7 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_RTC_DRV_ABB5ZES3) += rtc-ab-b5ze-s3.o obj-$(CONFIG_RTC_DRV_ABX80X) += rtc-abx80x.o obj-$(CONFIG_RTC_DRV_ARMADA38X) += rtc-armada38x.o obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o +obj-$(CONFIG_RTC_DRV_ASPEED) += rtc-aspeed.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o diff --git a/drivers/rtc/rtc-aspeed.c b/drivers/rtc/rtc-aspeed.c new file mode 100644 index 00000000000000..11ea6300d18fbf --- /dev/null +++ b/drivers/rtc/rtc-aspeed.c @@ -0,0 +1,147 @@ +/* + * RTC driver for the Aspeed 24xx SoCs + * + * Copyright 2015 IBM Corp. + * + * Joel Stanley + * + * 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. + * + */ + +#include +#include +#include +#include +#include + +struct aspeed_rtc { + struct rtc_device *rtc_dev; + void __iomem *base; + spinlock_t lock; +}; + +#define RTC_TIME 0x00 +#define RTC_YEAR 0x04 +#define RTC_CTRL 0x10 + +#define RTC_UNLOCK 0x02 +#define RTC_ENABLE 0x01 + +static int aspeed_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct aspeed_rtc *rtc = dev_get_drvdata(dev); + unsigned int cent, year, mon, day, hour, min, sec; + u32 reg1, reg2; + + do { + reg2 = readl(rtc->base + RTC_YEAR); + reg1 = readl(rtc->base + RTC_TIME); + } while (reg2 != readl(rtc->base + RTC_YEAR)); + + day = (reg1 >> 24) & 0x1f; + hour = (reg1 >> 16) & 0x1f; + min = (reg1 >> 8) & 0x3f; + sec = (reg1 >> 0) & 0x3f; + cent = (reg2 >> 16) & 0x1f; + year = (reg2 >> 8) & 0x7f; + mon = (reg2 >> 0) & 0x3f; + + rtc_time64_to_tm(mktime64(cent*100 + year, mon, day, hour, min, sec), + tm); + + return 0; +} + +static int aspeed_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct aspeed_rtc *rtc = dev_get_drvdata(dev); + unsigned long flags; + u32 reg1, reg2, ctrl; + int year, cent; + + /* tm_year counts from 1900 */ + cent = (tm->tm_year + 1900) / 100; + year = tm->tm_year % 100; + + reg1 = (tm->tm_mday << 24) | (tm->tm_hour << 16) | (tm->tm_min << 8) | + tm->tm_sec; + + reg2 = ((cent & 0x1f) << 16) | ((year & 0x7f) << 8) | + (tm->tm_mon & 0xf); + + /* TODO: Do we need to lock? */ + spin_lock_irqsave(&rtc->lock, flags); + + ctrl = readl(rtc->base + RTC_CTRL); + writel(ctrl | RTC_UNLOCK, rtc->base + RTC_CTRL); + + writel(reg1, rtc->base + RTC_TIME); + writel(reg2, rtc->base + RTC_YEAR); + + writel(ctrl, rtc->base + RTC_CTRL); + + spin_unlock_irqrestore(&rtc->lock, flags); + + return 0; +} + +static struct rtc_class_ops aspeed_rtc_ops = { + .read_time = aspeed_rtc_read_time, + .set_time = aspeed_rtc_set_time, +}; + +static int aspeed_rtc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct aspeed_rtc *rtc; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rtc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rtc->base)) + return PTR_ERR(rtc->base); + + platform_set_drvdata(pdev, rtc); + + rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name, + &aspeed_rtc_ops, THIS_MODULE); + + if (IS_ERR(rtc->rtc_dev)) { + dev_err(&pdev->dev, "failed to register\n"); + return PTR_ERR(rtc->rtc_dev); + } + + spin_lock_init(&rtc->lock); + + /* Enable RTC and clear the unlock bit */ + writel(RTC_ENABLE, rtc->base + RTC_CTRL); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id aspeed_rtc_of_match_table[] = { + { .compatible = "aspeed,rtc", }, + {} +}; +#endif + +static struct platform_driver aspeed_rtc_driver = { + .driver = { + .name = "aspeed-rtc", + .of_match_table = of_match_ptr(aspeed_rtc_of_match_table), + }, +}; + +module_platform_driver_probe(aspeed_rtc_driver, aspeed_rtc_probe); + +MODULE_DESCRIPTION("Aspeed AST24xx RTC driver"); +MODULE_AUTHOR("Joel Stanley "); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 79e1aa1b0959f1..5fb1c4239dd5c2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -578,6 +578,16 @@ config LPC18XX_WATCHDOG To compile this driver as a module, choose M here: the module will be called lpc18xx_wdt. +config ASPEED_24xx_WATCHDOG + tristate "Aspeed 23xx 24xx SoCs watchdog support" + depends on ARCH_ASPEED + select WATCHDOG_CORE + help + Say Y here to include support for the watchdog timer + in Apseed 23xx or 24xx BMC SoCs. + To compile this driver as a module, choose M here: the + module will be called aspeed_wdt. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 0c616e3f67bb57..9c899d86addec8 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o +obj-$(CONFIG_ASPEED_24xx_WATCHDOG) += aspeed_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c new file mode 100644 index 00000000000000..9b82420f902542 --- /dev/null +++ b/drivers/watchdog/aspeed_wdt.c @@ -0,0 +1,223 @@ +/* + * Copyright 2015 IBM Corp. + * + * Joel Stanley + * + * Based on the qcom-watchdog driver + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +struct aspeed_wdt { + struct watchdog_device wdd; + struct clk *clk; + unsigned long rate; + struct notifier_block restart_nb; + void __iomem *base; +}; + +static const struct of_device_id aspeed_wdt_of_table[] = { + { .compatible = "aspeed,wdt", }, + { }, +}; +MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); + +#define WDT_STATUS 0x00 +#define WDT_RELOAD_VALUE 0x04 +#define WDT_RESTART 0x08 +#define WDT_CTRL 0x0C +#define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5) +#define WDT_CTRL_RESET_SYSTEM (0x1 << 1) +#define WDT_CTRL_ENABLE (0x1 << 0) + +#define WDT_RESTART_MAGIC 0x4755 + +static void aspeed_wdt_enable(struct aspeed_wdt *wdt, int count) +{ + u32 ctrl = WDT_CTRL_RESET_MODE_FULL_CHIP | WDT_CTRL_RESET_SYSTEM | + WDT_CTRL_ENABLE; + + writel(0, wdt->base + WDT_CTRL); + writel(count, wdt->base + WDT_RELOAD_VALUE); + writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); + writel(ctrl, wdt->base + WDT_CTRL); +} + +static int aspeed_wdt_start(struct watchdog_device *wdd) +{ + struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd); + dev_dbg(wdd->dev, "starting with timeout of %d (rate %lu)\n", + wdd->timeout, wdt->rate); + aspeed_wdt_enable(wdt, wdd->timeout * wdt->rate); + return 0; +} + +static int aspeed_wdt_stop(struct watchdog_device *wdd) +{ + struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd); + + writel(0, wdt->base + WDT_CTRL); + return 0; +} + +static int aspeed_wdt_ping(struct watchdog_device *wdd) +{ + struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd); + + dev_dbg(wdd->dev, "ping\n"); + writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART); + return 0; +} + +static int aspeed_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + dev_dbg(wdd->dev, "timeout set to %u\n", timeout); + wdd->timeout = timeout; + return aspeed_wdt_start(wdd); +} + +static int aspeed_wdt_restart(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct aspeed_wdt *wdt = container_of(nb, + struct aspeed_wdt, restart_nb); + + /* + * Trigger watchdog bite: + * Setup reload count to be 128ms, and enable WDT. + */ + aspeed_wdt_enable(wdt, 128 * wdt->rate / 1000); + + return NOTIFY_DONE; +} + +static const struct watchdog_ops aspeed_wdt_ops = { + .start = aspeed_wdt_start, + .stop = aspeed_wdt_stop, + .ping = aspeed_wdt_ping, + .set_timeout = aspeed_wdt_set_timeout, + .owner = THIS_MODULE, +}; + +static const struct watchdog_info aspeed_wdt_info = { + .options = WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE + | WDIOF_SETTIMEOUT, + .identity = KBUILD_MODNAME, +}; + +static int aspeed_wdt_remove(struct platform_device *pdev) +{ + struct aspeed_wdt *wdt = platform_get_drvdata(pdev); + + unregister_restart_handler(&wdt->restart_nb); + watchdog_unregister_device(&wdt->wdd); + clk_disable_unprepare(wdt->clk); + return 0; +} + +static int aspeed_wdt_probe(struct platform_device *pdev) +{ + struct aspeed_wdt *wdt; + struct resource *res; + int ret; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + wdt->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt->clk)) { + dev_err(&pdev->dev, "failed to get input clock\n"); + return PTR_ERR(wdt->clk); + } + + ret = clk_prepare_enable(wdt->clk); + if (ret) { + dev_err(&pdev->dev, "failed to setup clock\n"); + goto err; + } + + /* + * We use the clock rate to calculate the max timeout, so ensure it's + * not zero to avoid a divide-by-zero exception. + * + * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such + * that it would bite before a second elapses it's usefulness is + * limited. Bail if this is the case. + */ + wdt->rate = clk_get_rate(wdt->clk); + if (wdt->rate == 0 || + wdt->rate > 0x10000000U) { + dev_err(&pdev->dev, "invalid clock rate\n"); + ret = -EINVAL; + goto err; + } + + wdt->wdd.dev = &pdev->dev; + wdt->wdd.info = &aspeed_wdt_info; + wdt->wdd.ops = &aspeed_wdt_ops; + wdt->wdd.min_timeout = 1; + wdt->wdd.max_timeout = 0x10000000U / wdt->rate; + + /* + * If 'timeout-sec' unspecified in devicetree, assume a 30 second + * default, unless the max timeout is less than 30 seconds, then use + * the max instead. + */ + wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U); + watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev); + + ret = watchdog_register_device(&wdt->wdd); + if (ret) { + dev_err(&pdev->dev, "failed to register\n"); + goto err; + } + + /* + * WDT restart notifier has priority 0 (use as a last resort) + */ + wdt->restart_nb.notifier_call = aspeed_wdt_restart; + ret = register_restart_handler(&wdt->restart_nb); + if (ret) + dev_err(&pdev->dev, "failed to setup restart handler\n"); + + dev_info(&pdev->dev, "rate %lu, max timeout %u, timeout %d\n", + wdt->rate, wdt->wdd.max_timeout, wdt->wdd.timeout); + + platform_set_drvdata(pdev, wdt); + return 0; + +err: + clk_disable_unprepare(wdt->clk); + return ret; +} + +static struct platform_driver aspeed_watchdog_driver = { + .probe = aspeed_wdt_probe, + .remove = aspeed_wdt_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = aspeed_wdt_of_table, + }, +}; +module_platform_driver(aspeed_watchdog_driver); + +MODULE_DESCRIPTION("Aspeed AST23/4xx Watchdog Driver"); diff --git a/include/net/ncsi.h b/include/net/ncsi.h new file mode 100644 index 00000000000000..6041ca3d821bfa --- /dev/null +++ b/include/net/ncsi.h @@ -0,0 +1,59 @@ +#ifndef __NET_NCSI_H +#define __NET_NCSI_H + +#include + +/* + * The NCSI device states seen from external. More NCSI device states are + * only visible internally (in net/ncsi/internal.h). When the NCSI device + * is registered, it's in ncsi_dev_state_registered state. The state + * ncsi_dev_state_start is used to drive to choose active package and + * channel. After that, its state is changed to ncsi_dev_state_functional. + * + * The state ncsi_dev_state_stop helps to shut down the currently active + * package and channel while ncsi_dev_state_config helps to reconfigure + * them. + */ +enum { + ncsi_dev_state_registered = 0x0000, + ncsi_dev_state_functional = 0x0100, + ncsi_dev_state_start = 0x0200, + ncsi_dev_state_config = 0x0300, + ncsi_dev_state_stop = 0x0400 +}; + +struct ncsi_dev { + int nd_state; + int nd_link_up; + struct net_device *nd_dev; + void (*nd_handler)(struct ncsi_dev *ndev); +}; + +#ifdef CONFIG_NET_NCSI +struct ncsi_dev *ncsi_register_dev(struct net_device *dev, + void (*notifier)(struct ncsi_dev *nd)); +int ncsi_start_dev(struct ncsi_dev *nd); +int ncsi_stop_dev(struct ncsi_dev *nd); +void ncsi_unregister_dev(struct ncsi_dev *nd); +#else /* !CONFIG_NET_NCSI */ +static inline struct ncsi_dev *ncsi_register_dev(struct net_device *dev, + void (*notifier)(struct ncsi_dev *nd)) +{ + return NULL; +} + +static inline int ncsi_start_dev(struct ncsi_dev *nd) +{ + return -ENOTTY; +} + +static inline int ncsi_stop_dev(struct ncsi_dev *nd) +{ + return -ENOTTY; +} + +void inline ncsi_unregister_dev(struct ncsi_dev *nd) +{ +} +#endif /* CONFIG_NET_NCSI */ +#endif /* __NET_NCSI_H */ diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index f7b2db44eb4b07..490938f2e6010c 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -74,6 +74,7 @@ header-y += bpf_common.h header-y += bpf.h header-y += bpqether.h header-y += bsg.h +header-y += bt-host.h header-y += btrfs.h header-y += can.h header-y += capability.h diff --git a/include/uapi/linux/bt-host.h b/include/uapi/linux/bt-host.h new file mode 100644 index 00000000000000..a4298d9e7e2610 --- /dev/null +++ b/include/uapi/linux/bt-host.h @@ -0,0 +1,18 @@ +/* + * Copyright 2015 IBM Corp. + * + * 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. + */ + +#ifndef _UAPI_LINUX_BT_HOST_H +#define _UAPI_LINUX_BT_HOST_H + +#include + +#define __BT_HOST_IOCTL_MAGIC 0xb1 +#define BT_HOST_IOCTL_SMS_ATN _IO(__BT_HOST_IOCTL_MAGIC, 0x00) + +#endif /* _UAPI_LINUX_BT_HOST_H */ diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index ea9221b0331ade..b20e9e678d2f15 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -86,6 +86,7 @@ #define ETH_P_8021AH 0x88E7 /* 802.1ah Backbone Service Tag */ #define ETH_P_MVRP 0x88F5 /* 802.1Q MVRP */ #define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */ +#define ETH_P_NCSI 0x88F8 /* NCSI protocol */ #define ETH_P_PRP 0x88FB /* IEC 62439-3 PRP/HSRv0 */ #define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */ #define ETH_P_TDLS 0x890D /* TDLS */ diff --git a/include/uapi/linux/ncsi.h b/include/uapi/linux/ncsi.h new file mode 100644 index 00000000000000..9a3d18025a40ca --- /dev/null +++ b/include/uapi/linux/ncsi.h @@ -0,0 +1,200 @@ +#ifndef _UAPI_LINUX_NCSI_H +#define _UAPI_LINUX_NCSI_H + +/* NCSI netlink message type */ +enum { + NCSI_MSG_BASE = 16, + NCSI_MSG_GET_LAYOUT = 16, + NCSI_MSG_GET_VERSION, + NCSI_MSG_GET_CAP, + NCSI_MSG_GET_MODE, + NCSI_MSG_GET_FILTER, + NCSI_MSG_GET_STATS, + NCSI_MSG_SET_MODE, + NCSI_MSG_SET_FILTER, + NCSI_MSG_MAX +}; + + +/* NCSI channel capabilities */ +enum { + NCSI_CAP_BASE = 0, + NCSI_CAP_GENERIC = 0, + NCSI_CAP_BC, + NCSI_CAP_MC, + NCSI_CAP_BUFFER, + NCSI_CAP_AEN, + NCSI_CAP_VLAN, + NCSI_CAP_MAX +}; + +enum { + NCSI_CAP_GENERIC_HWA = 0x01, /* HW arbitration */ + NCSI_CAP_GENERIC_HDS = 0x02, /* HNC driver status change */ + NCSI_CAP_GENERIC_FC = 0x04, /* HNC to MC flow control */ + NCSI_CAP_GENERIC_FC1 = 0x08, /* MC to HNC flow control */ + NCSI_CAP_GENERIC_MC = 0x10, /* Global multicast filtering */ + NCSI_CAP_GENERIC_MASK = 0x1f, + NCSI_CAP_BC_ARP = 0x01, /* ARP packet filtering */ + NCSI_CAP_BC_DHCPC = 0x02, /* DHCP client filtering */ + NCSI_CAP_BC_DHCPS = 0x04, /* DHCP server filtering */ + NCSI_CAP_BC_NETBIOS = 0x08, /* NetBIOS packet filtering */ + NCSI_CAP_BC_MASK = 0x0f, + NCSI_CAP_MC_NEIGHBOR = 0x01, /* IPv6 neighbor filtering */ + NCSI_CAP_MC_ROUTER = 0x02, /* IPv6 router filering */ + NCSI_CAP_MC_DHCPv6 = 0x04, /* DHCPv6 filtering */ + NCSI_CAP_MC_MASK = 0x07, + NCSI_CAP_AEN_LSC = 0x01, /* Link status change AEN */ + NCSI_CAP_AEN_CR = 0x02, /* Configuration required AEN */ + NCSI_CAP_AEN_HDS = 0x04, /* HNC driver status AEN */ + NCSI_CAP_AEN_MASK = 0x07, + NCSI_CAP_VLAN_ONLY = 0x01, /* VLAN is supported */ + NCSI_CAP_VLAN_NO = 0x02, /* Filter VLAN and non-VLAN */ + NCSI_CAP_VLAN_ANY = 0x04, /* Filter Any-and-non-VLAN */ + NCSI_CAP_VLAN_MASK = 0x07 +}; + +/* NCSI channel mode */ +enum { + NCSI_MODE_BASE = 0, + NCSI_MODE_ENABLE = 0, + NCSI_MODE_TX_ENABLE, + NCSI_MODE_LINK, + NCSI_MODE_VLAN, + NCSI_MODE_BC, + NCSI_MODE_MC, + NCSI_MODE_AEN, + NCSI_MODE_FC, + NCSI_MODE_MAX +}; + +/* NCSI channel filters */ +enum { + NCSI_FILTER_BASE = 0, + NCSI_FILTER_VLAN = 0, + NCSI_FILTER_UC, + NCSI_FILTER_MC, + NCSI_FILTER_MIXED, + NCSI_FILTER_MAX +}; + +/* + * It's put right after netlink message header. Also, it's + * used to convey NCSI topology layout. + */ +struct ncsi_msg { + __u32 nm_flag; +#define NCSI_FLAG_REQUEST 0x1 +#define NCSI_FLAG_RESPONSE 0x2 +#define NCSI_FLAG_ACTIVE_CHANNEL 0x4 + + __u32 nm_ifindex; /* ID of network device */ + __u32 nm_package_id; /* ID of NCSI package */ + __u32 nm_channel_id; /* ID of NCSI channel */ + __u32 nm_index; /* ID of mode, capability or filter */ + __u32 nm_errcode; /* Error code */ +}; + +enum { + NCSI_SUCCESS, + NCSI_ERR_PARAM, + NCSI_ERR_NO_MEM, + NCSI_ERR_NO_DEV, + NCSI_ERR_NOT_ACTIVE, + NCSI_ERR_INTERNAL, +}; + +/* NCSI channel version */ +struct ncsi_channel_version { + __u32 ncv_version; /* Supported BCD encoded NCSI version */ + __u32 ncv_alpha2; /* Supported BCD encoded NCSI version */ + __u8 ncv_fw_name[12]; /* Firware name string */ + __u32 ncv_fw_version; /* Firmware version */ + __u16 ncv_pci_ids[4]; /* PCI identification */ + __u32 ncv_mf_id; /* Manufacture ID */ +}; + +/* NCSI channel capability */ +struct ncsi_channel_cap { + __u32 ncc_index; /* Index of channel capabilities */ + __u32 ncc_cap; /* NCSI channel capability */ +}; + +/* NCSI channel mode */ +struct ncsi_channel_mode { + __u32 ncm_index; /* Index of channel modes */ + __u32 ncm_enable; /* Enabled or disabled */ + __u32 ncm_size; /* Valid entries in ncm_data[] */ + __u32 ncm_data[8]; /* Data entries */ +}; + +/* NCSI channel filter */ +struct ncsi_channel_filter { + __u32 ncf_index; /* Index of channel filters */ + __u32 ncf_total; /* Total entries in the filter table */ + __u64 ncf_bitmap; /* Bitmap of valid entries */ + __u8 ncf_data[]; /* Data for the valid entries */ +}; + +/* NCSI channel statistics */ +struct ncsi_channel_stats { + __u32 ncs_hnc_cnt_hi; /* Counter cleared */ + __u32 ncs_hnc_cnt_lo; /* Counter cleared */ + __u32 ncs_hnc_rx_bytes; /* Rx bytes */ + __u32 ncs_hnc_tx_bytes; /* Tx bytes */ + __u32 ncs_hnc_rx_uc_pkts; /* Rx UC packets */ + __u32 ncs_hnc_rx_mc_pkts; /* Rx MC packets */ + __u32 ncs_hnc_rx_bc_pkts; /* Rx BC packets */ + __u32 ncs_hnc_tx_uc_pkts; /* Tx UC packets */ + __u32 ncs_hnc_tx_mc_pkts; /* Tx MC packets */ + __u32 ncs_hnc_tx_bc_pkts; /* Tx BC packets */ + __u32 ncs_hnc_fcs_err; /* FCS errors */ + __u32 ncs_hnc_align_err; /* Alignment errors */ + __u32 ncs_hnc_false_carrier; /* False carrier detection */ + __u32 ncs_hnc_runt_pkts; /* Rx runt packets */ + __u32 ncs_hnc_jabber_pkts; /* Rx jabber packets */ + __u32 ncs_hnc_rx_pause_xon; /* Rx pause XON frames */ + __u32 ncs_hnc_rx_pause_xoff; /* Rx XOFF frames */ + __u32 ncs_hnc_tx_pause_xon; /* Tx XON frames */ + __u32 ncs_hnc_tx_pause_xoff; /* Tx XOFF frames */ + __u32 ncs_hnc_tx_s_collision; /* Single collision frames */ + __u32 ncs_hnc_tx_m_collision; /* Multiple collision frames */ + __u32 ncs_hnc_l_collision; /* Late collision frames */ + __u32 ncs_hnc_e_collision; /* Excessive collision frames */ + __u32 ncs_hnc_rx_ctl_frames; /* Rx control frames */ + __u32 ncs_hnc_rx_64_frames; /* Rx 64-bytes frames */ + __u32 ncs_hnc_rx_127_frames; /* Rx 65-127 bytes frames */ + __u32 ncs_hnc_rx_255_frames; /* Rx 128-255 bytes frames */ + __u32 ncs_hnc_rx_511_frames; /* Rx 256-511 bytes frames */ + __u32 ncs_hnc_rx_1023_frames; /* Rx 512-1023 bytes frames */ + __u32 ncs_hnc_rx_1522_frames; /* Rx 1024-1522 bytes frames */ + __u32 ncs_hnc_rx_9022_frames; /* Rx 1523-9022 bytes frames */ + __u32 ncs_hnc_tx_64_frames; /* Tx 64-bytes frames */ + __u32 ncs_hnc_tx_127_frames; /* Tx 65-127 bytes frames */ + __u32 ncs_hnc_tx_255_frames; /* Tx 128-255 bytes frames */ + __u32 ncs_hnc_tx_511_frames; /* Tx 256-511 bytes frames */ + __u32 ncs_hnc_tx_1023_frames; /* Tx 512-1023 bytes frames */ + __u32 ncs_hnc_tx_1522_frames; /* Tx 1024-1522 bytes frames */ + __u32 ncs_hnc_tx_9022_frames; /* Tx 1523-9022 bytes frames */ + __u32 ncs_hnc_rx_valid_bytes; /* Rx valid bytes */ + __u32 ncs_hnc_rx_runt_pkts; /* Rx error runt packets */ + __u32 ncs_hnc_rx_jabber_pkts; /* Rx error jabber packets */ + __u32 ncs_ncsi_rx_cmds; /* Rx NCSI commands */ + __u32 ncs_ncsi_dropped_cmds; /* Dropped commands */ + __u32 ncs_ncsi_cmd_type_errs; /* Command type errors */ + __u32 ncs_ncsi_cmd_csum_errs; /* Command checksum errors */ + __u32 ncs_ncsi_rx_pkts; /* Rx NCSI packets */ + __u32 ncs_ncsi_tx_pkts; /* Tx NCSI packets */ + __u32 ncs_ncsi_tx_aen_pkts; /* Tx AEN packets */ + __u32 ncs_pt_tx_pkts; /* Tx packets */ + __u32 ncs_pt_tx_dropped; /* Tx dropped packets */ + __u32 ncs_pt_tx_channel_err; /* Tx channel errors */ + __u32 ncs_pt_tx_us_err; /* Tx undersize errors */ + __u32 ncs_pt_rx_pkts; /* Rx packets */ + __u32 ncs_pt_rx_dropped; /* Rx dropped packets */ + __u32 ncs_pt_rx_channel_err; /* Rx channel errors */ + __u32 ncs_pt_rx_us_err; /* Rx undersize errors */ + __u32 ncs_pt_rx_os_err; /* Rx oversize errors */ +}; + +#endif /* _UAPI_LINUX_NCSI_H */ diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h index 6f3fe16cd22a24..3f35f8b70d90b4 100644 --- a/include/uapi/linux/netlink.h +++ b/include/uapi/linux/netlink.h @@ -27,6 +27,7 @@ #define NETLINK_ECRYPTFS 19 #define NETLINK_RDMA 20 #define NETLINK_CRYPTO 21 /* Crypto layer */ +#define NETLINK_NCSI 22 /* NCSI */ #define NETLINK_INET_DIAG NETLINK_SOCK_DIAG diff --git a/net/Kconfig b/net/Kconfig index 7021c1bf44d6ce..67b7e363dfeaa9 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -232,6 +232,7 @@ source "net/netlink/Kconfig" source "net/mpls/Kconfig" source "net/hsr/Kconfig" source "net/switchdev/Kconfig" +source "net/ncsi/Kconfig" config RPS bool diff --git a/net/Makefile b/net/Makefile index 3995613e5510cf..9c1d4b11401c67 100644 --- a/net/Makefile +++ b/net/Makefile @@ -74,3 +74,4 @@ obj-$(CONFIG_HSR) += hsr/ ifneq ($(CONFIG_NET_SWITCHDEV),) obj-y += switchdev/ endif +obj-$(CONFIG_NET_NCSI) += ncsi/ diff --git a/net/ncsi/Kconfig b/net/ncsi/Kconfig new file mode 100644 index 00000000000000..723f0ebd58e6ab --- /dev/null +++ b/net/ncsi/Kconfig @@ -0,0 +1,10 @@ +# +# Configuration for NCSI support +# + +config NET_NCSI + bool "NCSI interface support (EXPERIMENTAL)" + depends on INET + ---help--- + This module provides NCSI (Network Controller Sideband Interface) + support. diff --git a/net/ncsi/Makefile b/net/ncsi/Makefile new file mode 100644 index 00000000000000..bf9f7a981d3dbf --- /dev/null +++ b/net/ncsi/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for NCSI API +# +obj-$(CONFIG_NET_NCSI) += ncsi-cmd.o ncsi-rsp.o ncsi-aen.o \ + ncsi-manage.o ncsi-netlink.o ncsi.o diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h new file mode 100644 index 00000000000000..51683f2105a2d6 --- /dev/null +++ b/net/ncsi/internal.h @@ -0,0 +1,165 @@ +#ifndef __NCSI_INTERNAL_H__ +#define __NCSI_INTERNAL_H__ + +struct ncsi_dev_priv; +struct ncsi_package; + +#define NCSI_PACKAGE_INDEX(c) (((c) >> 5) & 0x7) +#define NCSI_CHANNEL_INDEX(c) ((c) & 0x1ffff) +#define NCSI_TO_CHANNEL(p, c) ((((p) & 0x7) << 5) | ((c) & 0x1ffff)) + +/* Channel state */ +enum { + ncsi_channel_state_deselected_initial, + ncsi_channel_state_selected_initial, + ncsi_channel_state_deselected_ready, + ncsi_channel_state_selected_ready, +}; + +struct ncsi_channel { + unsigned char nc_id; + int nc_state; + struct ncsi_package *nc_package; + struct ncsi_channel_version nc_version; + struct ncsi_channel_cap nc_caps[NCSI_CAP_MAX]; + struct ncsi_channel_mode nc_modes[NCSI_MODE_MAX]; + struct ncsi_channel_filter *nc_filters[NCSI_FILTER_MAX]; + struct ncsi_channel_stats nc_stats; + struct list_head nc_node; +}; + +struct ncsi_package { + unsigned char np_id; + struct ncsi_dev_priv *np_ndp; + atomic_t np_channel_num; + spinlock_t np_channel_lock; + struct list_head np_channels; + struct list_head np_node; +}; + +struct ncsi_skb_parms { + unsigned int nsp_valid; + unsigned int nsp_portid; + struct nlmsghdr nsp_nlh; +}; + +#define NCSI_CB(skb) (*(struct ncsi_skb_parms*)&((skb)->cb)) + +struct ncsi_req { + unsigned char nr_id; + bool nr_used; + struct ncsi_dev_priv *nr_ndp; + struct sk_buff *nr_cmd; + struct sk_buff *nr_rsp; + struct timer_list nr_timer; + bool nr_timer_enabled; +}; + +enum { + ncsi_dev_state_major = 0xff00, + ncsi_dev_state_minor = 0x00ff, + ncsi_dev_state_start_deselect = 0x0201, + ncsi_dev_state_start_package, + ncsi_dev_state_start_channel, + ncsi_dev_state_start_sp, + ncsi_dev_state_start_cis, + ncsi_dev_state_start_gvi, + ncsi_dev_state_start_gc, + ncsi_dev_state_start_dp, + ncsi_dev_state_start_active, + ncsi_dev_state_config_sp = 0x0301, + ncsi_dev_state_config_cis, + ncsi_dev_state_config_sma, + ncsi_dev_state_config_ebf, + ncsi_dev_state_config_ecnt, + ncsi_dev_state_config_ec, + ncsi_dev_state_config_gls, + ncsi_dev_state_config_done, + ncsi_dev_state_stop_select = 0x0401, + ncsi_dev_state_stop_dcnt, + ncsi_dev_state_stop_dc, + ncsi_dev_state_stop_deselect, + ncsi_dev_state_stop_done +}; + +struct ncsi_dev_priv { + struct ncsi_dev ndp_ndev; + int ndp_flags; +#define NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE 0x1 + struct ncsi_package *ndp_active_package; + struct ncsi_channel *ndp_active_channel; + atomic_t ndp_package_num; + spinlock_t ndp_package_lock; + struct list_head ndp_packages; + atomic_t ndp_pending_reqs; + atomic_t ndp_last_req_idx; + spinlock_t ndp_req_lock; + struct ncsi_req ndp_reqs[256]; + struct work_struct ndp_work; + struct packet_type ndp_ptype; + struct list_head ndp_node; +}; + +struct ncsi_cmd_arg { + struct ncsi_dev_priv *nca_ndp; + unsigned char nca_type; + unsigned char nca_id; + unsigned char nca_package; + unsigned char nca_channel; + unsigned short nca_payload; + struct nlmsghdr *nca_nlh; + unsigned int nca_portid; + union { + unsigned char nca_bytes[16]; + unsigned short nca_words[8]; + unsigned int nca_dwords[4]; + }; +}; + +extern struct net *ncsi_net; +extern struct list_head ncsi_dev_list; +extern spinlock_t ncsi_dev_lock; + +#define TO_NCSI_DEV_PRIV(nd) \ + container_of(nd, struct ncsi_dev_priv, ndp_ndev) +#define NCSI_FOR_EACH_DEV(ndp) \ + list_for_each_entry_rcu(ndp, &ncsi_dev_list, ndp_node) +#define NCSI_FOR_EACH_PACKAGE(ndp, np) \ + list_for_each_entry_rcu(np, &ndp->ndp_packages, np_node) +#define NCSI_FOR_EACH_CHANNEL(np, nc) \ + list_for_each_entry_rcu(nc, &np->np_channels, nc_node) + +/* Resources */ +int ncsi_find_channel_filter(struct ncsi_channel *nc, int table, void *data); +int ncsi_add_channel_filter(struct ncsi_channel *nc, int table, void *data); +int ncsi_del_channel_filter(struct ncsi_channel *nc, int table, int index); +struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np, + unsigned char id); +struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np, + unsigned char id); +struct ncsi_package *ncsi_add_package(struct ncsi_dev_priv *ndp, + unsigned char id); +struct ncsi_package *ncsi_find_package(struct ncsi_dev_priv *ndp, + unsigned char id); +void ncsi_release_package(struct ncsi_package *np); +void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp, + unsigned char id, + struct ncsi_package **np, + struct ncsi_channel **nc); +struct ncsi_req *ncsi_alloc_req(struct ncsi_dev_priv *ndp); +void ncsi_free_req(struct ncsi_req *nr, bool check, bool timeout); +struct ncsi_dev *ncsi_find_dev(struct net_device *dev); +int ncsi_config_dev(struct ncsi_dev *nd); + +/* Packet handlers */ +int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca); +int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, + struct packet_type *pt, struct net_device *orig_dev); +int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb); + +/* Netlink */ +int __net_init ncsi_netlink_init(struct net *net); +void __net_exit ncsi_netlink_exit(struct net *net); +void ncsi_netlink_reply(struct nlmsghdr *h, + unsigned int portid, bool timeout); +#endif /* __NCSI_INTERNAL_H__ */ diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c new file mode 100644 index 00000000000000..89433bc197e7f1 --- /dev/null +++ b/net/ncsi/ncsi-aen.c @@ -0,0 +1,197 @@ +/* + * Copyright Gavin Shan, IBM Corporation 2015. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "internal.h" +#include "ncsi-pkt.h" + +static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h, + const unsigned short payload) +{ + unsigned char *stream; + __be32 *checksum, csum; + __be32 high, low; + int i; + + if (h->common.revision != NCSI_PKT_REVISION) + return -EINVAL; + if (ntohs(h->common.length) != payload) + return -EINVAL; + + /* + * Validate checksum, which might be zeroes if the + * sender doesn't support checksum according to NCSI + * specification. + */ + checksum = (__be32 *)((void *)(h + 1) + payload - 4); + if (ntohl(*checksum) == 0) + return 0; + + csum = 0; + stream = (unsigned char *)h; + for (i = 0; i < sizeof(*h) + payload - 4; i += 2) { + high = stream[i]; + low = stream[i + 1]; + csum += ((high << 8) | low); + } + + csum = ~csum + 1; + if (*checksum != htonl(csum)) + return -EINVAL; + + return 0; +} + +static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, + struct ncsi_aen_pkt_hdr *h) +{ + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct ncsi_aen_lsc_pkt *lsc; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_aen_pkt(h, 12); + if (ret) + return ret; + + /* Find the NCSI channel */ + ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update the link status */ + ncm = &nc->nc_modes[NCSI_MODE_LINK]; + lsc = (struct ncsi_aen_lsc_pkt *)h; + ncm->ncm_data[2] = ntohl(lsc->status); + ncm->ncm_data[4] = ntohl(lsc->oem_status); + if (!ndp->ndp_active_channel || + ndp->ndp_active_channel != nc || + ncm->ncm_data[2] & 0x1) + return 0; + + /* If this channel is the active one and the link is down, + * we have to choose another channel to be active one. + */ + ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE; + ncsi_stop_dev(nd); + + return 0; +} + +static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp, + struct ncsi_aen_pkt_hdr *h) +{ + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct ncsi_channel *nc; + int ret; + + ret = ncsi_validate_aen_pkt(h, 4); + if (ret) + return ret; + + /* Find the NCSI channel */ + ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc); + if (!nc) + return -ENODEV; + + /* If the channel is active one, we need reconfigure it */ + if (!ndp->ndp_active_channel || + ndp->ndp_active_channel != nc) + return 0; + + ncsi_config_dev(nd); + + return 0; +} + +static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp, + struct ncsi_aen_pkt_hdr *h) +{ + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + struct ncsi_aen_hncdsc_pkt *hncdsc; + int ret; + + ret = ncsi_validate_aen_pkt(h, 4); + if (ret) + return ret; + + /* Find the NCSI channel */ + ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc); + if (!nc) + return -ENODEV; + + /* If the channel is active one, we need reconfigure it */ + ncm = &nc->nc_modes[NCSI_MODE_LINK]; + hncdsc = (struct ncsi_aen_hncdsc_pkt *)h; + ncm->ncm_data[3] = ntohl(hncdsc->status); + if (ndp->ndp_active_channel != nc || + ncm->ncm_data[3] & 0x1) + return 0; + + /* If this channel is the active one and the link doesn't + * work, we have to choose another channel to be active one. + * The logic here is exactly similar to what we do when link + * is down on the active channel. + */ + ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE; + ncsi_stop_dev(nd); + + return 0; +} + +static struct ncsi_aen_handler { + unsigned char nah_type; + int (*nah_handler)(struct ncsi_dev_priv *ndp, + struct ncsi_aen_pkt_hdr *h); +} ncsi_aen_handlers[] = { + { NCSI_PKT_AEN_LSC, ncsi_aen_handler_lsc }, + { NCSI_PKT_AEN_CR, ncsi_aen_handler_cr }, + { NCSI_PKT_AEN_HNCDSC, ncsi_aen_handler_hncdsc }, + { 0, NULL } +}; + +int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb) +{ + struct ncsi_aen_pkt_hdr *h; + struct ncsi_aen_handler *nah; + int ret; + + /* Find the handler */ + h = (struct ncsi_aen_pkt_hdr *)skb_network_header(skb); + nah = ncsi_aen_handlers; + while (nah->nah_handler) { + if (nah->nah_type == h->type) + break; + + nah++; + } + + if (!nah->nah_handler) { + pr_warn("NCSI: Received unrecognized AEN packet (0x%x)\n", + h->type); + return -ENOENT; + } + + ret = nah->nah_handler(ndp, h); + consume_skb(skb); + + return ret; +} diff --git a/net/ncsi/ncsi-cmd.c b/net/ncsi/ncsi-cmd.c new file mode 100644 index 00000000000000..d8efa25f44f4c2 --- /dev/null +++ b/net/ncsi/ncsi-cmd.c @@ -0,0 +1,380 @@ +/* + * Copyright Gavin Shan, IBM Corporation 2015. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "internal.h" +#include "ncsi-pkt.h" + +/* + * This function should be called after the data area has been + * populated completely. + */ +static int ncsi_cmd_build_header(struct ncsi_pkt_hdr *h, + struct ncsi_cmd_arg *nca) +{ + __be32 csum, *checksum; + __be32 low, high; + unsigned char *stream; + int i; + + h->mc_id = 0; + h->revision = NCSI_PKT_REVISION; + h->reserved = 0; + h->id = nca->nca_id; + h->type = nca->nca_type; + h->channel = NCSI_TO_CHANNEL(nca->nca_package, + nca->nca_channel); + h->length = htons(nca->nca_payload); + h->reserved1[0] = 0; + h->reserved1[1] = 0; + + /* Calculate the checksum */ + csum = 0; + stream = (unsigned char *)h; + for (i = 0; i < sizeof(*h) + nca->nca_payload; i += 2) { + high = stream[i]; + low = stream[i + 1]; + csum += ((high << 8) | low); + } + + /* Fill with the calculated checksum */ + checksum = (__be32 *)((void *)(h + 1) + nca->nca_payload); + csum = (~csum + 1); + *checksum = htonl(csum); + + return 0; +} + +static int ncsi_cmd_handler_default(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_pkt *cmd; + + if (!nca) + return 0; + + cmd = (struct ncsi_cmd_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_sp(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_sp_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_sp_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->hw_arbitration = nca->nca_bytes[0]; + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_dc(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_dc_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_dc_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->ald = nca->nca_bytes[0]; + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_rc(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_rc_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_rc_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_ae(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_ae_pkt *cmd; + + if (!nca) + return 8; + + cmd = (struct ncsi_cmd_ae_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->mc_id = nca->nca_bytes[0]; + cmd->mode = htonl(nca->nca_dwords[1]); + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_sl(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_sl_pkt *cmd; + + if (!nca) + return 8; + + cmd = (struct ncsi_cmd_sl_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->mode = htonl(nca->nca_dwords[0]); + cmd->oem_mode = htonl(nca->nca_dwords[1]); + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_svf(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_svf_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_svf_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->vlan = htons(nca->nca_words[0]); + cmd->index = nca->nca_bytes[2]; + cmd->enable = nca->nca_bytes[3]; + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_ev(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_ev_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_ev_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->mode = nca->nca_bytes[0]; + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_sma(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_sma_pkt *cmd; + int i; + + if (!nca) + return 8; + + cmd = (struct ncsi_cmd_sma_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + for (i = 0; i < 6; i++) + cmd->mac[i] = nca->nca_bytes[i]; + cmd->index = nca->nca_bytes[6]; + cmd->at_e = nca->nca_bytes[7]; + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_ebf(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_ebf_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_ebf_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->mode = htonl(nca->nca_dwords[0]); + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_egmf(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_egmf_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_egmf_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->mode = htonl(nca->nca_dwords[0]); + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static int ncsi_cmd_handler_snfc(struct sk_buff *skb, + struct ncsi_cmd_arg *nca) +{ + struct ncsi_cmd_snfc_pkt *cmd; + + if (!nca) + return 4; + + cmd = (struct ncsi_cmd_snfc_pkt *)skb_put(skb, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->mode = nca->nca_bytes[0]; + return ncsi_cmd_build_header(&cmd->cmd.common, nca); +} + +static struct ncsi_cmd_handler { + unsigned char nch_type; + int (*nch_handler)(struct sk_buff *skb, + struct ncsi_cmd_arg *nca); +} ncsi_cmd_handlers[] = { + { NCSI_PKT_CMD_CIS, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_SP, ncsi_cmd_handler_sp }, + { NCSI_PKT_CMD_DP, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_EC, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_DC, ncsi_cmd_handler_dc }, + { NCSI_PKT_CMD_RC, ncsi_cmd_handler_rc }, + { NCSI_PKT_CMD_ECNT, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_DCNT, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_AE, ncsi_cmd_handler_ae }, + { NCSI_PKT_CMD_SL, ncsi_cmd_handler_sl }, + { NCSI_PKT_CMD_GLS, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_SVF, ncsi_cmd_handler_svf }, + { NCSI_PKT_CMD_EV, ncsi_cmd_handler_ev }, + { NCSI_PKT_CMD_DV, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_SMA, ncsi_cmd_handler_sma }, + { NCSI_PKT_CMD_EBF, ncsi_cmd_handler_ebf }, + { NCSI_PKT_CMD_DBF, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_EGMF, ncsi_cmd_handler_egmf }, + { NCSI_PKT_CMD_DGMF, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_SNFC, ncsi_cmd_handler_snfc }, + { NCSI_PKT_CMD_GVI, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_GC, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_GP, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_GCPS, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_GNS, ncsi_cmd_handler_default }, + { NCSI_PKT_CMD_GNPTS, ncsi_cmd_handler_default }, + { 0, NULL } +}; + +static struct ncsi_req *ncsi_alloc_cmd_req(struct ncsi_cmd_arg *nca) +{ + struct ncsi_dev_priv *ndp = nca->nca_ndp; + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct net_device *dev = nd->nd_dev; + int hlen = LL_RESERVED_SPACE(dev); + int tlen = dev->needed_tailroom; + int len = hlen + tlen; + struct sk_buff *skb; + struct ncsi_req *nr; + + nr = ncsi_alloc_req(ndp); + if (!nr) + return NULL; + + /* NCSI command packet has 16-bytes header, payload, + * 4-bytes checksum and optional padding. + */ + len += sizeof(struct ncsi_cmd_pkt_hdr); + len += 4; + if (nca->nca_payload < 26) + len += 26; + else + len += nca->nca_payload; + + /* Allocate skb */ + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + ncsi_free_req(nr, false, false); + return NULL; + } + + nr->nr_cmd = skb; + skb_reserve(skb, hlen); + skb_reset_network_header(skb); + + skb->dev = dev; + skb->protocol = htons(ETH_P_NCSI); + + if (nca->nca_nlh) { + NCSI_CB(skb).nsp_valid = 1; + memcpy(&NCSI_CB(skb).nsp_nlh, nca->nca_nlh, + nlmsg_total_size(sizeof(struct ncsi_msg))); + } else { + NCSI_CB(skb).nsp_valid = 0; + } + + return nr; +} + +int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca) +{ + struct ncsi_req *nr; + struct ethhdr *eh; + struct ncsi_cmd_handler *nch; + int i, ret; + + /* Search for the handler */ + nch = ncsi_cmd_handlers; + while (nch->nch_handler) { + if (nch->nch_type == nca->nca_type) + break; + nch++; + } + + if (!nch->nch_handler) { + pr_info("%s: Cannot send packet with type 0x%x\n", + __func__, nca->nca_type); + return -ENOENT; + } + + /* Get packet payload length and allocate the request */ + nca->nca_payload = nch->nch_handler(NULL, NULL); + nr = ncsi_alloc_cmd_req(nca); + if (!nr) + return -ENOMEM; + + /* Prepare the packet */ + nca->nca_id = nr->nr_id; + ret = nch->nch_handler(nr->nr_cmd, nca); + if (ret) + goto out; + + /* Fill the ethernet header */ + eh = (struct ethhdr *)skb_push(nr->nr_cmd, sizeof(*eh)); + eh->h_proto = htons(ETH_P_NCSI); + for (i = 0; i < ETH_ALEN; i++) { + eh->h_dest[i] = 0xff; + eh->h_source[i] = 0xff; + } + + /* Send NCSI packet */ + skb_get(nr->nr_cmd); + ret = dev_queue_xmit_sk(NULL, nr->nr_cmd); + if (ret) + goto out; + + /* Start the timer for the request that might not have + * corresponding response. I'm not sure 1 second delay + * here is enough. Anyway, NCSI is internal network, so + * the responsiveness should be as fast as enough. + */ + nr->nr_timer_enabled = true; + mod_timer(&nr->nr_timer, jiffies + 1 * HZ); + + return 0; +out: + ncsi_free_req(nr, false, false); + return ret; +} diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c new file mode 100644 index 00000000000000..eb71c71cfae515 --- /dev/null +++ b/net/ncsi/ncsi-manage.c @@ -0,0 +1,914 @@ +/* + * Copyright Gavin Shan, IBM Corporation 2015. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ncsi-pkt.h" +#include "internal.h" + +LIST_HEAD(ncsi_dev_list); +DEFINE_SPINLOCK(ncsi_dev_lock); + +int ncsi_find_channel_filter(struct ncsi_channel *nc, int table, void *data) +{ + struct ncsi_channel_filter *ncf; + int idx, entry_size; + void *bitmap; + + switch (table) { + case NCSI_FILTER_VLAN: + entry_size = 2; + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + case NCSI_FILTER_MIXED: + entry_size = 6; + break; + default: + return -EINVAL; + } + + /* Check if the filter table has been initialized */ + ncf = nc->nc_filters[table]; + if (!ncf) + return -ENODEV; + + /* Check the valid entries one by one */ + bitmap = (void *)&ncf->ncf_bitmap; + idx = -1; + while ((idx = find_next_bit(bitmap, ncf->ncf_total, idx+1)) + < ncf->ncf_total) { + if (!memcmp(ncf->ncf_data + entry_size * idx, data, entry_size)) + return idx; + } + + return -ENOENT; +} + +int ncsi_add_channel_filter(struct ncsi_channel *nc, int table, void *data) +{ + struct ncsi_channel_filter *ncf; + int idx, entry_size; + void *bitmap; + + /* Needn't add it if it's already existing */ + idx = ncsi_find_channel_filter(nc, table, data); + if (idx >= 0) + return idx; + + switch (table) { + case NCSI_FILTER_VLAN: + entry_size = 2; + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + case NCSI_FILTER_MIXED: + entry_size = 6; + break; + default: + return -EINVAL; + } + + /* Check if the filter table has been initialized */ + ncf = nc->nc_filters[table]; + if (!ncf) + return -ENODEV; + + /* Propagate the filter */ + bitmap = (void *)&ncf->ncf_bitmap; + do { + idx = find_next_zero_bit(bitmap, ncf->ncf_total, 0); + if (idx >= ncf->ncf_total) + return -ENOSPC; + } while (test_and_set_bit(idx, bitmap)); + + memcpy(ncf->ncf_data + entry_size * idx, data, entry_size); + return idx; +} + +int ncsi_del_channel_filter(struct ncsi_channel *nc, int table, int index) +{ + struct ncsi_channel_filter *ncf; + int entry_size; + void *bitmap; + + switch (table) { + case NCSI_FILTER_VLAN: + entry_size = 2; + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + case NCSI_FILTER_MIXED: + entry_size = 6; + break; + default: + return -EINVAL; + } + + /* Check if the filter table has been initialized */ + ncf = nc->nc_filters[table]; + if (!ncf || index >= ncf->ncf_total) + return -ENODEV; + + /* Check if the entry is valid */ + bitmap = (void *)&ncf->ncf_bitmap; + if (test_and_clear_bit(index, bitmap)) + memset(ncf->ncf_data + entry_size * index, 0, entry_size); + + return 0; +} + +struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np, unsigned char id) +{ + struct ncsi_channel *nc, *tmp; + int index; + + nc = kzalloc(sizeof(*nc), GFP_ATOMIC); + if (!nc) { + pr_warn("%s: Out of memory !\n", __func__); + return NULL; + } + + nc->nc_package = np; + nc->nc_id = id; + for (index = 0; index < NCSI_CAP_MAX; index++) + nc->nc_caps[index].ncc_index = index; + for (index = 0; index < NCSI_MODE_MAX; index++) + nc->nc_modes[index].ncm_index = index; + + spin_lock(&np->np_channel_lock); + tmp = ncsi_find_channel(np, id); + if (tmp) { + spin_unlock(&np->np_channel_lock); + kfree(nc); + return tmp; + } + list_add_tail_rcu(&nc->nc_node, &np->np_channels); + spin_unlock(&np->np_channel_lock); + + atomic_inc(&np->np_channel_num); + return nc; +} + +struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np, + unsigned char id) +{ + struct ncsi_channel *nc; + + NCSI_FOR_EACH_CHANNEL(np, nc) { + if (nc->nc_id == id) + return nc; + } + + return NULL; +} + +static void ncsi_release_channel(struct ncsi_channel *nc) +{ + struct ncsi_dev_priv *ndp = nc->nc_package->np_ndp; + struct ncsi_package *np = nc->nc_package; + struct ncsi_channel_filter *ncf; + int i; + + /* Release filters */ + for (i = 0; i < NCSI_FILTER_MAX; i++) { + ncf = nc->nc_filters[i]; + if (!ncf) + continue; + + nc->nc_filters[i] = NULL; + kfree(ncf); + } + + /* Update active channel if necessary */ + if (ndp->ndp_active_channel == nc) { + ndp->ndp_active_package = NULL; + ndp->ndp_active_channel = NULL; + } + + /* Remove and free channel */ + list_del_rcu(&nc->nc_node); + kfree(nc); + BUG_ON(atomic_dec_return(&np->np_channel_num) < 0); +} + +struct ncsi_package *ncsi_add_package(struct ncsi_dev_priv *ndp, + unsigned char id) +{ + struct ncsi_package *np, *tmp; + + np = kzalloc(sizeof(*np), GFP_ATOMIC); + if (!np) { + pr_warn("%s: Out of memory !\n", __func__); + return NULL; + } + + np->np_id = id; + np->np_ndp = ndp; + spin_lock_init(&np->np_channel_lock); + INIT_LIST_HEAD(&np->np_channels); + + spin_lock(&ndp->ndp_package_lock); + tmp = ncsi_find_package(ndp, id); + if (tmp) { + spin_unlock(&ndp->ndp_package_lock); + kfree(np); + return tmp; + } + list_add_tail_rcu(&np->np_node, &ndp->ndp_packages); + spin_unlock(&ndp->ndp_package_lock); + + atomic_inc(&ndp->ndp_package_num); + return np; +} + +struct ncsi_package *ncsi_find_package(struct ncsi_dev_priv *ndp, + unsigned char id) +{ + struct ncsi_package *np; + + NCSI_FOR_EACH_PACKAGE(ndp, np) { + if (np->np_id == id) + return np; + } + + return NULL; +} + +void ncsi_release_package(struct ncsi_package *np) +{ + struct ncsi_dev_priv *ndp = np->np_ndp; + struct ncsi_channel *nc, *tmp; + + /* Release all child channels */ + spin_lock(&np->np_channel_lock); + list_for_each_entry_safe(nc, tmp, &np->np_channels, nc_node) + ncsi_release_channel(nc); + spin_unlock(&np->np_channel_lock); + + /* Clear active package if necessary */ + if (ndp->ndp_active_package == np) { + ndp->ndp_active_package = NULL; + ndp->ndp_active_channel = NULL; + } + + /* Remove and free package */ + list_del_rcu(&np->np_node); + kfree(np); + + /* Decrease number of packages */ + BUG_ON(atomic_dec_return(&ndp->ndp_package_num) < 0); +} + +void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp, + unsigned char id, + struct ncsi_package **np, + struct ncsi_channel **nc) +{ + struct ncsi_package *p; + struct ncsi_channel *c; + + p = ncsi_find_package(ndp, NCSI_PACKAGE_INDEX(id)); + c = p ? ncsi_find_channel(p, NCSI_CHANNEL_INDEX(id)) : NULL; + + if (np) + *np = p; + if (nc) + *nc = c; +} + +/* + * For two consective NCSI commands, the packet IDs shouldn't be + * same. Otherwise, the bogus response might be replied. So the + * available IDs are allocated in round-robin fasion. + */ +struct ncsi_req *ncsi_alloc_req(struct ncsi_dev_priv *ndp) +{ + struct ncsi_req *nr = NULL; + int idx, limit = 256; + + spin_lock(&ndp->ndp_req_lock); + + /* Check if there is one available request until the ceiling */ + for (idx = atomic_read(&ndp->ndp_last_req_idx); + !nr && idx < limit; idx++) { + if (ndp->ndp_reqs[idx].nr_used) + continue; + + ndp->ndp_reqs[idx].nr_used = true; + nr = &ndp->ndp_reqs[idx]; + atomic_inc(&ndp->ndp_last_req_idx); + if (atomic_read(&ndp->ndp_last_req_idx) >= limit) + atomic_set(&ndp->ndp_last_req_idx, 0); + } + + /* Fail back to check from the starting cursor */ + for (idx = 0; !nr && idx < atomic_read(&ndp->ndp_last_req_idx); idx++) { + if (ndp->ndp_reqs[idx].nr_used) + continue; + + ndp->ndp_reqs[idx].nr_used = true; + nr = &ndp->ndp_reqs[idx]; + atomic_inc(&ndp->ndp_last_req_idx); + if (atomic_read(&ndp->ndp_last_req_idx) >= limit) + atomic_set(&ndp->ndp_last_req_idx, 0); + } + + spin_unlock(&ndp->ndp_req_lock); + return nr; +} + +void ncsi_free_req(struct ncsi_req *nr, bool check, bool timeout) +{ + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct sk_buff *cmd, *rsp; + + if (nr->nr_timer_enabled) { + nr->nr_timer_enabled = false; + del_timer_sync(&nr->nr_timer); + } + + spin_lock(&ndp->ndp_req_lock); + cmd = nr->nr_cmd; + rsp = nr->nr_rsp; + nr->nr_cmd = NULL; + nr->nr_rsp = NULL; + nr->nr_used = false; + spin_unlock(&ndp->ndp_req_lock); + + /* If the NCSI command was sent because of netlink + * messages, we need reply with the result or error. + */ + if (check && cmd && NCSI_CB(cmd).nsp_valid) + ncsi_netlink_reply(&NCSI_CB(cmd).nsp_nlh, + NCSI_CB(cmd).nsp_portid, timeout); + + if (check && cmd && atomic_dec_return(&ndp->ndp_pending_reqs) == 0) + schedule_work(&ndp->ndp_work); + /* Release command and response */ + consume_skb(cmd); + consume_skb(rsp); +} + +struct ncsi_dev *ncsi_find_dev(struct net_device *dev) +{ + struct ncsi_dev_priv *ndp; + + NCSI_FOR_EACH_DEV(ndp) { + if (ndp->ndp_ndev.nd_dev == dev) + return &ndp->ndp_ndev; + } + + return NULL; +} + +static int ncsi_select_active_channel(struct ncsi_dev_priv *ndp) +{ + struct ncsi_package *np; + struct ncsi_channel *nc; + + /* For now, we simply choose the first valid channel as active one. + * There might be more factors, like the channel's capacity, can + * be considered to pick the active channel in future. + */ + NCSI_FOR_EACH_PACKAGE(ndp, np) { + NCSI_FOR_EACH_CHANNEL(np, nc) { + ndp->ndp_active_package = np; + ndp->ndp_active_channel = nc; + return 0; + } + } + + return -ENXIO; +} + +static void ncsi_dev_config(struct ncsi_dev_priv *ndp) +{ + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct net_device *dev = nd->nd_dev; + struct ncsi_package *np = ndp->ndp_active_package; + struct ncsi_channel *nc = ndp->ndp_active_channel; + struct ncsi_cmd_arg nca; + unsigned char index; + int ret; + + nca.nca_ndp = ndp; + nca.nca_nlh = NULL; + + /* When we're reconfiguring the active channel, the active package + * should be selected and the old setting on the active channel + * should be cleared. + */ + switch (nd->nd_state) { + case ncsi_dev_state_config: + case ncsi_dev_state_config_sp: + atomic_set(&ndp->ndp_pending_reqs, 1); + + /* Select the specific package */ + nca.nca_type = NCSI_PKT_CMD_SP; + nca.nca_bytes[0] = 1; + nca.nca_package = np->np_id; + nca.nca_channel = 0x1f; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + + nd->nd_state = ncsi_dev_state_config_cis; + break; + case ncsi_dev_state_config_cis: + atomic_set(&ndp->ndp_pending_reqs, 1); + + /* Clear initial state */ + nca.nca_type = NCSI_PKT_CMD_CIS; + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + + nd->nd_state = ncsi_dev_state_config_sma; + break; + case ncsi_dev_state_config_sma: + case ncsi_dev_state_config_ebf: + case ncsi_dev_state_config_ecnt: + case ncsi_dev_state_config_ec: + case ncsi_dev_state_config_gls: + atomic_set(&ndp->ndp_pending_reqs, 1); + + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + + /* Use first entry in unicast filter table. Note that + * the MAC filter table starts from entry 1 instead of + * 0. + */ + if (nd->nd_state == ncsi_dev_state_config_sma) { + nca.nca_type = NCSI_PKT_CMD_SMA; + for (index = 0; index < 6; index++) + nca.nca_bytes[index] = dev->dev_addr[index]; + nca.nca_bytes[6] = 0x1; + nca.nca_bytes[7] = 0x1; + nd->nd_state = ncsi_dev_state_config_ebf; + } else if (nd->nd_state == ncsi_dev_state_config_ebf) { + nca.nca_type = NCSI_PKT_CMD_EBF; + nca.nca_dwords[0] = nc->nc_caps[NCSI_CAP_BC].ncc_cap; + nd->nd_state = ncsi_dev_state_config_ecnt; + } else if (nd->nd_state == ncsi_dev_state_config_ecnt) { + nca.nca_type = NCSI_PKT_CMD_ECNT; + nd->nd_state = ncsi_dev_state_config_ec; + } else if (nd->nd_state == ncsi_dev_state_config_ec) { + nca.nca_type = NCSI_PKT_CMD_EC; + nd->nd_state = ncsi_dev_state_config_gls; + } else if (nd->nd_state == ncsi_dev_state_config_gls) { + nca.nca_type = NCSI_PKT_CMD_GLS; + nd->nd_state = ncsi_dev_state_config_done; + } + + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + + break; + case ncsi_dev_state_config_done: + nd->nd_state = ncsi_dev_state_functional; + nd->nd_link_up = 0; + if (nc->nc_modes[NCSI_MODE_LINK].ncm_data[2] & 0x1) + nd->nd_link_up = 1; + + if (!(ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE)) + nd->nd_handler(nd); + ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE; + + break; + default: + pr_debug("%s: Unrecognized NCSI dev state 0x%x\n", + __func__, nd->nd_state); + return; + } + + return; + +error: + nd->nd_state = ncsi_dev_state_functional; + nd->nd_link_up = 0; + ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE; + nd->nd_handler(nd); +} + +static void ncsi_dev_start(struct ncsi_dev_priv *ndp) +{ + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_cmd_arg nca; + unsigned char index; + int ret; + + nca.nca_ndp = ndp; + nca.nca_nlh = NULL; + switch (nd->nd_state) { + case ncsi_dev_state_start: + nd->nd_state = ncsi_dev_state_start_deselect; + /* Fall through */ + case ncsi_dev_state_start_deselect: + atomic_set(&ndp->ndp_pending_reqs, 8); + + /* Deselect all possible packages */ + nca.nca_type = NCSI_PKT_CMD_DP; + nca.nca_channel = 0x1f; + for (index = 0; index < 8; index++) { + nca.nca_package = index; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + } + + nd->nd_state = ncsi_dev_state_start_package; + break; + case ncsi_dev_state_start_package: + atomic_set(&ndp->ndp_pending_reqs, 16); + + /* Select all possible packages */ + nca.nca_type = NCSI_PKT_CMD_SP; + nca.nca_bytes[0] = 1; + nca.nca_channel = 0x1f; + for (index = 0; index < 8; index++) { + nca.nca_package = index; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + } + + /* Disable all possible packages */ + nca.nca_type = NCSI_PKT_CMD_DP; + for (index = 0; index < 8; index++) { + nca.nca_package = index; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + } + + nd->nd_state = ncsi_dev_state_start_channel; + break; + case ncsi_dev_state_start_channel: + /* The available packages should have been detected. To + * iterate every package to probe its channels. + */ + if (!ndp->ndp_active_package) { + ndp->ndp_active_package = list_first_or_null_rcu( + &ndp->ndp_packages, struct ncsi_package, + np_node); + if (!ndp->ndp_active_package) + goto error; + } else { + if (list_is_last(&ndp->ndp_active_package->np_node, + &ndp->ndp_packages)) { + nd->nd_state = ncsi_dev_state_start_active; + goto choose_active_channel; + } + + ndp->ndp_active_package = list_entry_rcu( + ndp->ndp_active_package->np_node.next, + struct ncsi_package, np_node); + } + /* Fall through */ + case ncsi_dev_state_start_sp: + atomic_set(&ndp->ndp_pending_reqs, 1); + + /* Select the specific package */ + nca.nca_type = NCSI_PKT_CMD_SP; + nca.nca_bytes[0] = 1; + nca.nca_package = ndp->ndp_active_package->np_id; + nca.nca_channel = 0x1f; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + + nd->nd_state = ncsi_dev_state_start_cis; + break; + case ncsi_dev_state_start_cis: + atomic_set(&ndp->ndp_pending_reqs, 0x20); + + /* Clear initial state */ + nca.nca_type = NCSI_PKT_CMD_CIS; + nca.nca_package = ndp->ndp_active_package->np_id; + for (index = 0; index < 0x20; index++) { + nca.nca_channel = index; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + } + + nd->nd_state = ncsi_dev_state_start_gvi; + break; + case ncsi_dev_state_start_gvi: + case ncsi_dev_state_start_gc: + /* The available channels of the active package should have + * been populated. + */ + np = ndp->ndp_active_package; + atomic_set(&ndp->ndp_pending_reqs, + atomic_read(&np->np_channel_num)); + + /* Get version information or get capacity */ + if (nd->nd_state == ncsi_dev_state_start_gvi) + nca.nca_type = NCSI_PKT_CMD_GVI; + else + nca.nca_type = NCSI_PKT_CMD_GC; + + nca.nca_package = np->np_id; + NCSI_FOR_EACH_CHANNEL(np, nc) { + nca.nca_channel = nc->nc_id; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + } + + if (nd->nd_state == ncsi_dev_state_start_gvi) + nd->nd_state = ncsi_dev_state_start_gc; + else + nd->nd_state = ncsi_dev_state_start_dp; + break; + case ncsi_dev_state_start_dp: + atomic_set(&ndp->ndp_pending_reqs, 1); + + /* Deselect the active package */ + nca.nca_type = NCSI_PKT_CMD_DP; + nca.nca_package = ndp->ndp_active_package->np_id; + nca.nca_channel = 0x1f; + ret = ncsi_xmit_cmd(&nca); + if (ret) + goto error; + + nd->nd_state = ncsi_dev_state_start_channel; + break; + case ncsi_dev_state_start_active: +choose_active_channel: + /* All packages and channels should have been populated. Also, + * the information for all channels should have been retrieved. + */ + ndp->ndp_active_package = NULL; + ncsi_select_active_channel(ndp); + if (!ndp->ndp_active_package || + !ndp->ndp_active_channel) + goto error; + + /* To configure the active channel */ + nd->nd_state = ncsi_dev_state_config_sma; + ncsi_dev_config(ndp); + default: + pr_debug("%s: Unrecognized NCSI dev state 0x%x\n", + __func__, nd->nd_state); + } + + return; + +error: + ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE; + nd->nd_state = ncsi_dev_state_functional; + nd->nd_link_up = 0; + nd->nd_handler(nd); +} + +static void ncsi_dev_stop(struct ncsi_dev_priv *ndp) +{ + struct ncsi_dev *nd = &ndp->ndp_ndev; + struct ncsi_package *np, *tmp; + struct ncsi_channel *nc; + struct ncsi_cmd_arg nca; + int ret; + + nca.nca_ndp = ndp; + nca.nca_nlh = NULL; + switch (nd->nd_state) { + case ncsi_dev_state_stop: + /* If there're no active channel, we're done */ + if (!ndp->ndp_active_channel) { + nd->nd_state = ncsi_dev_state_stop_done; + goto done; + } + + nd->nd_state = ncsi_dev_state_stop_select; + /* Fall through */ + case ncsi_dev_state_stop_select: + case ncsi_dev_state_stop_dcnt: + case ncsi_dev_state_stop_dc: + case ncsi_dev_state_stop_deselect: + atomic_set(&ndp->ndp_pending_reqs, 1); + + np = ndp->ndp_active_package; + nc = ndp->ndp_active_channel; + nca.nca_package = np->np_id; + if (nd->nd_state == ncsi_dev_state_stop_select) { + nca.nca_type = NCSI_PKT_CMD_SP; + nca.nca_channel = 0x1f; + nca.nca_bytes[0] = 1; + nd->nd_state = ncsi_dev_state_stop_dcnt; + } else if (nd->nd_state == ncsi_dev_state_stop_dcnt) { + nca.nca_type = NCSI_PKT_CMD_DCNT; + nca.nca_channel = nc->nc_id; + nd->nd_state = ncsi_dev_state_stop_dc; + } else if (nd->nd_state == ncsi_dev_state_stop_dc) { + nca.nca_type = NCSI_PKT_CMD_DC; + nca.nca_channel = nc->nc_id; + nca.nca_bytes[0] = 1; + nd->nd_state = ncsi_dev_state_stop_deselect; + } else if (nd->nd_state == ncsi_dev_state_stop_deselect) { + nca.nca_type = NCSI_PKT_CMD_DP; + nca.nca_channel = 0x1f; + nd->nd_state = ncsi_dev_state_stop_done; + } + + ret = ncsi_xmit_cmd(&nca); + if (ret) { + nd->nd_state = ncsi_dev_state_stop_done; + goto done; + } + + break; + case ncsi_dev_state_stop_done: +done: + spin_lock(&ndp->ndp_package_lock); + list_for_each_entry_safe(np, tmp, &ndp->ndp_packages, np_node) + ncsi_release_package(np); + spin_unlock(&ndp->ndp_package_lock); + + if (!(ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE)) { + nd->nd_state = ncsi_dev_state_functional; + nd->nd_link_up = 0; + nd->nd_handler(nd); + } else { + nd->nd_state = ncsi_dev_state_start; + ncsi_dev_start(ndp); + } + + break; + default: + pr_warn("%s: Unsupported NCSI dev state 0x%x\n", + __func__, nd->nd_state); + } +} + +static void ncsi_dev_work(struct work_struct *work) +{ + struct ncsi_dev_priv *ndp = container_of(work, struct ncsi_dev_priv, + ndp_work); + struct ncsi_dev *nd = &ndp->ndp_ndev; + + switch (nd->nd_state & ncsi_dev_state_major) { + case ncsi_dev_state_start: + ncsi_dev_start(ndp); + break; + case ncsi_dev_state_stop: + ncsi_dev_stop(ndp); + break; + case ncsi_dev_state_config: + ncsi_dev_config(ndp); + break; + default: + pr_warn("%s: Unsupported NCSI dev state 0x%x\n", + __func__, nd->nd_state); + } +} + +static void ncsi_req_timeout(unsigned long data) +{ + struct ncsi_req *nr = (struct ncsi_req *)data; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + + /* If the request already had associated response, + * let the response handler to release it. + */ + spin_lock(&ndp->ndp_req_lock); + nr->nr_timer_enabled = false; + if (nr->nr_rsp || !nr->nr_cmd) { + spin_unlock(&ndp->ndp_req_lock); + return; + } + spin_unlock(&ndp->ndp_req_lock); + + /* Release the request */ + ncsi_free_req(nr, true, true); +} + +struct ncsi_dev *ncsi_register_dev(struct net_device *dev, + void (*handler)(struct ncsi_dev *ndev)) +{ + struct ncsi_dev_priv *ndp; + struct ncsi_dev *nd; + int idx; + + /* Check if the device has been registered or not */ + nd = ncsi_find_dev(dev); + if (nd) + return nd; + + /* Create NCSI device */ + ndp = kzalloc(sizeof(*ndp), GFP_ATOMIC); + if (!ndp) { + pr_warn("%s: Out of memory !\n", __func__); + return NULL; + } + + nd = &ndp->ndp_ndev; + nd->nd_state = ncsi_dev_state_registered; + nd->nd_dev = dev; + nd->nd_handler = handler; + + /* Initialize private NCSI device */ + spin_lock_init(&ndp->ndp_package_lock); + INIT_LIST_HEAD(&ndp->ndp_packages); + INIT_WORK(&ndp->ndp_work, ncsi_dev_work); + spin_lock_init(&ndp->ndp_req_lock); + atomic_set(&ndp->ndp_last_req_idx, 0); + for (idx = 0; idx < 256; idx++) { + ndp->ndp_reqs[idx].nr_id = idx; + ndp->ndp_reqs[idx].nr_ndp = ndp; + setup_timer(&ndp->ndp_reqs[idx].nr_timer, ncsi_req_timeout, + (unsigned long)&ndp->ndp_reqs[idx]); + } + + spin_lock(&ncsi_dev_lock); + list_add_tail_rcu(&ndp->ndp_node, &ncsi_dev_list); + spin_unlock(&ncsi_dev_lock); + + /* Register NCSI packet receiption handler */ + ndp->ndp_ptype.type = cpu_to_be16(ETH_P_NCSI); + ndp->ndp_ptype.func = ncsi_rcv_rsp; + ndp->ndp_ptype.dev = dev; + dev_add_pack(&ndp->ndp_ptype); + + return nd; +} +EXPORT_SYMBOL_GPL(ncsi_register_dev); + +int ncsi_start_dev(struct ncsi_dev *nd) +{ + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); + + if (nd->nd_state != ncsi_dev_state_registered && + nd->nd_state != ncsi_dev_state_functional) + return -ENOTTY; + + nd->nd_state = ncsi_dev_state_start; + schedule_work(&ndp->ndp_work); + + return 0; +} +EXPORT_SYMBOL_GPL(ncsi_start_dev); + +int ncsi_config_dev(struct ncsi_dev *nd) +{ + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); + + if (nd->nd_state != ncsi_dev_state_functional) + return -ENOTTY; + + nd->nd_state = ncsi_dev_state_config; + schedule_work(&ndp->ndp_work); + + return 0; +} + +int ncsi_stop_dev(struct ncsi_dev *nd) +{ + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); + + if (nd->nd_state != ncsi_dev_state_functional) + return -ENOTTY; + + nd->nd_state = ncsi_dev_state_stop; + schedule_work(&ndp->ndp_work); + + return 0; +} +EXPORT_SYMBOL_GPL(ncsi_stop_dev); + +void ncsi_unregister_dev(struct ncsi_dev *nd) +{ + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); + struct ncsi_package *np, *tmp; + + dev_remove_pack(&ndp->ndp_ptype); + + spin_lock(&ndp->ndp_package_lock); + list_for_each_entry_safe(np, tmp, &ndp->ndp_packages, np_node) + ncsi_release_package(np); + spin_unlock(&ndp->ndp_package_lock); +} +EXPORT_SYMBOL_GPL(ncsi_unregister_dev); diff --git a/net/ncsi/ncsi-netlink.c b/net/ncsi/ncsi-netlink.c new file mode 100644 index 00000000000000..bcabd397d50bc9 --- /dev/null +++ b/net/ncsi/ncsi-netlink.c @@ -0,0 +1,1042 @@ +/* + * Copyright Gavin Shan, IBM Corporation 2015. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "internal.h" +#include "ncsi-pkt.h" + +static struct sock *ncsi_sock; + +static void ncsi_netlink_error(struct nlmsghdr *h, + unsigned int portid, + int errcode) +{ + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg *src, *dst; + int ret; + + skb = nlmsg_new(sizeof(*dst), GFP_ATOMIC); + if (!skb) + return; + + /* The request might not have the common data instance */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + if (nlmsg_len(h) >= sizeof(*src)) { + src = nlmsg_data(h); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + dst->nm_errcode = errcode; + } else { + memset(dst, 0, sizeof(*dst)); + dst->nm_flag = NCSI_FLAG_RESPONSE; + dst->nm_errcode = errcode; + } + + nlmsg_end(skb, nlh); + ret = nlmsg_notify(ncsi_sock, skb, portid, 0, 1, 0); + if (ret) { + pr_warn("%s: Error %d sending message (%d)\n", + __func__, ret, errcode); + nlmsg_free(skb); + } +} + +static struct sk_buff *ncsi_netlink_get_layout(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg nm, *src, *dst; + size_t size = sizeof(*dst);; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + if (!ndp) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + NCSI_FOR_EACH_PACKAGE(ndp, np) + size += nla_total_size(sizeof(nm)) * + atomic_read(&np->np_channel_num); + if (size <= sizeof(nm)) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + skb = nlmsg_new(size, GFP_KERNEL); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(nm), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* All available channels */ + NCSI_FOR_EACH_PACKAGE(ndp, np) { + NCSI_FOR_EACH_CHANNEL(np, nc) { + nm.nm_flag = 0; + if (nc == ndp->ndp_active_channel) + nm.nm_flag = NCSI_FLAG_ACTIVE_CHANNEL; + nm.nm_ifindex = src->nm_ifindex; + nm.nm_package_id = np->np_id; + nm.nm_channel_id = nc->nc_id; + nm.nm_index = 0; + nm.nm_index = NCSI_SUCCESS; + + if (nla_put(skb, 0, sizeof(nm), &nm)) { + *errcode = NCSI_ERR_INTERNAL; + nlmsg_free(skb); + return NULL; + } + } + } + + nlmsg_end(skb, nlh); + return skb; +} + +static struct sk_buff *ncsi_netlink_get_version(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_version *ncv; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg *src, *dst; + size_t size = sizeof(*dst);; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + size += nla_total_size(sizeof(*ncv)); + skb = nlmsg_new(size, GFP_KERNEL); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill channel version */ + ncv = &nc->nc_version; + if (nla_put(skb, 0, sizeof(*ncv), ncv)) { + *errcode = NCSI_ERR_INTERNAL; + nlmsg_free(skb); + return NULL; + } + + nlmsg_end(skb, nlh); + return skb; +} + +static struct sk_buff *ncsi_netlink_get_cap(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_cap *ncc; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg *src, *dst; + size_t size = sizeof(*dst); + int index; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + if (src->nm_index > NCSI_CAP_MAX) { + *errcode = NCSI_ERR_PARAM; + return NULL; + } else if (src->nm_index == NCSI_CAP_MAX) { + size += nla_total_size(sizeof(*ncc)) * NCSI_CAP_MAX; + } else { + size += nla_total_size(sizeof(*ncc)); + } + skb = nlmsg_new(size, GFP_KERNEL); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill one or all capabilities */ + for (index = 0; index < NCSI_CAP_MAX; index++) { + ncc = &nc->nc_caps[index]; + if (src->nm_index == NCSI_CAP_MAX || + src->nm_index == index) { + if (nla_put(skb, 0, sizeof(*ncc), ncc)) { + *errcode = NCSI_ERR_INTERNAL; + nlmsg_free(skb); + return NULL; + } + } + } + + nlmsg_end(skb, nlh); + return skb; +} + +static struct sk_buff *ncsi_netlink_get_mode(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_cmd_arg nca; + struct ncsi_msg *src; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + if (nd->nd_state != ncsi_dev_state_functional || + ndp->ndp_active_channel != nc) { + *errcode = NCSI_ERR_NOT_ACTIVE; + return NULL; + } + if (src->nm_index > NCSI_MODE_MAX) { + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + /* Send NCSI GP command */ + nca.nca_ndp = ndp; + nca.nca_nlh = h; + nca.nca_type = NCSI_PKT_CMD_GP; + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + if (ncsi_xmit_cmd(&nca)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + *errcode = NCSI_SUCCESS; + return NULL; +} + +static struct sk_buff *ncsi_netlink_get_filter(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_cmd_arg nca; + struct ncsi_msg *src; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + if (nd->nd_state != ncsi_dev_state_functional || + ndp->ndp_active_channel != nc) { + *errcode = NCSI_ERR_NOT_ACTIVE; + return NULL; + } + if (src->nm_index > NCSI_FILTER_MAX) { + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + /* Send NCSI GP command */ + nca.nca_ndp = ndp; + nca.nca_nlh = h; + nca.nca_type = NCSI_PKT_CMD_GP; + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + if (ncsi_xmit_cmd(&nca)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + *errcode = NCSI_SUCCESS; + return NULL; +} + +struct sk_buff *ncsi_netlink_get_stats(struct nlmsghdr *h, int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_msg *src; + struct ncsi_cmd_arg nca; + unsigned char cmd; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + if (nd->nd_state != ncsi_dev_state_functional || + ndp->ndp_active_channel != nc) { + *errcode = NCSI_ERR_NOT_ACTIVE; + return NULL; + } + if (src->nm_index > NCSI_FILTER_MAX) { + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + /* Get NIC statistics */ + nca.nca_ndp = ndp; + nca.nca_nlh = NULL; + nca.nca_type = NCSI_PKT_CMD_GCPS; + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + for (cmd = NCSI_PKT_CMD_GCPS; cmd <= NCSI_PKT_CMD_GNPTS; cmd++) { + nca.nca_type = cmd; + if (cmd == NCSI_PKT_CMD_GNPTS) + nca.nca_nlh = h; + if (ncsi_xmit_cmd(&nca)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + } + + *errcode = NCSI_SUCCESS; + return NULL; +} + +struct sk_buff *ncsi_netlink_set_mode(struct nlmsghdr *h, int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + struct ncsi_msg *src; + struct nlattr *nla; + struct ncsi_cmd_arg nca; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + if (nd->nd_state != ncsi_dev_state_functional || + ndp->ndp_active_channel != nc) { + *errcode = NCSI_ERR_NOT_ACTIVE; + return NULL; + } + + /* Get NIC statistics */ + nla = nlmsg_attrdata(h, sizeof(*src)); + ncm = nla_data(nla); + + nca.nca_ndp = ndp; + nca.nca_nlh = h; + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + switch (src->nm_index) { + case NCSI_MODE_ENABLE: + if (ncm->ncm_enable) { + nca.nca_type = NCSI_PKT_CMD_EC; + } else { + nca.nca_type = NCSI_PKT_CMD_DC; + nca.nca_bytes[0] = ncm->ncm_data[0]; + } + break; + case NCSI_MODE_TX_ENABLE: + if (ncm->ncm_enable) + nca.nca_type = NCSI_PKT_CMD_ECNT; + else + nca.nca_type = NCSI_PKT_CMD_DCNT; + break; + case NCSI_MODE_LINK: + nca.nca_type = NCSI_PKT_CMD_SL; + nca.nca_dwords[0] = ncm->ncm_data[0]; + nca.nca_dwords[1] = ncm->ncm_data[1]; + break; + case NCSI_MODE_VLAN: + if (ncm->ncm_enable) { + nca.nca_type = NCSI_PKT_CMD_EV; + nca.nca_bytes[0] = ncm->ncm_data[0]; + } else { + nca.nca_type = NCSI_PKT_CMD_DV; + } + break; + case NCSI_MODE_BC: + if (ncm->ncm_enable) { + nca.nca_type = NCSI_PKT_CMD_EBF; + nca.nca_dwords[0] = ncm->ncm_data[0]; + } else { + nca.nca_type = NCSI_PKT_CMD_DBF; + } + break; + case NCSI_MODE_MC: + if (ncm->ncm_enable) { + nca.nca_type = NCSI_PKT_CMD_EGMF; + nca.nca_dwords[0] = ncm->ncm_data[0]; + } else { + nca.nca_type = NCSI_PKT_CMD_DGMF; + } + break; + case NCSI_MODE_AEN: + nca.nca_type = NCSI_PKT_CMD_AE; + nca.nca_bytes[0] = ncm->ncm_data[0]; + nca.nca_dwords[1] = ncm->ncm_data[1]; + break; + case NCSI_MODE_FC: + nca.nca_type = NCSI_PKT_CMD_SNFC; + nca.nca_bytes[0] = ncm->ncm_data[0]; + break; + default: + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + if (ncsi_xmit_cmd(&nca)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + *errcode = NCSI_SUCCESS; + return NULL; +} + +struct sk_buff *ncsi_netlink_set_filter(struct nlmsghdr *h, int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_filter *ncf; + struct ncsi_msg *src; + struct nlattr *nla; + struct ncsi_cmd_arg nca; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + if (nd->nd_state != ncsi_dev_state_functional || + ndp->ndp_active_channel != nc) { + *errcode = NCSI_ERR_NOT_ACTIVE; + return NULL; + } + + /* Get NIC statistics */ + nla = nlmsg_attrdata(h, sizeof(*src)); + ncf = nla_data(nla); + + nca.nca_ndp = ndp; + nca.nca_nlh = h; + nca.nca_package = np->np_id; + nca.nca_channel = nc->nc_id; + switch (src->nm_index) { + case NCSI_FILTER_VLAN: + nca.nca_type = NCSI_PKT_CMD_SVF; + memcpy(nca.nca_bytes, ncf->ncf_data, 4); + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + memcpy(nca.nca_bytes, ncf->ncf_data, 8); + nca.nca_bytes[7] &= 0x1f; + if (src->nm_index == NCSI_FILTER_MC) + nca.nca_bytes[7] |= 0x20; + break; + default: + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + if (ncsi_xmit_cmd(&nca)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + *errcode = NCSI_SUCCESS; + return NULL; +} + +static struct sk_buff *ncsi_netlink_get_mode_reply(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg *src, *dst; + size_t size = sizeof(*dst); + int index; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + if (src->nm_index > NCSI_MODE_MAX) { + *errcode = NCSI_ERR_PARAM; + return NULL; + } else if (src->nm_index == NCSI_MODE_MAX) { + size += nla_total_size(sizeof(*ncm)) * NCSI_MODE_MAX; + } else { + size += nla_total_size(sizeof(*ncm)); + } + skb = nlmsg_new(size, GFP_ATOMIC); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill one or all modes */ + for (index = 0; index < NCSI_MODE_MAX; index++) { + ncm = &nc->nc_modes[index]; + if (src->nm_index == NCSI_MODE_MAX || + src->nm_index == index) { + if (nla_put(skb, 0, sizeof(*ncm), ncm)) { + *errcode = NCSI_ERR_INTERNAL; + nlmsg_free(skb); + return NULL; + } + } + } + + nlmsg_end(skb, nlh); + return skb; +} + +static struct sk_buff *ncsi_netlink_get_filter_reply(struct nlmsghdr *h, + int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_filter *ncf; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg *src, *dst; + size_t entry_size, size = sizeof(*dst); + int index; + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + if (src->nm_index > NCSI_FILTER_MAX) { + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + for (index = 0; index < NCSI_FILTER_MAX; index++) { + ncf = nc->nc_filters[index]; + switch (index) { + case NCSI_FILTER_VLAN: + entry_size = 2; + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + case NCSI_FILTER_MIXED: + entry_size = 6; + break; + default: + continue; + } + + if (src->nm_index == NCSI_FILTER_MAX || + src->nm_index == index) { + if (!ncf) + continue; + + size += nla_total_size(sizeof(*ncf) + + entry_size * ncf->ncf_total); + } + } + + skb = nlmsg_new(size, GFP_ATOMIC); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill one or all filters */ + for (index = 0; index < NCSI_FILTER_MAX; index++) { + ncf = nc->nc_filters[index]; + switch (index) { + case NCSI_FILTER_VLAN: + entry_size = 2; + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + case NCSI_FILTER_MIXED: + entry_size = 6; + break; + default: + continue; + } + + if (src->nm_index == NCSI_FILTER_MAX || + src->nm_index == index) { + if (!ncf) + continue; + + size = sizeof(*ncf) + entry_size * ncf->ncf_total; + if (nla_put(skb, 0, size, ncf)) { + nlmsg_free(skb); + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + } + } + + nlmsg_end(skb, nlh); + return skb; +} + +struct sk_buff *ncsi_netlink_get_stats_reply(struct nlmsghdr *h, int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct ncsi_channel_stats *ncs; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct ncsi_msg *src, *dst; + size_t size = sizeof(*dst); + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + size += nla_total_size(sizeof(*ncs)); + skb = nlmsg_new(size, GFP_ATOMIC); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill one or all filters */ + ncs = &nc->nc_stats; + if (nla_put(skb, 0, sizeof(*ncs), ncs)) { + nlmsg_free(skb); + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + nlmsg_end(skb, nlh); + return skb; +} + +struct sk_buff *ncsi_netlink_set_mode_reply(struct nlmsghdr *h, int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct nlattr *nla; + struct ncsi_msg *src, *dst; + struct ncsi_channel_mode *ncm; + size_t size = sizeof(*dst); + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + size += nla_total_size(sizeof(*ncm)); + skb = nlmsg_new(size, GFP_ATOMIC); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill one or all filters */ + nla = nlmsg_attrdata(h, sizeof(*dst)); + ncm = nla_data(nla); + if (nla_put(skb, 0, sizeof(*ncm), ncm)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + nlmsg_end(skb, nlh); + return skb; +} + +struct sk_buff *ncsi_netlink_set_filter_reply(struct nlmsghdr *h, int *errcode) +{ + struct net_device *dev; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct nlattr *nla; + struct ncsi_msg *src, *dst; + struct ncsi_channel_filter *ncf; + size_t extra, size = sizeof(*dst); + + /* Find the NCSI device */ + src = nlmsg_data(h); + dev = dev_get_by_index(ncsi_net, src->nm_ifindex); + nd = dev ? ncsi_find_dev(dev) : NULL; + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + np = ndp ? ncsi_find_package(ndp, src->nm_package_id) : NULL; + nc = np ? ncsi_find_channel(np, src->nm_channel_id) : NULL; + if (!nc) { + *errcode = NCSI_ERR_NO_DEV; + return NULL; + } + + /* Allocate response */ + size += nla_total_size(sizeof(*ncf) + 16); + skb = nlmsg_new(size, GFP_ATOMIC); + if (!skb) { + *errcode = NCSI_ERR_NO_MEM; + return NULL; + } + + /* Fill header */ + nlh = nlmsg_put(skb, h->nlmsg_pid, h->nlmsg_seq, + h->nlmsg_type, sizeof(*dst), 0); + dst = nlmsg_data(nlh); + memcpy(dst, src, sizeof(*dst)); + dst->nm_flag &= ~NCSI_FLAG_REQUEST; + dst->nm_flag |= NCSI_FLAG_RESPONSE; + + /* Fill one or all filters */ + nla = nlmsg_attrdata(h, sizeof(*dst)); + ncf = nla_data(nla); + switch(src->nm_index) { + case NCSI_FILTER_VLAN: + extra = 2; + break; + case NCSI_FILTER_UC: + case NCSI_FILTER_MC: + extra = 6; + break; + default: + nlmsg_free(skb); + *errcode = NCSI_ERR_PARAM; + return NULL; + } + + if (nla_put(skb, 0, sizeof(*ncf) + extra, ncf)) { + *errcode = NCSI_ERR_INTERNAL; + return NULL; + } + + nlmsg_end(skb, nlh); + return skb; +} + +void ncsi_netlink_reply(struct nlmsghdr *h, unsigned int portid, bool timeout) +{ + struct sk_buff *skb; + int errcode = NCSI_SUCCESS; + struct sk_buff *(*func)(struct nlmsghdr *, int *) = NULL; + + if (timeout) { + ncsi_netlink_error(h, portid, NCSI_ERR_INTERNAL); + return; + } + + switch (h->nlmsg_type) { + case NCSI_MSG_GET_MODE: + func = ncsi_netlink_get_mode_reply; + break; + case NCSI_MSG_GET_FILTER: + func = ncsi_netlink_get_filter_reply; + break; + case NCSI_MSG_GET_STATS: + func = ncsi_netlink_get_stats_reply; + break; + case NCSI_MSG_SET_MODE: + func = ncsi_netlink_set_mode_reply; + break; + case NCSI_MSG_SET_FILTER: + func = ncsi_netlink_set_filter_reply; + break; + default: + errcode = NCSI_ERR_PARAM; + goto out; + } + + skb = func(h, &errcode); + if (!skb) + goto out; + + nlmsg_notify(ncsi_sock, skb, portid, 0, 0, GFP_ATOMIC); + return; +out: + ncsi_netlink_error(h, portid, errcode); +} + +static int ncsi_netlink_rcv_msg(struct sk_buff *cmd, struct nlmsghdr *h) +{ + struct ncsi_msg *nm; + struct sk_buff *skb; + int errcode = NCSI_SUCCESS; + unsigned int portid = NETLINK_CB(cmd).portid; + struct sk_buff *(*func)(struct nlmsghdr *, int *) = NULL; + + if (h->nlmsg_type >= NCSI_MSG_MAX || + nlmsg_len(h) < sizeof(*nm)) { + errcode = NCSI_ERR_PARAM; + goto out; + } + + nm = nlmsg_data(h); + if (!(nm->nm_flag & NCSI_FLAG_REQUEST)) { + errcode = NCSI_ERR_PARAM; + goto out; + } + + switch (h->nlmsg_type) { + case NCSI_MSG_GET_LAYOUT: + func = ncsi_netlink_get_layout; + break; + case NCSI_MSG_GET_VERSION: + func = ncsi_netlink_get_version; + break; + case NCSI_MSG_GET_CAP: + func = ncsi_netlink_get_cap; + break; + case NCSI_MSG_GET_MODE: + func = ncsi_netlink_get_mode; + break; + case NCSI_MSG_GET_FILTER: + func = ncsi_netlink_get_filter; + break; + case NCSI_MSG_GET_STATS: + func = ncsi_netlink_get_stats; + break; + case NCSI_MSG_SET_MODE: + func = ncsi_netlink_set_mode; + break; + case NCSI_MSG_SET_FILTER: + func = ncsi_netlink_set_filter; + break; + default: + goto out; + } + + skb = func(h, &errcode); + if (errcode != NCSI_SUCCESS) + goto out; + + /* Split transactions */ + if (!skb) + return 0; + + return nlmsg_notify(ncsi_sock, skb, portid, 0, 0, GFP_KERNEL); +out: + ncsi_netlink_error(h, portid, errcode); + return -EINTR; +} + +static void ncsi_netlink_rcv(struct sk_buff *skb) +{ + netlink_rcv_skb(skb, &ncsi_netlink_rcv_msg); +} + +int __net_init ncsi_netlink_init(struct net *net) +{ + struct netlink_kernel_cfg cfg = { + .groups = 0, + .flags = NL_CFG_F_NONROOT_RECV | + NL_CFG_F_NONROOT_SEND, + .input = ncsi_netlink_rcv, + }; + + ncsi_sock = netlink_kernel_create(net, NETLINK_NCSI, &cfg); + return ncsi_sock ? 0 : -ENOMEM; +} + +void __net_exit ncsi_netlink_exit(struct net *net) +{ + netlink_kernel_release(ncsi_sock); + ncsi_sock = NULL; +} diff --git a/net/ncsi/ncsi-pkt.h b/net/ncsi/ncsi-pkt.h new file mode 100644 index 00000000000000..646f1fc0bd211a --- /dev/null +++ b/net/ncsi/ncsi-pkt.h @@ -0,0 +1,391 @@ +#ifndef __NCSI_PKT_H__ +#define __NCSI_PKT_H__ + +struct ncsi_pkt_hdr { + unsigned char mc_id; /* Management controller ID */ + unsigned char revision; /* NCSI version - 0x01 */ + unsigned char reserved; /* Reserved */ + unsigned char id; /* Packet sequence number */ + unsigned char type; /* Packet type */ + unsigned char channel; /* Network controller ID */ + __be16 length; /* Payload length */ + __be32 reserved1[2]; /* Reserved */ +}; + +struct ncsi_cmd_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ +}; + +struct ncsi_rsp_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ + __be16 code; /* Response code */ + __be16 reason; /* Response reason */ +}; + +struct ncsi_aen_pkt_hdr { + struct ncsi_pkt_hdr common; /* Common NCSI packet header */ + unsigned char reserved2[3]; /* Reserved */ + unsigned char type; /* AEN packet type */ +}; + +/* NCSI common command and response packets */ +struct ncsi_cmd_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 checksum; /* Checksum */ + unsigned char pad[26]; +}; + +struct ncsi_rsp_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Select Package */ +struct ncsi_cmd_sp_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char hw_arbitration; /* HW arbitration */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Disable Channel */ +struct ncsi_cmd_dc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char ald; /* Allow link down */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Reset Channel */ +struct ncsi_cmd_rc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 reserved; /* Reserved */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* AEN Enable */ +struct ncsi_cmd_ae_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mc_id; /* MC ID */ + __be32 mode; /* AEN working mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +}; + +/* Set Link */ +struct ncsi_cmd_sl_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Link working mode */ + __be32 oem_mode; /* OEM link mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +}; + +/* Get Link Status */ +struct ncsi_rsp_gls_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 status; /* Link status */ + __be32 other; /* Other indications */ + __be32 oem_status; /* OEM link status */ + __be32 checksum; /* Checksum */ + unsigned char pad[10]; +}; + +/* Set VLAN Filter */ +struct ncsi_cmd_svf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be16 reserved; /* Reserved */ + __be16 vlan; /* VLAN ID */ + __be16 reserved1; /* Reserved */ + unsigned char index; /* VLAN table index */ + unsigned char enable; /* Enable or disable */ + __be32 checksum; /* Checksum */ + unsigned char pad[14]; +}; + +/* Enable VLAN */ +struct ncsi_cmd_ev_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mode; /* VLAN filter mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Set MAC Address */ +struct ncsi_cmd_sma_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char mac[6]; /* MAC address */ + unsigned char index; /* MAC table index */ + unsigned char at_e; /* Addr type and operation */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +}; + +/* Enable Broadcast Filter */ +struct ncsi_cmd_ebf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Filter mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Enable Global Multicast Filter */ +struct ncsi_cmd_egmf_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + __be32 mode; /* Global MC mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Set NCSI Flow Control */ +struct ncsi_cmd_snfc_pkt { + struct ncsi_cmd_pkt_hdr cmd; /* Command header */ + unsigned char reserved[3]; /* Reserved */ + unsigned char mode; /* Flow control mode */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* Get Version ID */ +struct ncsi_rsp_gvi_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 ncsi_version; /* NCSI version */ + unsigned char reserved[3]; /* Reserved */ + unsigned char alpha2; /* NCSI version */ + unsigned char fw_name[12]; /* f/w name string */ + __be32 fw_version; /* f/w version */ + __be16 pci_ids[4]; /* PCI IDs */ + __be32 mf_id; /* Manufacture ID */ + __be32 checksum; +}; + +/* Get Capabilities */ +struct ncsi_rsp_gc_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 cap; /* Capabilities */ + __be32 bc_cap; /* Broadcast cap */ + __be32 mc_cap; /* Multicast cap */ + __be32 buf_cap; /* Buffering cap */ + __be32 aen_cap; /* AEN cap */ + unsigned char vlan_cnt; /* VLAN filter count */ + unsigned char mixed_cnt; /* Mix filter count */ + unsigned char mc_cnt; /* MC filter count */ + unsigned char uc_cnt; /* UC filter count */ + unsigned char reserved[2]; /* Reserved */ + unsigned char vlan_mode; /* VLAN mode */ + unsigned char channel_cnt; /* Channel count */ + __be32 checksum; /* Checksum */ +}; + +/* Get Parameters */ +struct ncsi_rsp_gp_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + unsigned char mac_cnt; /* Number of MAC addr */ + unsigned char reserved[2]; /* Reserved */ + unsigned char mac_enable; /* MAC addr enable flags */ + unsigned char vlan_cnt; /* VLAN tag count */ + unsigned char reserved1; /* Reserved */ + __be16 vlan_enable; /* VLAN tag enable flags */ + __be32 link_mode; /* Link setting */ + __be32 bc_mode; /* BC filter mode */ + __be32 valid_modes; /* Valid mode parameters */ + unsigned char vlan_mode; /* VLAN mode */ + unsigned char fc_mode; /* Flow control mode */ + unsigned char reserved2[2]; /* Reserved */ + __be32 aen_mode; /* AEN mode */ + unsigned char mac[6]; /* Supported MAC addr */ + __be16 vlan; /* Supported VLAN tags */ + __be32 checksum; /* Checksum */ +}; + +/* Get Controller Packet Statistics */ +struct ncsi_rsp_gcps_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 cnt_hi; /* Counter cleared */ + __be32 cnt_lo; /* Counter cleared */ + __be32 rx_bytes; /* Rx bytes */ + __be32 tx_bytes; /* Tx bytes */ + __be32 rx_uc_pkts; /* Rx UC packets */ + __be32 rx_mc_pkts; /* Rx MC packets */ + __be32 rx_bc_pkts; /* Rx BC packets */ + __be32 tx_uc_pkts; /* Tx UC packets */ + __be32 tx_mc_pkts; /* Tx MC packets */ + __be32 tx_bc_pkts; /* Tx BC packets */ + __be32 fcs_err; /* FCS errors */ + __be32 align_err; /* Alignment errors */ + __be32 false_carrier; /* False carrier detection */ + __be32 runt_pkts; /* Rx runt packets */ + __be32 jabber_pkts; /* Rx jabber packets */ + __be32 rx_pause_xon; /* Rx pause XON frames */ + __be32 rx_pause_xoff; /* Rx XOFF frames */ + __be32 tx_pause_xon; /* Tx XON frames */ + __be32 tx_pause_xoff; /* Tx XOFF frames */ + __be32 tx_s_collision; /* Single collision frames */ + __be32 tx_m_collision; /* Multiple collision frames */ + __be32 l_collision; /* Late collision frames */ + __be32 e_collision; /* Excessive collision frames */ + __be32 rx_ctl_frames; /* Rx control frames */ + __be32 rx_64_frames; /* Rx 64-bytes frames */ + __be32 rx_127_frames; /* Rx 65-127 bytes frames */ + __be32 rx_255_frames; /* Rx 128-255 bytes frames */ + __be32 rx_511_frames; /* Rx 256-511 bytes frames */ + __be32 rx_1023_frames; /* Rx 512-1023 bytes frames */ + __be32 rx_1522_frames; /* Rx 1024-1522 bytes frames */ + __be32 rx_9022_frames; /* Rx 1523-9022 bytes frames */ + __be32 tx_64_frames; /* Tx 64-bytes frames */ + __be32 tx_127_frames; /* Tx 65-127 bytes frames */ + __be32 tx_255_frames; /* Tx 128-255 bytes frames */ + __be32 tx_511_frames; /* Tx 256-511 bytes frames */ + __be32 tx_1023_frames; /* Tx 512-1023 bytes frames */ + __be32 tx_1522_frames; /* Tx 1024-1522 bytes frames */ + __be32 tx_9022_frames; /* Tx 1523-9022 bytes frames */ + __be32 rx_valid_bytes; /* Rx valid bytes */ + __be32 rx_runt_pkts; /* Rx error runt packets */ + __be32 rx_jabber_pkts; /* Rx error jabber packets */ + __be32 checksum; /* Checksum */ +}; + +/* Get NCSI Statistics */ +struct ncsi_rsp_gns_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 rx_cmds; /* Rx NCSI commands */ + __be32 dropped_cmds; /* Dropped commands */ + __be32 cmd_type_errs; /* Command type errors */ + __be32 cmd_csum_errs; /* Command checksum errors */ + __be32 rx_pkts; /* Rx NCSI packets */ + __be32 tx_pkts; /* Tx NCSI packets */ + __be32 tx_aen_pkts; /* Tx AEN packets */ + __be32 checksum; /* Checksum */ +}; + +/* Get NCSI Pass-through Statistics */ +struct ncsi_rsp_gnpts_pkt { + struct ncsi_rsp_pkt_hdr rsp; /* Response header */ + __be32 tx_pkts; /* Tx packets */ + __be32 tx_dropped; /* Tx dropped packets */ + __be32 tx_channel_err; /* Tx channel errors */ + __be32 tx_us_err; /* Tx undersize errors */ + __be32 rx_pkts; /* Rx packets */ + __be32 rx_dropped; /* Rx dropped packets */ + __be32 rx_channel_err; /* Rx channel errors */ + __be32 rx_us_err; /* Rx undersize errors */ + __be32 rx_os_err; /* Rx oversize errors */ + __be32 checksum; /* Checksum */ +}; + +/* AEN: Link State Change */ +struct ncsi_aen_lsc_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 status; /* Link status */ + __be32 oem_status; /* OEM link status */ + __be32 checksum; /* Checksum */ + unsigned char pad[14]; +}; + +/* AEN: Configuration Required */ +struct ncsi_aen_cr_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 checksum; /* Checksum */ + unsigned char pad[22]; +}; + +/* AEN: Host Network Controller Driver Status Change */ +struct ncsi_aen_hncdsc_pkt { + struct ncsi_aen_pkt_hdr aen; /* AEN header */ + __be32 status; /* Status */ + __be32 checksum; /* Checksum */ + unsigned char pad[18]; +}; + +/* NCSI packet revision */ +#define NCSI_PKT_REVISION 0x01 + +/* NCSI packet commands */ +#define NCSI_PKT_CMD_CIS 0x00 /* Clear Initial State */ +#define NCSI_PKT_CMD_SP 0x01 /* Select Package */ +#define NCSI_PKT_CMD_DP 0x02 /* Deselect Package */ +#define NCSI_PKT_CMD_EC 0x03 /* Enable Channel */ +#define NCSI_PKT_CMD_DC 0x04 /* Disable Channel */ +#define NCSI_PKT_CMD_RC 0x05 /* Reset Channel */ +#define NCSI_PKT_CMD_ECNT 0x06 /* Enable Channel Network Tx */ +#define NCSI_PKT_CMD_DCNT 0x07 /* Disable Channel Network Tx */ +#define NCSI_PKT_CMD_AE 0x08 /* AEN Enable */ +#define NCSI_PKT_CMD_SL 0x09 /* Set Link */ +#define NCSI_PKT_CMD_GLS 0x0a /* Get Link */ +#define NCSI_PKT_CMD_SVF 0x0b /* Set VLAN Filter */ +#define NCSI_PKT_CMD_EV 0x0c /* Enable VLAN */ +#define NCSI_PKT_CMD_DV 0x0d /* Disable VLAN */ +#define NCSI_PKT_CMD_SMA 0x0e /* Set MAC address */ +#define NCSI_PKT_CMD_EBF 0x10 /* Enable Broadcast Filter */ +#define NCSI_PKT_CMD_DBF 0x11 /* Disable Broadcast Filter */ +#define NCSI_PKT_CMD_EGMF 0x12 /* Enable Global Multicast Filter */ +#define NCSI_PKT_CMD_DGMF 0x13 /* Disable Global Multicast Filter */ +#define NCSI_PKT_CMD_SNFC 0x14 /* Set NCSI Flow Control */ +#define NCSI_PKT_CMD_GVI 0x15 /* Get Version ID */ +#define NCSI_PKT_CMD_GC 0x16 /* Get Capabilities */ +#define NCSI_PKT_CMD_GP 0x17 /* Get Parameters */ +#define NCSI_PKT_CMD_GCPS 0x18 /* Get Controller Packet Statistics */ +#define NCSI_PKT_CMD_GNS 0x19 /* Get NCSI Statistics */ +#define NCSI_PKT_CMD_GNPTS 0x1a /* Get NCSI Pass-throu Statistics */ +#define NCSI_PKT_CMD_OEM 0x50 /* OEM */ + +/* NCSI packet responses */ +#define NCSI_PKT_RSP_CIS (NCSI_PKT_CMD_CIS + 0x80) +#define NCSI_PKT_RSP_SP (NCSI_PKT_CMD_SP + 0x80) +#define NCSI_PKT_RSP_DP (NCSI_PKT_CMD_DP + 0x80) +#define NCSI_PKT_RSP_EC (NCSI_PKT_CMD_EC + 0x80) +#define NCSI_PKT_RSP_DC (NCSI_PKT_CMD_DC + 0x80) +#define NCSI_PKT_RSP_RC (NCSI_PKT_CMD_RC + 0x80) +#define NCSI_PKT_RSP_ECNT (NCSI_PKT_CMD_ECNT + 0x80) +#define NCSI_PKT_RSP_DCNT (NCSI_PKT_CMD_DCNT + 0x80) +#define NCSI_PKT_RSP_AE (NCSI_PKT_CMD_AE + 0x80) +#define NCSI_PKT_RSP_SL (NCSI_PKT_CMD_SL + 0x80) +#define NCSI_PKT_RSP_GLS (NCSI_PKT_CMD_GLS + 0x80) +#define NCSI_PKT_RSP_SVF (NCSI_PKT_CMD_SVF + 0x80) +#define NCSI_PKT_RSP_EV (NCSI_PKT_CMD_EV + 0x80) +#define NCSI_PKT_RSP_DV (NCSI_PKT_CMD_DV + 0x80) +#define NCSI_PKT_RSP_SMA (NCSI_PKT_CMD_SMA + 0x80) +#define NCSI_PKT_RSP_EBF (NCSI_PKT_CMD_EBF + 0x80) +#define NCSI_PKT_RSP_DBF (NCSI_PKT_CMD_DBF + 0x80) +#define NCSI_PKT_RSP_EGMF (NCSI_PKT_CMD_EGMF + 0x80) +#define NCSI_PKT_RSP_DGMF (NCSI_PKT_CMD_DGMF + 0x80) +#define NCSI_PKT_RSP_SNFC (NCSI_PKT_CMD_SNFC + 0x80) +#define NCSI_PKT_RSP_GVI (NCSI_PKT_CMD_GVI + 0x80) +#define NCSI_PKT_RSP_GC (NCSI_PKT_CMD_GC + 0x80) +#define NCSI_PKT_RSP_GP (NCSI_PKT_CMD_GP + 0x80) +#define NCSI_PKT_RSP_GCPS (NCSI_PKT_CMD_GCPS + 0x80) +#define NCSI_PKT_RSP_GNS (NCSI_PKT_CMD_GNS + 0x80) +#define NCSI_PKT_RSP_GNPTS (NCSI_PKT_CMD_GNPTS + 0x80) +#define NCSI_PKT_RSP_OEM (NCSI_PKT_CMD_OEM + 0x80) + +/* NCSI packet type: AEN */ +#define NCSI_PKT_AEN 0xFF /* AEN Packet */ +#define NCSI_PKT_AEN_LSC 0x00 /* Link status change */ +#define NCSI_PKT_AEN_CR 0x01 /* Configuration required */ +#define NCSI_PKT_AEN_HNCDSC 0x02 /* HNC driver status change */ + +/* NCSI response code/reason */ +#define NCSI_PKT_RSP_C_COMPLETED 0x0000 /* Command Completed */ +#define NCSI_PKT_RSP_C_FAILED 0x0001 /* Command Failed */ +#define NCSI_PKT_RSP_C_UNAVAILABLE 0x0002 /* Command Unavailable */ +#define NCSI_PKT_RSP_C_UNSUPPORTED 0x0003 /* Command Unsupported */ +#define NCSI_PKT_RSP_R_NO_ERROR 0x0000 /* No Error */ +#define NCSI_PKT_RSP_R_INTERFACE 0x0001 /* Interface not ready */ +#define NCSI_PKT_RSP_R_PARAM 0x0002 /* Invalid Parameter */ +#define NCSI_PKT_RSP_R_CHANNEL 0x0003 /* Channel not Ready */ +#define NCSI_PKT_RSP_R_PACKAGE 0x0004 /* Package not Ready */ +#define NCSI_PKT_RSP_R_LENGTH 0x0005 /* Invalid payload length */ +#define NCSI_PKT_RSP_R_UNKNOWN 0x7fff /* Command type unsupported */ + +/* NCSI AEN packet type */ +#define NCSI_PKT_AEN_LSC 0x00 /* Link status change */ +#define NCSI_PKT_AEN_CR 0x01 /* Configuration required */ +#define NCSI_PKT_AEN_HNCDSC 0x02 /* Host driver status change */ + +#endif /* __NCSI_PKT_H__ */ diff --git a/net/ncsi/ncsi-rsp.c b/net/ncsi/ncsi-rsp.c new file mode 100644 index 00000000000000..2b196533b01fb7 --- /dev/null +++ b/net/ncsi/ncsi-rsp.c @@ -0,0 +1,1167 @@ +/* + * Copyright Gavin Shan, IBM Corporation 2015. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "internal.h" +#include "ncsi-pkt.h" + +static int ncsi_validate_rsp_pkt(struct ncsi_req *nr, + unsigned short payload) +{ + struct ncsi_rsp_pkt_hdr *h; + unsigned char *stream; + __be32 *checksum, csum; + __be32 high, low; + int i; + + /* + * Check NCSI packet header. We don't need validate + * the packet type, which should have been checked + * before calling this function. + */ + h = (struct ncsi_rsp_pkt_hdr *)skb_network_header(nr->nr_rsp); + if (h->common.revision != NCSI_PKT_REVISION) + return -EINVAL; + if (ntohs(h->common.length) != payload) + return -EINVAL; + + /* Check on code and reason */ + if (ntohs(h->code) != NCSI_PKT_RSP_C_COMPLETED || + ntohs(h->reason) != NCSI_PKT_RSP_R_NO_ERROR) + return -EINVAL; + + /* + * Validate checksum, which might be zeroes if the + * sender doesn't support checksum according to NCSI + * specification. + */ + checksum = (__be32 *)((void *)(h + 1) + payload - 4); + if (ntohl(*checksum) == 0) + return 0; + + csum = 0; + stream = (unsigned char *)h; + for (i = 0; i < sizeof(*h) + payload - 4; i += 2) { + high = stream[i]; + low = stream[i + 1]; + csum += ((high << 8) | low); + } + + csum = ~csum + 1; + if (*checksum != htonl(csum)) + return -EINVAL; + + return 0; +} + +static int ncsi_rsp_handler_default(struct ncsi_req *nr) +{ + return ncsi_validate_rsp_pkt(nr, 0); +} + +static int ncsi_rsp_handler_cis(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, &np, &nc); + if (!np) + return -ENODEV; + + /* Add the channel if necessary */ + if (!nc) + nc = ncsi_add_channel(np, + NCSI_CHANNEL_INDEX(rsp->rsp.common.channel)); + else if (nc->nc_state == ncsi_channel_state_deselected_initial) + nc->nc_state = ncsi_channel_state_deselected_ready; + else if (nc->nc_state == ncsi_channel_state_selected_initial) + nc->nc_state = ncsi_channel_state_selected_ready; + + return nc ? 0 : -ENODEV; +} + +static int ncsi_rsp_handler_sp(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* + * Add the package if it's not existing. Otherwise, + * to change the state of its child channels. + */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + &np, NULL); + if (!np) { + np = ncsi_add_package(ndp, + NCSI_PACKAGE_INDEX(rsp->rsp.common.channel)); + if (!np) + return -ENODEV; + } + + NCSI_FOR_EACH_CHANNEL(np, nc) { + if (nc->nc_state == ncsi_channel_state_deselected_initial) + nc->nc_state = ncsi_channel_state_selected_initial; + else if (nc->nc_state == ncsi_channel_state_deselected_ready) + nc->nc_state = ncsi_channel_state_selected_ready; + } + + return 0; +} + +static int ncsi_rsp_handler_dp(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_package *np; + struct ncsi_channel *nc; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + &np, NULL); + if (!np) + return -ENODEV; + + /* Change state of all channels attached to the package */ + NCSI_FOR_EACH_CHANNEL(np, nc) { + if (nc->nc_state == ncsi_channel_state_selected_initial) + nc->nc_state = ncsi_channel_state_deselected_initial; + else if (nc->nc_state == ncsi_channel_state_selected_ready) + nc->nc_state = ncsi_channel_state_deselected_ready; + } + + return 0; +} + +static int ncsi_rsp_handler_ec(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + ncm = &nc->nc_modes[NCSI_MODE_ENABLE]; + if (ncm->ncm_enable) + return -EBUSY; + + ncm->ncm_enable = 1; + return 0; +} + +static int ncsi_rsp_handler_dc(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp= nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + ncm = &nc->nc_modes[NCSI_MODE_ENABLE]; + if (!ncm->ncm_enable) + return -EBUSY; + + ncm->ncm_enable = 0;; + return 0; +} + +static int ncsi_rsp_handler_rc(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update state for the specified channel */ + if (nc->nc_state == ncsi_channel_state_deselected_ready) + nc->nc_state = ncsi_channel_state_deselected_initial; + else if (nc->nc_state == ncsi_channel_state_selected_ready) + nc->nc_state = ncsi_channel_state_selected_initial; + + return 0; +} + +static int ncsi_rsp_handler_ecnt(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + ncm = &nc->nc_modes[NCSI_MODE_TX_ENABLE]; + if (ncm->ncm_enable) + return -EBUSY; + + ncm->ncm_enable = 1; + return 0; +} + +static int ncsi_rsp_handler_dcnt(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + ncm = &nc->nc_modes[NCSI_MODE_TX_ENABLE]; + if (!ncm->ncm_enable) + return -EBUSY; + + ncm->ncm_enable = 1; + return 0; +} + +static int ncsi_rsp_handler_ae(struct ncsi_req *nr) +{ + struct ncsi_cmd_ae_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if the AEN has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_AEN]; + if (ncm->ncm_enable) + return -EBUSY; + + /* Update to AEN configuration */ + cmd = (struct ncsi_cmd_ae_pkt *)skb_network_header(nr->nr_cmd); + ncm->ncm_enable = 1; + ncm->ncm_data[0] = cmd->mc_id; + ncm->ncm_data[1] = ntohl(cmd->mode); + + return 0; +} + +static int ncsi_rsp_handler_sl(struct ncsi_req *nr) +{ + struct ncsi_cmd_sl_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + cmd = (struct ncsi_cmd_sl_pkt *)skb_network_header(nr->nr_cmd); + ncm = &nc->nc_modes[NCSI_MODE_LINK]; + ncm->ncm_data[0] = ntohl(cmd->mode); + ncm->ncm_data[1] = ntohl(cmd->oem_mode); + + return 0; +} + +static int ncsi_rsp_handler_gls(struct ncsi_req *nr) +{ + struct ncsi_rsp_gls_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 16); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_gls_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + ncm = &nc->nc_modes[NCSI_MODE_LINK]; + ncm->ncm_data[2] = ntohl(rsp->status); + ncm->ncm_data[3] = ntohl(rsp->other); + ncm->ncm_data[4] = ntohl(rsp->oem_status); + + return 0; +} + +static int ncsi_rsp_handler_svf(struct ncsi_req *nr) +{ + struct ncsi_cmd_svf_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_filter *ncf; + unsigned short vlan; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + cmd = (struct ncsi_cmd_svf_pkt *)skb_network_header(nr->nr_cmd); + ncf = nc->nc_filters[NCSI_FILTER_VLAN]; + if (!ncf) + return -ENOENT; + if (cmd->index >= ncf->ncf_total) + return -ERANGE; + + /* Add or remove the VLAN filter */ + if (!(cmd->enable & 0x1)) { + ret = ncsi_del_channel_filter(nc, NCSI_FILTER_VLAN, cmd->index); + } else { + vlan = ntohs(cmd->vlan); + ret = ncsi_add_channel_filter(nc, NCSI_FILTER_VLAN, &vlan); + } + + return ret; +} + +static int ncsi_rsp_handler_ev(struct ncsi_req *nr) +{ + struct ncsi_cmd_ev_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if VLAN mode has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_VLAN]; + if (ncm->ncm_enable) + return -EBUSY; + + /* Update to VLAN mode */ + cmd = (struct ncsi_cmd_ev_pkt *)skb_network_header(nr->nr_cmd); + ncm->ncm_enable = 1; + ncm->ncm_data[0] = ntohl(cmd->mode); + + return 0; +} + +static int ncsi_rsp_handler_dv(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if VLAN mode has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_VLAN]; + if (!ncm->ncm_enable) + return -EBUSY; + + /* Update to VLAN mode */ + ncm->ncm_enable = 0; + return 0; +} + +static int ncsi_rsp_handler_sma(struct ncsi_req *nr) +{ + struct ncsi_cmd_sma_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_filter *ncf; + void *bitmap; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* According to NCSI spec 1.01, the mixed filter table + * isn't supported yet. + */ + cmd = (struct ncsi_cmd_sma_pkt *)skb_network_header(nr->nr_cmd); + switch (cmd->at_e >> 5) { + case 0x0: /* UC address */ + ncf = nc->nc_filters[NCSI_FILTER_UC]; + break; + case 0x1: /* MC address */ + ncf = nc->nc_filters[NCSI_FILTER_MC]; + break; + default: + return -EINVAL; + } + + /* Sanity check on the filter */ + if (!ncf) + return -ENOENT; + else if (cmd->index >= ncf->ncf_total) + return -ERANGE; + + bitmap = &ncf->ncf_bitmap; + if (cmd->at_e & 0x1) { + if (test_and_set_bit(cmd->index, bitmap)) + return -EBUSY; + memcpy(ncf->ncf_data + 6 * cmd->index, cmd->mac, 6); + } else { + if (!test_and_clear_bit(cmd->index, bitmap)) + return -EBUSY; + + memset(ncf->ncf_data + 6 * cmd->index, 0, 6); + } + + return 0; +} + +static int ncsi_rsp_handler_ebf(struct ncsi_req *nr) +{ + struct ncsi_cmd_ebf_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the package and channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if broadcast filter has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_BC]; + if (ncm->ncm_enable) + return -EBUSY; + + /* Update to broadcast filter mode */ + cmd = (struct ncsi_cmd_ebf_pkt *)skb_network_header(nr->nr_cmd); + ncm->ncm_enable = 1; + ncm->ncm_data[0] = ntohl(cmd->mode); + + return 0; +} + +static int ncsi_rsp_handler_dbf(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if broadcast filter isn't enabled */ + ncm = &nc->nc_modes[NCSI_MODE_BC]; + if (!ncm->ncm_enable) + return -EBUSY; + + /* Update to broadcast filter mode */ + ncm->ncm_enable = 0; + ncm->ncm_data[0] = 0; + + return 0; +} + +static int ncsi_rsp_handler_egmf(struct ncsi_req *nr) +{ + struct ncsi_cmd_egmf_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if multicast filter has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_MC]; + if (ncm->ncm_enable) + return -EBUSY; + + /* Update to multicast filter mode */ + cmd = (struct ncsi_cmd_egmf_pkt *)skb_network_header(nr->nr_cmd); + ncm->ncm_enable = 1; + ncm->ncm_data[0] = ntohl(cmd->mode); + + return 0; +} + +static int ncsi_rsp_handler_dgmf(struct ncsi_req *nr) +{ + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if multicast filter has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_MC]; + if (!ncm->ncm_enable) + return -EBUSY; + + /* Update to multicast filter mode */ + ncm->ncm_enable = 0; + ncm->ncm_data[0] = 0; + + return 0; +} + +static int ncsi_rsp_handler_snfc(struct ncsi_req *nr) +{ + struct ncsi_cmd_snfc_pkt *cmd; + struct ncsi_rsp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_mode *ncm; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 4); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Check if flow control has been enabled */ + ncm = &nc->nc_modes[NCSI_MODE_FC]; + if (ncm->ncm_enable) + return -EBUSY; + + /* Update to flow control mode */ + cmd = (struct ncsi_cmd_snfc_pkt *)skb_network_header(nr->nr_cmd); + ncm->ncm_enable = 1; + ncm->ncm_data[0] = cmd->mode; + + return 0; +} + +static int ncsi_rsp_handler_gvi(struct ncsi_req *nr) +{ + struct ncsi_rsp_gvi_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_version *ncv; + int i, ret; + + ret = ncsi_validate_rsp_pkt( nr, 36); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_gvi_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update to channel's version info */ + ncv = &nc->nc_version; + ncv->ncv_version = ntohl(rsp->ncsi_version); + ncv->ncv_alpha2 = rsp->alpha2; + memcpy(ncv->ncv_fw_name, rsp->fw_name, 12); + ncv->ncv_fw_version = ntohl(rsp->fw_version); + for (i = 0; i < 4; i++) + ncv->ncv_pci_ids[i] = ntohs(rsp->pci_ids[i]); + ncv->ncv_mf_id = ntohl(rsp->mf_id); + + return 0; +} + +static int ncsi_rsp_handler_gc(struct ncsi_req *nr) +{ + struct ncsi_rsp_gc_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_filter *ncf; + size_t size, entry_size; + int cnt, i, ret; + + ret = ncsi_validate_rsp_pkt( nr, 32); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_gc_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update channel's capabilities */ + nc->nc_caps[NCSI_CAP_GENERIC].ncc_cap = ntohl(rsp->cap) & + NCSI_CAP_GENERIC_MASK; + nc->nc_caps[NCSI_CAP_BC].ncc_cap = ntohl(rsp->bc_cap) & + NCSI_CAP_BC_MASK; + nc->nc_caps[NCSI_CAP_MC].ncc_cap = ntohl(rsp->mc_cap) & + NCSI_CAP_MC_MASK; + nc->nc_caps[NCSI_CAP_BUFFER].ncc_cap = ntohl(rsp->buf_cap); + nc->nc_caps[NCSI_CAP_AEN].ncc_cap = ntohl(rsp->aen_cap) & + NCSI_CAP_AEN_MASK; + nc->nc_caps[NCSI_CAP_VLAN].ncc_cap = rsp->vlan_mode & + NCSI_CAP_VLAN_MASK; + + /* Build filters */ + for (i = 0; i < NCSI_FILTER_MAX; i++) { + switch (i) { + case NCSI_FILTER_VLAN: + cnt = rsp->vlan_cnt; + entry_size = 2; + break; + case NCSI_FILTER_MIXED: + cnt = rsp->mixed_cnt; + entry_size = 6; + break; + case NCSI_FILTER_MC: + cnt = rsp->mc_cnt; + entry_size = 6; + break; + case NCSI_FILTER_UC: + cnt = rsp->uc_cnt; + entry_size = 6; + break; + default: + continue; + } + + if (!cnt || nc->nc_filters[i]) + continue; + + size = sizeof(*ncf) + cnt * entry_size; + ncf = kzalloc(size, GFP_ATOMIC); + if (!ncf) { + pr_warn("%s: Cannot alloc filter table (%d)\n", + __func__, i); + return -ENOMEM; + } + + ncf->ncf_index = i; + ncf->ncf_total = cnt; + ncf->ncf_bitmap = 0x0ul; + nc->nc_filters[i] = ncf; + } + + return 0; +} + +static int ncsi_rsp_handler_gp(struct ncsi_req *nr) +{ + struct ncsi_pkt_hdr *h; + struct ncsi_rsp_gp_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + unsigned short length, enable, vlan; + unsigned char *pdata; + int table, i, ret; + + /* + * The get parameter response packet has variable length. + * The payload should be figured out from the packet + * header, instead of the fixed one we have for other types + * of packets. + */ + h = (struct ncsi_pkt_hdr *)skb_network_header(nr->nr_rsp); + length = ntohs(h->length); + if (length < 32) + return -EINVAL; + + ret = ncsi_validate_rsp_pkt( nr, length); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_gp_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Modes with explicit enabled indications */ + if (ntohl(rsp->valid_modes) & 0x1) { /* BC filter mode */ + nc->nc_modes[NCSI_MODE_BC].ncm_enable = 1; + nc->nc_modes[NCSI_MODE_BC].ncm_data[0] = ntohl(rsp->bc_mode); + } + if (ntohl(rsp->valid_modes) & 0x2) /* Channel enabled */ + nc->nc_modes[NCSI_MODE_ENABLE].ncm_enable = 1; + if (ntohl(rsp->valid_modes) & 0x4) /* Channel Tx enabled */ + nc->nc_modes[NCSI_MODE_TX_ENABLE].ncm_enable = 1; + if (ntohl(rsp->valid_modes) & 0x8) /* MC filter mode */ + nc->nc_modes[NCSI_MODE_MC].ncm_enable = 1; + + /* Modes without explicit enabled indications */ + nc->nc_modes[NCSI_MODE_LINK].ncm_enable = 1; + nc->nc_modes[NCSI_MODE_LINK].ncm_data[0] = ntohl(rsp->link_mode); + nc->nc_modes[NCSI_MODE_VLAN].ncm_enable = 1; + nc->nc_modes[NCSI_MODE_VLAN].ncm_data[0] = rsp->vlan_mode; + nc->nc_modes[NCSI_MODE_FC].ncm_enable = 1; + nc->nc_modes[NCSI_MODE_FC].ncm_data[0] = rsp->fc_mode; + nc->nc_modes[NCSI_MODE_AEN].ncm_enable = 1; + nc->nc_modes[NCSI_MODE_AEN].ncm_data[0] = ntohl(rsp->aen_mode); + + /* MAC addresses filter table */ + pdata = (unsigned char *)rsp + 48; + enable = rsp->mac_enable; + for (i = 0; i < rsp->mac_cnt; i++, pdata += 6) { + if (i >= (nc->nc_filters[NCSI_FILTER_UC]->ncf_total + + nc->nc_filters[NCSI_FILTER_MC]->ncf_total)) + table = NCSI_FILTER_MIXED; + else if (i >= nc->nc_filters[NCSI_FILTER_UC]->ncf_total) + table = NCSI_FILTER_MC; + else + table = NCSI_FILTER_UC; + + if (!(enable & (0x1 << i))) + continue; + + if (ncsi_find_channel_filter(nc, table, pdata) >= 0) + continue; + + ncsi_add_channel_filter(nc, table, pdata); + } + + /* VLAN filter table */ + enable = ntohs(rsp->vlan_enable); + for (i = 0; i < rsp->vlan_cnt; i++, pdata += 2) { + if (!(enable & (0x1 << i))) + continue; + + vlan = ntohs(*(__be16 *)pdata); + if (ncsi_find_channel_filter(nc, NCSI_FILTER_VLAN, &vlan) >= 0) + continue; + + ncsi_add_channel_filter(nc, NCSI_FILTER_VLAN, &vlan); + } + + return 0; +} + +static int ncsi_rsp_handler_gcps(struct ncsi_req *nr) +{ + struct ncsi_rsp_gcps_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_stats *ncs; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 172); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_gcps_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update HNC's statistics */ + ncs = &nc->nc_stats; + ncs->ncs_hnc_cnt_hi = ntohl(rsp->cnt_hi); + ncs->ncs_hnc_cnt_lo = ntohl(rsp->cnt_lo); + ncs->ncs_hnc_rx_bytes = ntohl(rsp->rx_bytes); + ncs->ncs_hnc_tx_bytes = ntohl(rsp->tx_bytes); + ncs->ncs_hnc_rx_uc_pkts = ntohl(rsp->rx_uc_pkts); + ncs->ncs_hnc_rx_mc_pkts = ntohl(rsp->rx_mc_pkts); + ncs->ncs_hnc_rx_bc_pkts = ntohl(rsp->rx_bc_pkts); + ncs->ncs_hnc_tx_uc_pkts = ntohl(rsp->tx_uc_pkts); + ncs->ncs_hnc_tx_mc_pkts = ntohl(rsp->tx_mc_pkts); + ncs->ncs_hnc_tx_bc_pkts = ntohl(rsp->tx_bc_pkts); + ncs->ncs_hnc_fcs_err = ntohl(rsp->fcs_err); + ncs->ncs_hnc_align_err = ntohl(rsp->align_err); + ncs->ncs_hnc_false_carrier = ntohl(rsp->false_carrier); + ncs->ncs_hnc_runt_pkts = ntohl(rsp->runt_pkts); + ncs->ncs_hnc_jabber_pkts = ntohl(rsp->jabber_pkts); + ncs->ncs_hnc_rx_pause_xon = ntohl(rsp->rx_pause_xon); + ncs->ncs_hnc_rx_pause_xoff = ntohl(rsp->rx_pause_xoff); + ncs->ncs_hnc_tx_pause_xon = ntohl(rsp->tx_pause_xon); + ncs->ncs_hnc_tx_pause_xoff = ntohl(rsp->tx_pause_xoff); + ncs->ncs_hnc_tx_s_collision = ntohl(rsp->tx_s_collision); + ncs->ncs_hnc_tx_m_collision = ntohl(rsp->tx_m_collision); + ncs->ncs_hnc_l_collision = ntohl(rsp->l_collision); + ncs->ncs_hnc_e_collision = ntohl(rsp->e_collision); + ncs->ncs_hnc_rx_ctl_frames = ntohl(rsp->rx_ctl_frames); + ncs->ncs_hnc_rx_64_frames = ntohl(rsp->rx_64_frames); + ncs->ncs_hnc_rx_127_frames = ntohl(rsp->rx_127_frames); + ncs->ncs_hnc_rx_255_frames = ntohl(rsp->rx_255_frames); + ncs->ncs_hnc_rx_511_frames = ntohl(rsp->rx_511_frames); + ncs->ncs_hnc_rx_1023_frames = ntohl(rsp->rx_1023_frames); + ncs->ncs_hnc_rx_1522_frames = ntohl(rsp->rx_1522_frames); + ncs->ncs_hnc_rx_9022_frames = ntohl(rsp->rx_9022_frames); + ncs->ncs_hnc_tx_64_frames = ntohl(rsp->tx_64_frames); + ncs->ncs_hnc_tx_127_frames = ntohl(rsp->tx_127_frames); + ncs->ncs_hnc_tx_255_frames = ntohl(rsp->tx_255_frames); + ncs->ncs_hnc_tx_511_frames = ntohl(rsp->tx_511_frames); + ncs->ncs_hnc_tx_1023_frames = ntohl(rsp->tx_1023_frames); + ncs->ncs_hnc_tx_1522_frames = ntohl(rsp->tx_1522_frames); + ncs->ncs_hnc_tx_9022_frames = ntohl(rsp->tx_9022_frames); + ncs->ncs_hnc_rx_valid_bytes = ntohl(rsp->rx_valid_bytes); + ncs->ncs_hnc_rx_runt_pkts = ntohl(rsp->rx_runt_pkts); + ncs->ncs_hnc_rx_jabber_pkts = ntohl(rsp->rx_jabber_pkts); + + return 0; +} + +static int ncsi_rsp_handler_gns(struct ncsi_req *nr) +{ + struct ncsi_rsp_gns_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_stats *ncs; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 172); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_gns_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update HNC's statistics */ + ncs = &nc->nc_stats; + ncs->ncs_ncsi_rx_cmds = ntohl(rsp->rx_cmds); + ncs->ncs_ncsi_dropped_cmds = ntohl(rsp->dropped_cmds); + ncs->ncs_ncsi_cmd_type_errs = ntohl(rsp->cmd_type_errs); + ncs->ncs_ncsi_cmd_csum_errs = ntohl(rsp->cmd_csum_errs); + ncs->ncs_ncsi_rx_pkts = ntohl(rsp->rx_pkts); + ncs->ncs_ncsi_tx_pkts = ntohl(rsp->tx_pkts); + ncs->ncs_ncsi_tx_aen_pkts = ntohl(rsp->tx_aen_pkts); + + return 0; +} + +static int ncsi_rsp_handler_gnpts(struct ncsi_req *nr) +{ + struct ncsi_rsp_gnpts_pkt *rsp; + struct ncsi_dev_priv *ndp = nr->nr_ndp; + struct ncsi_channel *nc; + struct ncsi_channel_stats *ncs; + int ret; + + ret = ncsi_validate_rsp_pkt( nr, 172); + if (ret) + return ret; + + /* Find the channel */ + rsp = (struct ncsi_rsp_gnpts_pkt *)skb_network_header(nr->nr_rsp); + ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, + NULL, &nc); + if (!nc) + return -ENODEV; + + /* Update HNC's statistics */ + ncs = &nc->nc_stats; + ncs->ncs_pt_tx_pkts = ntohl(rsp->tx_pkts); + ncs->ncs_pt_tx_dropped = ntohl(rsp->tx_dropped); + ncs->ncs_pt_tx_channel_err = ntohl(rsp->tx_channel_err); + ncs->ncs_pt_tx_us_err = ntohl(rsp->tx_us_err); + ncs->ncs_pt_rx_pkts = ntohl(rsp->rx_pkts); + ncs->ncs_pt_rx_dropped = ntohl(rsp->rx_dropped); + ncs->ncs_pt_rx_channel_err = ntohl(rsp->rx_channel_err); + ncs->ncs_pt_rx_us_err = ntohl(rsp->rx_us_err); + ncs->ncs_pt_rx_os_err = ntohl(rsp->rx_os_err); + + return 0; +} + +static struct ncsi_rsp_handler { + unsigned char nrh_type; + int (*nrh_handler)(struct ncsi_req *nr); +} ncsi_rsp_handlers[] = { + { NCSI_PKT_RSP_CIS, ncsi_rsp_handler_cis }, + { NCSI_PKT_RSP_SP, ncsi_rsp_handler_sp }, + { NCSI_PKT_RSP_DP, ncsi_rsp_handler_dp }, + { NCSI_PKT_RSP_EC, ncsi_rsp_handler_ec }, + { NCSI_PKT_RSP_DC, ncsi_rsp_handler_dc }, + { NCSI_PKT_RSP_RC, ncsi_rsp_handler_rc }, + { NCSI_PKT_RSP_ECNT, ncsi_rsp_handler_ecnt }, + { NCSI_PKT_RSP_DCNT, ncsi_rsp_handler_dcnt }, + { NCSI_PKT_RSP_AE, ncsi_rsp_handler_ae }, + { NCSI_PKT_RSP_SL, ncsi_rsp_handler_sl }, + { NCSI_PKT_RSP_GLS, ncsi_rsp_handler_gls }, + { NCSI_PKT_RSP_SVF, ncsi_rsp_handler_svf }, + { NCSI_PKT_RSP_EV, ncsi_rsp_handler_ev }, + { NCSI_PKT_RSP_DV, ncsi_rsp_handler_dv }, + { NCSI_PKT_RSP_SMA, ncsi_rsp_handler_sma }, + { NCSI_PKT_RSP_EBF, ncsi_rsp_handler_ebf }, + { NCSI_PKT_RSP_DBF, ncsi_rsp_handler_dbf }, + { NCSI_PKT_RSP_EGMF, ncsi_rsp_handler_egmf }, + { NCSI_PKT_RSP_DGMF, ncsi_rsp_handler_dgmf }, + { NCSI_PKT_RSP_SNFC, ncsi_rsp_handler_snfc }, + { NCSI_PKT_RSP_GVI, ncsi_rsp_handler_gvi }, + { NCSI_PKT_RSP_GC, ncsi_rsp_handler_gc }, + { NCSI_PKT_RSP_GP, ncsi_rsp_handler_gp }, + { NCSI_PKT_RSP_GCPS, ncsi_rsp_handler_gcps }, + { NCSI_PKT_RSP_GNS, ncsi_rsp_handler_gns }, + { NCSI_PKT_RSP_GNPTS, ncsi_rsp_handler_gnpts }, + { NCSI_PKT_RSP_OEM, ncsi_rsp_handler_default }, + { 0, NULL } +}; + +#if 0 +void ncsi_pkt_dump(struct sk_buff *skb) +{ + struct skb_shared_info *info = skb_shinfo(skb); + skb_frag_t *frag; + char *data; + int limit, i; + + pr_info("head: 0x%p data: 0x%p tail: 0x%p end: 0x%p\n", + skb->head, skb->data, + skb_tail_pointer(skb), skb_end_pointer(skb)); + pr_info("mac_header: 0x%p network_header: 0x%p\n", + skb_mac_header(skb), skb_network_header(skb)); + pr_info("len: 0x%x data_len: 0x%x truesize: 0x%x\n", + skb->len, skb->data_len, skb->truesize); + + for (i = 0; i < info->nr_frags; i++) { + frag = &info->frags[i]; + pr_info("FRAG[%d]: 0x%p offset: 0x%x size: 0x%x\n", + i, frag->page.p, frag->page_offset, frag->size); + } + + data = skb_mac_header(skb); + limit = skb->len + sizeof(struct ethhdr); + for (i = 0; i < limit; data++, i++) { + if (i % 16 == 0) + printk("\n%02x ", *data); + else + printk("%02x ", *data); + } +} +#endif + +int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, + struct packet_type *pt, struct net_device *orig_dev) +{ + struct ncsi_rsp_handler *nrh = NULL; + struct ncsi_dev *nd; + struct ncsi_dev_priv *ndp; + struct ncsi_req *nr; + struct ncsi_pkt_hdr *hdr; + int ret; + + /* Find the NCSI device */ + nd = ncsi_find_dev(dev); + ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + if (!ndp) + return -ENODEV; + + /* Check if it's AEN packet */ + hdr = (struct ncsi_pkt_hdr *)skb_network_header(skb); + if (hdr->type == NCSI_PKT_AEN) + return ncsi_aen_handler(ndp, skb); + + /* Find the handler */ + nrh = ncsi_rsp_handlers; + while (nrh->nrh_handler) { + if (nrh->nrh_type == hdr->type) + break; + + nrh++; + } + + if (!nrh->nrh_handler) { + pr_warn("NCSI: Received unrecognized packet (0x%x)\n", + hdr->type); + return -ENOENT; + } + + /* Associate with the request */ + nr = &ndp->ndp_reqs[hdr->id]; + spin_lock(&ndp->ndp_req_lock); + if (!nr->nr_used) { + spin_unlock(&ndp->ndp_req_lock); + return -ENODEV; + } + + nr->nr_rsp = skb; + if (!nr->nr_timer_enabled) { + spin_unlock(&ndp->ndp_req_lock); + ret = -ENOENT; + goto out; + } + + /* Process the response */ + ret = nrh->nrh_handler(nr); + +out: + ncsi_free_req(nr, true, false); + return ret; +} diff --git a/net/ncsi/ncsi.c b/net/ncsi/ncsi.c new file mode 100644 index 00000000000000..c84939ecca8d33 --- /dev/null +++ b/net/ncsi/ncsi.c @@ -0,0 +1,49 @@ +/* + * Copyright Gavin Shan, IBM Corporation 2015. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "internal.h" + +struct net *ncsi_net = NULL; + +static int __net_init ncsi_net_init(struct net *net) +{ + ncsi_net = net; + return ncsi_netlink_init(net); +} + +static void __net_exit ncsi_net_exit(struct net *net) +{ + ncsi_netlink_exit(net); + ncsi_net = NULL; +} + +static struct pernet_operations ncsi_net_ops = { + .init = ncsi_net_init, + .exit = ncsi_net_exit, +}; + +static int __init ncsi_init(void) +{ + if (ncsi_net) + return -EEXIST; + + return register_pernet_subsys(&ncsi_net_ops); +} + +fs_initcall_sync(ncsi_init);