Merge "drivers: cpu_cooling: vote thermal limit on CPUFREQ_INCOMPATIBLE event"
diff --git a/Documentation/filesystems/f2fs.txt b/Documentation/filesystems/f2fs.txt
index 193a034..d9a0f69 100644
--- a/Documentation/filesystems/f2fs.txt
+++ b/Documentation/filesystems/f2fs.txt
@@ -154,6 +154,26 @@
enabled by default.
data_flush Enable data flushing before checkpoint in order to
persist data of regular and symlink.
+fault_injection=%d Enable fault injection in all supported types with
+ specified injection rate.
+fault_type=%d Support configuring fault injection type, should be
+ enabled with fault_injection option, fault type value
+ is shown below, it supports single or combined type.
+ Type_Name Type_Value
+ FAULT_KMALLOC 0x000000001
+ FAULT_KVMALLOC 0x000000002
+ FAULT_PAGE_ALLOC 0x000000004
+ FAULT_PAGE_GET 0x000000008
+ FAULT_ALLOC_BIO 0x000000010
+ FAULT_ALLOC_NID 0x000000020
+ FAULT_ORPHAN 0x000000040
+ FAULT_BLOCK 0x000000080
+ FAULT_DIR_DEPTH 0x000000100
+ FAULT_EVICT_INODE 0x000000200
+ FAULT_TRUNCATE 0x000000400
+ FAULT_IO 0x000000800
+ FAULT_CHECKPOINT 0x000001000
+ FAULT_DISCARD 0x000002000
mode=%s Control block allocation mode which supports "adaptive"
and "lfs". In "lfs" mode, there should be no random
writes towards main area.
diff --git a/Documentation/hwmon/ina2xx b/Documentation/hwmon/ina2xx
index cfd31d9..f8bf140 100644
--- a/Documentation/hwmon/ina2xx
+++ b/Documentation/hwmon/ina2xx
@@ -32,7 +32,7 @@
Datasheet: Publicly available at the Texas Instruments website
http://www.ti.com/
-Author: Lothar Felten <l-felten@ti.com>
+Author: Lothar Felten <lothar.felten@gmail.com>
Description
-----------
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index f2139f5..fdc9af2 100644
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -1100,12 +1100,6 @@
nopku [X86] Disable Memory Protection Keys CPU feature found
in some Intel CPUs.
- eagerfpu= [X86]
- on enable eager fpu restore
- off disable eager fpu restore
- auto selects the default scheme, which automatically
- enables eagerfpu restore for xsaveopt.
-
module.async_probe [KNL]
Enable asynchronous probe on this module.
diff --git a/Makefile b/Makefile
index cdc22f3..3655d3c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
VERSION = 4
PATCHLEVEL = 9
-SUBLEVEL = 129
+SUBLEVEL = 133
EXTRAVERSION =
NAME = Roaring Lionus
diff --git a/arch/Kconfig b/arch/Kconfig
index ad306a9..0ecbd6d 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -530,6 +530,7 @@
bool "Use clang Link Time Optimization (LTO) (EXPERIMENTAL)"
depends on ARCH_SUPPORTS_LTO_CLANG
depends on !FTRACE_MCOUNT_RECORD || HAVE_C_RECORDMCOUNT
+ depends on !KASAN
select LTO
select THIN_ARCHIVES
select LD_DEAD_CODE_DATA_ELIMINATION
diff --git a/arch/arc/include/asm/atomic.h b/arch/arc/include/asm/atomic.h
index 54b54da..49112f7 100644
--- a/arch/arc/include/asm/atomic.h
+++ b/arch/arc/include/asm/atomic.h
@@ -84,7 +84,7 @@
"1: llock %[orig], [%[ctr]] \n" \
" " #asm_op " %[val], %[orig], %[i] \n" \
" scond %[val], [%[ctr]] \n" \
- " \n" \
+ " bnz 1b \n" \
: [val] "=&r" (val), \
[orig] "=&r" (orig) \
: [ctr] "r" (&v->counter), \
diff --git a/arch/arc/kernel/process.c b/arch/arc/kernel/process.c
index 0e8c015..3ce1213 100644
--- a/arch/arc/kernel/process.c
+++ b/arch/arc/kernel/process.c
@@ -213,6 +213,26 @@
task_thread_info(current)->thr_ptr;
}
+
+ /*
+ * setup usermode thread pointer #1:
+ * when child is picked by scheduler, __switch_to() uses @c_callee to
+ * populate usermode callee regs: this works (despite being in a kernel
+ * function) since special return path for child @ret_from_fork()
+ * ensures those regs are not clobbered all the way to RTIE to usermode
+ */
+ c_callee->r25 = task_thread_info(p)->thr_ptr;
+
+#ifdef CONFIG_ARC_CURR_IN_REG
+ /*
+ * setup usermode thread pointer #2:
+ * however for this special use of r25 in kernel, __switch_to() sets
+ * r25 for kernel needs and only in the final return path is usermode
+ * r25 setup, from pt_regs->user_r25. So set that up as well
+ */
+ c_regs->user_r25 = c_callee->r25;
+#endif
+
return 0;
}
diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi
index ce54a70..a1a9280 100644
--- a/arch/arm/boot/dts/dra7.dtsi
+++ b/arch/arm/boot/dts/dra7.dtsi
@@ -1770,7 +1770,7 @@
};
};
- dcan1: can@481cc000 {
+ dcan1: can@4ae3c000 {
compatible = "ti,dra7-d_can";
ti,hwmods = "dcan1";
reg = <0x4ae3c000 0x2000>;
@@ -1780,7 +1780,7 @@
status = "disabled";
};
- dcan2: can@481d0000 {
+ dcan2: can@48480000 {
compatible = "ti,dra7-d_can";
ti,hwmods = "dcan2";
reg = <0x48480000 0x2000>;
diff --git a/arch/arm/mach-mvebu/pmsu.c b/arch/arm/mach-mvebu/pmsu.c
index f39bd51..faaf7c3 100644
--- a/arch/arm/mach-mvebu/pmsu.c
+++ b/arch/arm/mach-mvebu/pmsu.c
@@ -116,8 +116,8 @@
PMSU_BOOT_ADDR_REDIRECT_OFFSET(hw_cpu));
}
-extern unsigned char mvebu_boot_wa_start;
-extern unsigned char mvebu_boot_wa_end;
+extern unsigned char mvebu_boot_wa_start[];
+extern unsigned char mvebu_boot_wa_end[];
/*
* This function sets up the boot address workaround needed for SMP
@@ -130,7 +130,7 @@
phys_addr_t resume_addr_reg)
{
void __iomem *sram_virt_base;
- u32 code_len = &mvebu_boot_wa_end - &mvebu_boot_wa_start;
+ u32 code_len = mvebu_boot_wa_end - mvebu_boot_wa_start;
mvebu_mbus_del_window(BOOTROM_BASE, BOOTROM_SIZE);
mvebu_mbus_add_window_by_id(crypto_eng_target, crypto_eng_attribute,
diff --git a/arch/arm/mach-omap2/omap_hwmod_reset.c b/arch/arm/mach-omap2/omap_hwmod_reset.c
index b68f9c0..d5ddba0 100644
--- a/arch/arm/mach-omap2/omap_hwmod_reset.c
+++ b/arch/arm/mach-omap2/omap_hwmod_reset.c
@@ -92,11 +92,13 @@
*/
void omap_hwmod_rtc_unlock(struct omap_hwmod *oh)
{
- local_irq_disable();
+ unsigned long flags;
+
+ local_irq_save(flags);
omap_rtc_wait_not_busy(oh);
omap_hwmod_write(OMAP_RTC_KICK0_VALUE, oh, OMAP_RTC_KICK0_REG);
omap_hwmod_write(OMAP_RTC_KICK1_VALUE, oh, OMAP_RTC_KICK1_REG);
- local_irq_enable();
+ local_irq_restore(flags);
}
/**
@@ -110,9 +112,11 @@
*/
void omap_hwmod_rtc_lock(struct omap_hwmod *oh)
{
- local_irq_disable();
+ unsigned long flags;
+
+ local_irq_save(flags);
omap_rtc_wait_not_busy(oh);
omap_hwmod_write(0x0, oh, OMAP_RTC_KICK0_REG);
omap_hwmod_write(0x0, oh, OMAP_RTC_KICK1_REG);
- local_irq_enable();
+ local_irq_restore(flags);
}
diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile
index 2b265a7..826f47b 100644
--- a/arch/arm64/Makefile
+++ b/arch/arm64/Makefile
@@ -81,7 +81,7 @@
KBUILD_CFLAGS_MODULE += -mcmodel=large
ifeq ($(CONFIG_LTO_CLANG), y)
# Code model is not stored in LLVM IR, so we need to pass it also to LLVMgold
-LDFLAGS += -plugin-opt=-code-model=large
+KBUILD_LDFLAGS_MODULE += -plugin-opt=-code-model=large
endif
endif
diff --git a/arch/arm64/boot/dts/qcom/msm8917-qrd.dtsi b/arch/arm64/boot/dts/qcom/msm8917-qrd.dtsi
index 6a4c10e..5c63ed3 100644
--- a/arch/arm64/boot/dts/qcom/msm8917-qrd.dtsi
+++ b/arch/arm64/boot/dts/qcom/msm8917-qrd.dtsi
@@ -13,7 +13,6 @@
#include <dt-bindings/clock/msm-clocks-8952.h>
#include "msm8917-camera-sensor-qrd.dtsi"
#include "msm8937-mdss-panels.dtsi"
-#include "msm8917-pmi8937.dtsi"
&blsp1_uart2 {
status = "ok";
diff --git a/arch/arm64/boot/dts/qcom/sdm439.dtsi b/arch/arm64/boot/dts/qcom/sdm439.dtsi
index a59b457..3fee488 100644
--- a/arch/arm64/boot/dts/qcom/sdm439.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm439.dtsi
@@ -675,6 +675,10 @@
};
};
+&sdhc_1 {
+ qcom,ddr-config = <0x00040868>;
+};
+
&mdss_mdp {
qcom,vbif-settings = <0xd0 0x20>;
};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr-dvt.dtsi b/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr-dvt.dtsi
index b671d0e..9409a4c 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr-dvt.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr-dvt.dtsi
@@ -69,7 +69,7 @@
sensor-position-yaw = <0>;
cam_vio-supply = <&pm8998_lvs1>;
cam_vana-supply = <&pmi8998_bob>;
- cam_vdig-supply = <&pm8998_s3>;
+ cam_vdig-supply = <&camera_eyetracking_force>;
cam_clk-supply = <&titan_top_gdsc>;
regulator-names = "cam_vio", "cam_vana", "cam_vdig",
"cam_clk";
diff --git a/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr.dtsi b/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr.dtsi
index 6052074..f19007f 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-camera-sensor-qvr.dtsi
@@ -591,7 +591,7 @@
sensor-position-yaw = <0>;
cam_vio-supply = <&pm8998_lvs1>;
cam_vana-supply = <&pmi8998_bob>;
- cam_vdig-supply = <&pm8998_s3>;
+ cam_vdig-supply = <&camera_eyetracking_force>;
cam_clk-supply = <&titan_top_gdsc>;
regulator-names = "cam_vio", "cam_vana", "cam_vdig",
"cam_clk";
diff --git a/arch/arm64/include/asm/jump_label.h b/arch/arm64/include/asm/jump_label.h
index 1b5e0e8..7e2b3e3 100644
--- a/arch/arm64/include/asm/jump_label.h
+++ b/arch/arm64/include/asm/jump_label.h
@@ -28,7 +28,7 @@
static __always_inline bool arch_static_branch(struct static_key *key, bool branch)
{
- asm goto("1: nop\n\t"
+ asm_volatile_goto("1: nop\n\t"
".pushsection __jump_table, \"aw\"\n\t"
".align 3\n\t"
".quad 1b, %l[l_yes], %c0\n\t"
@@ -42,7 +42,7 @@
static __always_inline bool arch_static_branch_jump(struct static_key *key, bool branch)
{
- asm goto("1: b %l[l_yes]\n\t"
+ asm_volatile_goto("1: b %l[l_yes]\n\t"
".pushsection __jump_table, \"aw\"\n\t"
".align 3\n\t"
".quad 1b, %l[l_yes], %c0\n\t"
diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index fe39e68..ba0d52c 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -42,6 +42,11 @@
void kvm_inject_dabt(struct kvm_vcpu *vcpu, unsigned long addr);
void kvm_inject_pabt(struct kvm_vcpu *vcpu, unsigned long addr);
+static inline bool vcpu_el1_is_32bit(struct kvm_vcpu *vcpu)
+{
+ return !(vcpu->arch.hcr_el2 & HCR_RW);
+}
+
static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu)
{
vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS;
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index d3e0a2f..e41a7b4 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -57,6 +57,45 @@
return id & ~(KVM_REG_ARCH_MASK | KVM_REG_SIZE_MASK | KVM_REG_ARM_CORE);
}
+static int validate_core_offset(const struct kvm_one_reg *reg)
+{
+ u64 off = core_reg_offset_from_id(reg->id);
+ int size;
+
+ switch (off) {
+ case KVM_REG_ARM_CORE_REG(regs.regs[0]) ...
+ KVM_REG_ARM_CORE_REG(regs.regs[30]):
+ case KVM_REG_ARM_CORE_REG(regs.sp):
+ case KVM_REG_ARM_CORE_REG(regs.pc):
+ case KVM_REG_ARM_CORE_REG(regs.pstate):
+ case KVM_REG_ARM_CORE_REG(sp_el1):
+ case KVM_REG_ARM_CORE_REG(elr_el1):
+ case KVM_REG_ARM_CORE_REG(spsr[0]) ...
+ KVM_REG_ARM_CORE_REG(spsr[KVM_NR_SPSR - 1]):
+ size = sizeof(__u64);
+ break;
+
+ case KVM_REG_ARM_CORE_REG(fp_regs.vregs[0]) ...
+ KVM_REG_ARM_CORE_REG(fp_regs.vregs[31]):
+ size = sizeof(__uint128_t);
+ break;
+
+ case KVM_REG_ARM_CORE_REG(fp_regs.fpsr):
+ case KVM_REG_ARM_CORE_REG(fp_regs.fpcr):
+ size = sizeof(__u32);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (KVM_REG_SIZE(reg->id) == size &&
+ IS_ALIGNED(off, size / sizeof(__u32)))
+ return 0;
+
+ return -EINVAL;
+}
+
static int get_core_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
{
/*
@@ -76,6 +115,9 @@
(off + (KVM_REG_SIZE(reg->id) / sizeof(__u32))) >= nr_regs)
return -ENOENT;
+ if (validate_core_offset(reg))
+ return -EINVAL;
+
if (copy_to_user(uaddr, ((u32 *)regs) + off, KVM_REG_SIZE(reg->id)))
return -EFAULT;
@@ -98,6 +140,9 @@
(off + (KVM_REG_SIZE(reg->id) / sizeof(__u32))) >= nr_regs)
return -ENOENT;
+ if (validate_core_offset(reg))
+ return -EINVAL;
+
if (KVM_REG_SIZE(reg->id) > sizeof(tmp))
return -EINVAL;
@@ -107,17 +152,25 @@
}
if (off == KVM_REG_ARM_CORE_REG(regs.pstate)) {
- u32 mode = (*(u32 *)valp) & COMPAT_PSR_MODE_MASK;
+ u64 mode = (*(u64 *)valp) & COMPAT_PSR_MODE_MASK;
switch (mode) {
case COMPAT_PSR_MODE_USR:
+ if (!system_supports_32bit_el0())
+ return -EINVAL;
+ break;
case COMPAT_PSR_MODE_FIQ:
case COMPAT_PSR_MODE_IRQ:
case COMPAT_PSR_MODE_SVC:
case COMPAT_PSR_MODE_ABT:
case COMPAT_PSR_MODE_UND:
+ if (!vcpu_el1_is_32bit(vcpu))
+ return -EINVAL;
+ break;
case PSR_MODE_EL0t:
case PSR_MODE_EL1t:
case PSR_MODE_EL1h:
+ if (vcpu_el1_is_32bit(vcpu))
+ return -EINVAL;
break;
default:
err = -EINVAL;
diff --git a/arch/hexagon/include/asm/bitops.h b/arch/hexagon/include/asm/bitops.h
index 5e4a59b..2691a18 100644
--- a/arch/hexagon/include/asm/bitops.h
+++ b/arch/hexagon/include/asm/bitops.h
@@ -211,7 +211,7 @@
* This is defined the same way as ffs.
* Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
*/
-static inline long fls(int x)
+static inline int fls(int x)
{
int r;
@@ -232,7 +232,7 @@
* the libc and compiler builtin ffs routines, therefore
* differs in spirit from the above ffz (man ffs).
*/
-static inline long ffs(int x)
+static inline int ffs(int x)
{
int r;
diff --git a/arch/hexagon/kernel/dma.c b/arch/hexagon/kernel/dma.c
index b901778..0e2be48 100644
--- a/arch/hexagon/kernel/dma.c
+++ b/arch/hexagon/kernel/dma.c
@@ -68,7 +68,7 @@
panic("Can't create %s() memory pool!", __func__);
else
gen_pool_add(coherent_pool,
- pfn_to_virt(max_low_pfn),
+ (unsigned long)pfn_to_virt(max_low_pfn),
hexagon_coherent_pool_size, -1);
}
diff --git a/arch/powerpc/kernel/fadump.c b/arch/powerpc/kernel/fadump.c
index e3acf5c..0292504 100644
--- a/arch/powerpc/kernel/fadump.c
+++ b/arch/powerpc/kernel/fadump.c
@@ -365,9 +365,9 @@
}
early_param("fadump_reserve_mem", early_fadump_reserve_mem);
-static void register_fw_dump(struct fadump_mem_struct *fdm)
+static int register_fw_dump(struct fadump_mem_struct *fdm)
{
- int rc;
+ int rc, err;
unsigned int wait_time;
pr_debug("Registering for firmware-assisted kernel dump...\n");
@@ -384,7 +384,11 @@
} while (wait_time);
+ err = -EIO;
switch (rc) {
+ default:
+ pr_err("Failed to register. Unknown Error(%d).\n", rc);
+ break;
case -1:
printk(KERN_ERR "Failed to register firmware-assisted kernel"
" dump. Hardware Error(%d).\n", rc);
@@ -392,18 +396,22 @@
case -3:
printk(KERN_ERR "Failed to register firmware-assisted kernel"
" dump. Parameter Error(%d).\n", rc);
+ err = -EINVAL;
break;
case -9:
printk(KERN_ERR "firmware-assisted kernel dump is already "
" registered.");
fw_dump.dump_registered = 1;
+ err = -EEXIST;
break;
case 0:
printk(KERN_INFO "firmware-assisted kernel dump registration"
" is successful\n");
fw_dump.dump_registered = 1;
+ err = 0;
break;
}
+ return err;
}
void crash_fadump(struct pt_regs *regs, const char *str)
@@ -1006,7 +1014,7 @@
return addr;
}
-static void register_fadump(void)
+static int register_fadump(void)
{
unsigned long addr;
void *vaddr;
@@ -1017,7 +1025,7 @@
* assisted dump.
*/
if (!fw_dump.reserve_dump_area_size)
- return;
+ return -ENODEV;
ret = fadump_setup_crash_memory_ranges();
if (ret)
@@ -1032,7 +1040,7 @@
fadump_create_elfcore_headers(vaddr);
/* register the future kernel dump with firmware. */
- register_fw_dump(&fdm);
+ return register_fw_dump(&fdm);
}
static int fadump_unregister_dump(struct fadump_mem_struct *fdm)
@@ -1218,7 +1226,6 @@
switch (buf[0]) {
case '0':
if (fw_dump.dump_registered == 0) {
- ret = -EINVAL;
goto unlock_out;
}
/* Un-register Firmware-assisted dump */
@@ -1226,11 +1233,11 @@
break;
case '1':
if (fw_dump.dump_registered == 1) {
- ret = -EINVAL;
+ ret = -EEXIST;
goto unlock_out;
}
/* Register Firmware-assisted dump */
- register_fadump();
+ ret = register_fadump();
break;
default:
ret = -EINVAL;
diff --git a/arch/powerpc/kernel/machine_kexec.c b/arch/powerpc/kernel/machine_kexec.c
index 2694d07..9dafd7a 100644
--- a/arch/powerpc/kernel/machine_kexec.c
+++ b/arch/powerpc/kernel/machine_kexec.c
@@ -186,7 +186,12 @@
(unsigned long)(crashk_res.start >> 20),
(unsigned long)(memblock_phys_mem_size() >> 20));
- memblock_reserve(crashk_res.start, crash_size);
+ if (!memblock_is_region_memory(crashk_res.start, crash_size) ||
+ memblock_reserve(crashk_res.start, crash_size)) {
+ pr_err("Failed to reserve memory for crashkernel!\n");
+ crashk_res.start = crashk_res.end = 0;
+ return;
+ }
}
int overlaps_crashkernel(unsigned long start, unsigned long size)
diff --git a/arch/powerpc/kvm/book3s_64_mmu_hv.c b/arch/powerpc/kvm/book3s_64_mmu_hv.c
index 05f09ae..915e89f 100644
--- a/arch/powerpc/kvm/book3s_64_mmu_hv.c
+++ b/arch/powerpc/kvm/book3s_64_mmu_hv.c
@@ -314,7 +314,7 @@
unsigned long pp, key;
unsigned long v, gr;
__be64 *hptep;
- int index;
+ long int index;
int virtmode = vcpu->arch.shregs.msr & (data ? MSR_DR : MSR_IR);
/* Get SLB entry */
diff --git a/arch/powerpc/platforms/powernv/pci-ioda.c b/arch/powerpc/platforms/powernv/pci-ioda.c
index f52cc6f..8015e40 100644
--- a/arch/powerpc/platforms/powernv/pci-ioda.c
+++ b/arch/powerpc/platforms/powernv/pci-ioda.c
@@ -2623,7 +2623,7 @@
level_shift = entries_shift + 3;
level_shift = max_t(unsigned, level_shift, PAGE_SHIFT);
- if ((level_shift - 3) * levels + page_shift >= 60)
+ if ((level_shift - 3) * levels + page_shift >= 55)
return -EINVAL;
/* Allocate TCE table */
diff --git a/arch/s390/mm/extmem.c b/arch/s390/mm/extmem.c
index 02042b6..e6665a6 100644
--- a/arch/s390/mm/extmem.c
+++ b/arch/s390/mm/extmem.c
@@ -79,7 +79,7 @@
struct dcss_segment {
struct list_head list;
char dcss_name[8];
- char res_name[15];
+ char res_name[16];
unsigned long start_addr;
unsigned long end;
atomic_t ref_count;
@@ -432,7 +432,7 @@
memcpy(&seg->res_name, seg->dcss_name, 8);
EBCASC(seg->res_name, 8);
seg->res_name[8] = '\0';
- strncat(seg->res_name, " (DCSS)", 7);
+ strlcat(seg->res_name, " (DCSS)", sizeof(seg->res_name));
seg->res->name = seg->res_name;
rc = seg->vm_segtype;
if (rc == SEG_TYPE_SC ||
diff --git a/arch/s390/mm/pgalloc.c b/arch/s390/mm/pgalloc.c
index 995f785..781a044 100644
--- a/arch/s390/mm/pgalloc.c
+++ b/arch/s390/mm/pgalloc.c
@@ -26,7 +26,7 @@
.data = &page_table_allocate_pgste,
.maxlen = sizeof(int),
.mode = S_IRUGO | S_IWUSR,
- .proc_handler = proc_dointvec,
+ .proc_handler = proc_dointvec_minmax,
.extra1 = &page_table_allocate_pgste_min,
.extra2 = &page_table_allocate_pgste_max,
},
diff --git a/arch/x86/crypto/crc32c-intel_glue.c b/arch/x86/crypto/crc32c-intel_glue.c
index dd19584..5773e11 100644
--- a/arch/x86/crypto/crc32c-intel_glue.c
+++ b/arch/x86/crypto/crc32c-intel_glue.c
@@ -48,21 +48,13 @@
#ifdef CONFIG_X86_64
/*
* use carryless multiply version of crc32c when buffer
- * size is >= 512 (when eager fpu is enabled) or
- * >= 1024 (when eager fpu is disabled) to account
+ * size is >= 512 to account
* for fpu state save/restore overhead.
*/
-#define CRC32C_PCL_BREAKEVEN_EAGERFPU 512
-#define CRC32C_PCL_BREAKEVEN_NOEAGERFPU 1024
+#define CRC32C_PCL_BREAKEVEN 512
asmlinkage unsigned int crc_pcl(const u8 *buffer, int len,
unsigned int crc_init);
-static int crc32c_pcl_breakeven = CRC32C_PCL_BREAKEVEN_EAGERFPU;
-#define set_pcl_breakeven_point() \
-do { \
- if (!use_eager_fpu()) \
- crc32c_pcl_breakeven = CRC32C_PCL_BREAKEVEN_NOEAGERFPU; \
-} while (0)
#endif /* CONFIG_X86_64 */
static u32 crc32c_intel_le_hw_byte(u32 crc, unsigned char const *data, size_t length)
@@ -185,7 +177,7 @@
* use faster PCL version if datasize is large enough to
* overcome kernel fpu state save/restore overhead
*/
- if (len >= crc32c_pcl_breakeven && irq_fpu_usable()) {
+ if (len >= CRC32C_PCL_BREAKEVEN && irq_fpu_usable()) {
kernel_fpu_begin();
*crcp = crc_pcl(data, len, *crcp);
kernel_fpu_end();
@@ -197,7 +189,7 @@
static int __crc32c_pcl_intel_finup(u32 *crcp, const u8 *data, unsigned int len,
u8 *out)
{
- if (len >= crc32c_pcl_breakeven && irq_fpu_usable()) {
+ if (len >= CRC32C_PCL_BREAKEVEN && irq_fpu_usable()) {
kernel_fpu_begin();
*(__le32 *)out = ~cpu_to_le32(crc_pcl(data, len, *crcp));
kernel_fpu_end();
@@ -257,7 +249,6 @@
alg.update = crc32c_pcl_intel_update;
alg.finup = crc32c_pcl_intel_finup;
alg.digest = crc32c_pcl_intel_digest;
- set_pcl_breakeven_point();
}
#endif
return crypto_register_shash(&alg);
diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index 28f3691..d764992 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -91,7 +91,7 @@
.endm
.macro TRACE_IRQS_IRETQ_DEBUG
- bt $9, EFLAGS(%rsp) /* interrupts off? */
+ btl $9, EFLAGS(%rsp) /* interrupts off? */
jnc 1f
TRACE_IRQS_ON_DEBUG
1:
@@ -485,7 +485,7 @@
#ifdef CONFIG_PREEMPT
/* Interrupts are off */
/* Check if we need preemption */
- bt $9, EFLAGS(%rsp) /* were interrupts off? */
+ btl $9, EFLAGS(%rsp) /* were interrupts off? */
jnc 1f
0: cmpl $0, PER_CPU_VAR(__preempt_count)
jnz 1f
diff --git a/arch/x86/entry/vdso/vclock_gettime.c b/arch/x86/entry/vdso/vclock_gettime.c
index 02223cb..1e96709 100644
--- a/arch/x86/entry/vdso/vclock_gettime.c
+++ b/arch/x86/entry/vdso/vclock_gettime.c
@@ -37,8 +37,9 @@
notrace static long vdso_fallback_gettime(long clock, struct timespec *ts)
{
long ret;
- asm("syscall" : "=a" (ret) :
- "0" (__NR_clock_gettime), "D" (clock), "S" (ts) : "memory");
+ asm ("syscall" : "=a" (ret), "=m" (*ts) :
+ "0" (__NR_clock_gettime), "D" (clock), "S" (ts) :
+ "memory", "rcx", "r11");
return ret;
}
@@ -46,8 +47,9 @@
{
long ret;
- asm("syscall" : "=a" (ret) :
- "0" (__NR_gettimeofday), "D" (tv), "S" (tz) : "memory");
+ asm ("syscall" : "=a" (ret), "=m" (*tv), "=m" (*tz) :
+ "0" (__NR_gettimeofday), "D" (tv), "S" (tz) :
+ "memory", "rcx", "r11");
return ret;
}
@@ -58,13 +60,13 @@
{
long ret;
- asm(
+ asm (
"mov %%ebx, %%edx \n"
- "mov %2, %%ebx \n"
+ "mov %[clock], %%ebx \n"
"call __kernel_vsyscall \n"
"mov %%edx, %%ebx \n"
- : "=a" (ret)
- : "0" (__NR_clock_gettime), "g" (clock), "c" (ts)
+ : "=a" (ret), "=m" (*ts)
+ : "0" (__NR_clock_gettime), [clock] "g" (clock), "c" (ts)
: "memory", "edx");
return ret;
}
@@ -73,13 +75,13 @@
{
long ret;
- asm(
+ asm (
"mov %%ebx, %%edx \n"
- "mov %2, %%ebx \n"
+ "mov %[tv], %%ebx \n"
"call __kernel_vsyscall \n"
"mov %%edx, %%ebx \n"
- : "=a" (ret)
- : "0" (__NR_gettimeofday), "g" (tv), "c" (tz)
+ : "=a" (ret), "=m" (*tv), "=m" (*tz)
+ : "0" (__NR_gettimeofday), [tv] "g" (tv), "c" (tz)
: "memory", "edx");
return ret;
}
diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c
index 5d103a8..10c1a5c 100644
--- a/arch/x86/events/intel/lbr.c
+++ b/arch/x86/events/intel/lbr.c
@@ -342,7 +342,7 @@
mask = x86_pmu.lbr_nr - 1;
tos = task_ctx->tos;
- for (i = 0; i < tos; i++) {
+ for (i = 0; i < task_ctx->valid_lbrs; i++) {
lbr_idx = (tos - i) & mask;
wrlbr_from(lbr_idx, task_ctx->lbr_from[i]);
wrlbr_to (lbr_idx, task_ctx->lbr_to[i]);
@@ -350,6 +350,15 @@
if (x86_pmu.intel_cap.lbr_format == LBR_FORMAT_INFO)
wrmsrl(MSR_LBR_INFO_0 + lbr_idx, task_ctx->lbr_info[i]);
}
+
+ for (; i < x86_pmu.lbr_nr; i++) {
+ lbr_idx = (tos - i) & mask;
+ wrlbr_from(lbr_idx, 0);
+ wrlbr_to(lbr_idx, 0);
+ if (x86_pmu.intel_cap.lbr_format == LBR_FORMAT_INFO)
+ wrmsrl(MSR_LBR_INFO_0 + lbr_idx, 0);
+ }
+
wrmsrl(x86_pmu.lbr_tos, tos);
task_ctx->lbr_stack_state = LBR_NONE;
}
@@ -357,7 +366,7 @@
static void __intel_pmu_lbr_save(struct x86_perf_task_context *task_ctx)
{
unsigned lbr_idx, mask;
- u64 tos;
+ u64 tos, from;
int i;
if (task_ctx->lbr_callstack_users == 0) {
@@ -367,13 +376,17 @@
mask = x86_pmu.lbr_nr - 1;
tos = intel_pmu_lbr_tos();
- for (i = 0; i < tos; i++) {
+ for (i = 0; i < x86_pmu.lbr_nr; i++) {
lbr_idx = (tos - i) & mask;
- task_ctx->lbr_from[i] = rdlbr_from(lbr_idx);
+ from = rdlbr_from(lbr_idx);
+ if (!from)
+ break;
+ task_ctx->lbr_from[i] = from;
task_ctx->lbr_to[i] = rdlbr_to(lbr_idx);
if (x86_pmu.intel_cap.lbr_format == LBR_FORMAT_INFO)
rdmsrl(MSR_LBR_INFO_0 + lbr_idx, task_ctx->lbr_info[i]);
}
+ task_ctx->valid_lbrs = i;
task_ctx->tos = tos;
task_ctx->lbr_stack_state = LBR_VALID;
}
@@ -522,7 +535,7 @@
*/
static void intel_pmu_lbr_read_64(struct cpu_hw_events *cpuc)
{
- bool need_info = false;
+ bool need_info = false, call_stack = false;
unsigned long mask = x86_pmu.lbr_nr - 1;
int lbr_format = x86_pmu.intel_cap.lbr_format;
u64 tos = intel_pmu_lbr_tos();
@@ -533,7 +546,7 @@
if (cpuc->lbr_sel) {
need_info = !(cpuc->lbr_sel->config & LBR_NO_INFO);
if (cpuc->lbr_sel->config & LBR_CALL_STACK)
- num = tos;
+ call_stack = true;
}
for (i = 0; i < num; i++) {
@@ -546,6 +559,13 @@
from = rdlbr_from(lbr_idx);
to = rdlbr_to(lbr_idx);
+ /*
+ * Read LBR call stack entries
+ * until invalid entry (0s) is detected.
+ */
+ if (call_stack && !from)
+ break;
+
if (lbr_format == LBR_FORMAT_INFO && need_info) {
u64 info;
@@ -1175,4 +1195,8 @@
x86_pmu.lbr_sel_mask = LBR_SEL_MASK;
x86_pmu.lbr_sel_map = snb_lbr_sel_map;
+
+ /* Knights Landing does have MISPREDICT bit */
+ if (x86_pmu.intel_cap.lbr_format == LBR_FORMAT_LIP)
+ x86_pmu.intel_cap.lbr_format = LBR_FORMAT_EIP_FLAGS;
}
diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h
index f356317..1bfebbc 100644
--- a/arch/x86/events/perf_event.h
+++ b/arch/x86/events/perf_event.h
@@ -633,6 +633,7 @@
u64 lbr_to[MAX_LBR_ENTRIES];
u64 lbr_info[MAX_LBR_ENTRIES];
int tos;
+ int valid_lbrs;
int lbr_callstack_users;
int lbr_stack_state;
};
diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
index fbc1474..f6d1bc9 100644
--- a/arch/x86/include/asm/cpufeatures.h
+++ b/arch/x86/include/asm/cpufeatures.h
@@ -104,7 +104,6 @@
#define X86_FEATURE_EXTD_APICID ( 3*32+26) /* has extended APICID (8 bits) */
#define X86_FEATURE_AMD_DCM ( 3*32+27) /* multi-node processor */
#define X86_FEATURE_APERFMPERF ( 3*32+28) /* APERFMPERF */
-/* free, was #define X86_FEATURE_EAGER_FPU ( 3*32+29) * "eagerfpu" Non lazy FPU restore */
#define X86_FEATURE_NONSTOP_TSC_S3 ( 3*32+30) /* TSC doesn't stop in S3 state */
/* Intel-defined CPU features, CPUID level 0x00000001 (ecx), word 4 */
diff --git a/arch/x86/include/asm/fixmap.h b/arch/x86/include/asm/fixmap.h
index 8554f96..2515284 100644
--- a/arch/x86/include/asm/fixmap.h
+++ b/arch/x86/include/asm/fixmap.h
@@ -14,6 +14,16 @@
#ifndef _ASM_X86_FIXMAP_H
#define _ASM_X86_FIXMAP_H
+/*
+ * Exposed to assembly code for setting up initial page tables. Cannot be
+ * calculated in assembly code (fixmap entries are an enum), but is sanity
+ * checked in the actual fixmap C code to make sure that the fixmap is
+ * covered fully.
+ */
+#define FIXMAP_PMD_NUM 2
+/* fixmap starts downwards from the 507th entry in level2_fixmap_pgt */
+#define FIXMAP_PMD_TOP 507
+
#ifndef __ASSEMBLY__
#include <linux/kernel.h>
#include <asm/acpi.h>
diff --git a/arch/x86/include/asm/fpu/internal.h b/arch/x86/include/asm/fpu/internal.h
index 8852e3a..499d6ed 100644
--- a/arch/x86/include/asm/fpu/internal.h
+++ b/arch/x86/include/asm/fpu/internal.h
@@ -60,11 +60,6 @@
/*
* FPU related CPU feature flag helper routines:
*/
-static __always_inline __pure bool use_eager_fpu(void)
-{
- return true;
-}
-
static __always_inline __pure bool use_xsaveopt(void)
{
return static_cpu_has(X86_FEATURE_XSAVEOPT);
@@ -501,24 +496,6 @@
}
-/*
- * Wrap lazy FPU TS handling in a 'hw fpregs activation/deactivation'
- * idiom, which is then paired with the sw-flag (fpregs_active) later on:
- */
-
-static inline void __fpregs_activate_hw(void)
-{
- if (!use_eager_fpu())
- clts();
-}
-
-static inline void __fpregs_deactivate_hw(void)
-{
- if (!use_eager_fpu())
- stts();
-}
-
-/* Must be paired with an 'stts' (fpregs_deactivate_hw()) after! */
static inline void __fpregs_deactivate(struct fpu *fpu)
{
WARN_ON_FPU(!fpu->fpregs_active);
@@ -528,7 +505,6 @@
trace_x86_fpu_regs_deactivated(fpu);
}
-/* Must be paired with a 'clts' (fpregs_activate_hw()) before! */
static inline void __fpregs_activate(struct fpu *fpu)
{
WARN_ON_FPU(fpu->fpregs_active);
@@ -554,22 +530,17 @@
}
/*
- * Encapsulate the CR0.TS handling together with the
- * software flag.
- *
* These generally need preemption protection to work,
* do try to avoid using these on their own.
*/
static inline void fpregs_activate(struct fpu *fpu)
{
- __fpregs_activate_hw();
__fpregs_activate(fpu);
}
static inline void fpregs_deactivate(struct fpu *fpu)
{
__fpregs_deactivate(fpu);
- __fpregs_deactivate_hw();
}
/*
@@ -596,8 +567,7 @@
* or if the past 5 consecutive context-switches used math.
*/
fpu.preload = static_cpu_has(X86_FEATURE_FPU) &&
- new_fpu->fpstate_active &&
- (use_eager_fpu() || new_fpu->counter > 5);
+ new_fpu->fpstate_active;
if (old_fpu->fpregs_active) {
if (!copy_fpregs_to_fpstate(old_fpu))
@@ -611,18 +581,13 @@
/* Don't change CR0.TS if we just switch! */
if (fpu.preload) {
- new_fpu->counter++;
__fpregs_activate(new_fpu);
trace_x86_fpu_regs_activated(new_fpu);
prefetch(&new_fpu->state);
- } else {
- __fpregs_deactivate_hw();
}
} else {
- old_fpu->counter = 0;
old_fpu->last_cpu = -1;
if (fpu.preload) {
- new_fpu->counter++;
if (fpu_want_lazy_restore(new_fpu, cpu))
fpu.preload = 0;
else
diff --git a/arch/x86/include/asm/fpu/types.h b/arch/x86/include/asm/fpu/types.h
index 48df486..3c80f5b 100644
--- a/arch/x86/include/asm/fpu/types.h
+++ b/arch/x86/include/asm/fpu/types.h
@@ -322,17 +322,6 @@
unsigned char fpregs_active;
/*
- * @counter:
- *
- * This counter contains the number of consecutive context switches
- * during which the FPU stays used. If this is over a threshold, the
- * lazy FPU restore logic becomes eager, to save the trap overhead.
- * This is an unsigned char so that after 256 iterations the counter
- * wraps and the context switch behavior turns lazy again; this is to
- * deal with bursty apps that only use the FPU for a short time:
- */
- unsigned char counter;
- /*
* @state:
*
* In-memory copy of all FPU registers that we save/restore
@@ -340,29 +329,6 @@
* the registers in the FPU are more recent than this state
* copy. If the task context-switches away then they get
* saved here and represent the FPU state.
- *
- * After context switches there may be a (short) time period
- * during which the in-FPU hardware registers are unchanged
- * and still perfectly match this state, if the tasks
- * scheduled afterwards are not using the FPU.
- *
- * This is the 'lazy restore' window of optimization, which
- * we track though 'fpu_fpregs_owner_ctx' and 'fpu->last_cpu'.
- *
- * We detect whether a subsequent task uses the FPU via setting
- * CR0::TS to 1, which causes any FPU use to raise a #NM fault.
- *
- * During this window, if the task gets scheduled again, we
- * might be able to skip having to do a restore from this
- * memory buffer to the hardware registers - at the cost of
- * incurring the overhead of #NM fault traps.
- *
- * Note that on modern CPUs that support the XSAVEOPT (or other
- * optimized XSAVE instructions), we don't use #NM traps anymore,
- * as the hardware can track whether FPU registers need saving
- * or not. On such CPUs we activate the non-lazy ('eagerfpu')
- * logic, which unconditionally saves/restores all FPU state
- * across context switches. (if FPU state exists.)
*/
union fpregs_state state;
/*
diff --git a/arch/x86/include/asm/pgtable_64.h b/arch/x86/include/asm/pgtable_64.h
index 221a32e..d5c4df9 100644
--- a/arch/x86/include/asm/pgtable_64.h
+++ b/arch/x86/include/asm/pgtable_64.h
@@ -13,13 +13,14 @@
#include <asm/processor.h>
#include <linux/bitops.h>
#include <linux/threads.h>
+#include <asm/fixmap.h>
extern pud_t level3_kernel_pgt[512];
extern pud_t level3_ident_pgt[512];
extern pmd_t level2_kernel_pgt[512];
extern pmd_t level2_fixmap_pgt[512];
extern pmd_t level2_ident_pgt[512];
-extern pte_t level1_fixmap_pgt[512];
+extern pte_t level1_fixmap_pgt[512 * FIXMAP_PMD_NUM];
extern pgd_t init_level4_pgt[];
#define swapper_pg_dir init_level4_pgt
diff --git a/arch/x86/include/asm/trace/fpu.h b/arch/x86/include/asm/trace/fpu.h
index 9217ab1..342e597 100644
--- a/arch/x86/include/asm/trace/fpu.h
+++ b/arch/x86/include/asm/trace/fpu.h
@@ -14,7 +14,6 @@
__field(struct fpu *, fpu)
__field(bool, fpregs_active)
__field(bool, fpstate_active)
- __field(int, counter)
__field(u64, xfeatures)
__field(u64, xcomp_bv)
),
@@ -23,17 +22,15 @@
__entry->fpu = fpu;
__entry->fpregs_active = fpu->fpregs_active;
__entry->fpstate_active = fpu->fpstate_active;
- __entry->counter = fpu->counter;
if (boot_cpu_has(X86_FEATURE_OSXSAVE)) {
__entry->xfeatures = fpu->state.xsave.header.xfeatures;
__entry->xcomp_bv = fpu->state.xsave.header.xcomp_bv;
}
),
- TP_printk("x86/fpu: %p fpregs_active: %d fpstate_active: %d counter: %d xfeatures: %llx xcomp_bv: %llx",
+ TP_printk("x86/fpu: %p fpregs_active: %d fpstate_active: %d xfeatures: %llx xcomp_bv: %llx",
__entry->fpu,
__entry->fpregs_active,
__entry->fpstate_active,
- __entry->counter,
__entry->xfeatures,
__entry->xcomp_bv
)
diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c
index 430c095..fc96511 100644
--- a/arch/x86/kernel/fpu/core.c
+++ b/arch/x86/kernel/fpu/core.c
@@ -59,27 +59,9 @@
return this_cpu_read(in_kernel_fpu);
}
-/*
- * Were we in an interrupt that interrupted kernel mode?
- *
- * On others, we can do a kernel_fpu_begin/end() pair *ONLY* if that
- * pair does nothing at all: the thread must not have fpu (so
- * that we don't try to save the FPU state), and TS must
- * be set (so that the clts/stts pair does nothing that is
- * visible in the interrupted kernel thread).
- *
- * Except for the eagerfpu case when we return true; in the likely case
- * the thread has FPU but we are not going to set/clear TS.
- */
static bool interrupted_kernel_fpu_idle(void)
{
- if (kernel_fpu_disabled())
- return false;
-
- if (use_eager_fpu())
- return true;
-
- return !current->thread.fpu.fpregs_active && (read_cr0() & X86_CR0_TS);
+ return !kernel_fpu_disabled();
}
/*
@@ -127,7 +109,6 @@
copy_fpregs_to_fpstate(fpu);
} else {
this_cpu_write(fpu_fpregs_owner_ctx, NULL);
- __fpregs_activate_hw();
}
}
EXPORT_SYMBOL(__kernel_fpu_begin);
@@ -138,8 +119,6 @@
if (fpu->fpregs_active)
copy_kernel_to_fpregs(&fpu->state);
- else
- __fpregs_deactivate_hw();
kernel_fpu_enable();
}
@@ -201,10 +180,7 @@
trace_x86_fpu_before_save(fpu);
if (fpu->fpregs_active) {
if (!copy_fpregs_to_fpstate(fpu)) {
- if (use_eager_fpu())
- copy_kernel_to_fpregs(&fpu->state);
- else
- fpregs_deactivate(fpu);
+ copy_kernel_to_fpregs(&fpu->state);
}
}
trace_x86_fpu_after_save(fpu);
@@ -249,7 +225,6 @@
int fpu__copy(struct fpu *dst_fpu, struct fpu *src_fpu)
{
- dst_fpu->counter = 0;
dst_fpu->fpregs_active = 0;
dst_fpu->last_cpu = -1;
@@ -262,8 +237,7 @@
* Don't let 'init optimized' areas of the XSAVE area
* leak into the child task:
*/
- if (use_eager_fpu())
- memset(&dst_fpu->state.xsave, 0, fpu_kernel_xstate_size);
+ memset(&dst_fpu->state.xsave, 0, fpu_kernel_xstate_size);
/*
* Save current FPU registers directly into the child
@@ -285,10 +259,7 @@
memcpy(&src_fpu->state, &dst_fpu->state,
fpu_kernel_xstate_size);
- if (use_eager_fpu())
- copy_kernel_to_fpregs(&src_fpu->state);
- else
- fpregs_deactivate(src_fpu);
+ copy_kernel_to_fpregs(&src_fpu->state);
}
preempt_enable();
@@ -461,7 +432,6 @@
trace_x86_fpu_before_restore(fpu);
fpregs_activate(fpu);
copy_kernel_to_fpregs(&fpu->state);
- fpu->counter++;
trace_x86_fpu_after_restore(fpu);
kernel_fpu_enable();
}
@@ -479,7 +449,6 @@
void fpu__drop(struct fpu *fpu)
{
preempt_disable();
- fpu->counter = 0;
if (fpu->fpregs_active) {
/* Ignore delayed exceptions from user space */
diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c
index 3ec0d2d..3a93186 100644
--- a/arch/x86/kernel/fpu/signal.c
+++ b/arch/x86/kernel/fpu/signal.c
@@ -344,11 +344,9 @@
}
fpu->fpstate_active = 1;
- if (use_eager_fpu()) {
- preempt_disable();
- fpu__restore(fpu);
- preempt_enable();
- }
+ preempt_disable();
+ fpu__restore(fpu);
+ preempt_enable();
return err;
} else {
diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c
index abfbb61b..e9d7f46 100644
--- a/arch/x86/kernel/fpu/xstate.c
+++ b/arch/x86/kernel/fpu/xstate.c
@@ -890,15 +890,6 @@
*/
if (!boot_cpu_has(X86_FEATURE_OSPKE))
return -EINVAL;
- /*
- * For most XSAVE components, this would be an arduous task:
- * brining fpstate up to date with fpregs, updating fpstate,
- * then re-populating fpregs. But, for components that are
- * never lazily managed, we can just access the fpregs
- * directly. PKRU is never managed lazily, so we can just
- * manipulate it directly. Make sure it stays that way.
- */
- WARN_ON_ONCE(!use_eager_fpu());
/* Set the bits we need in PKRU: */
if (init_val & PKEY_DISABLE_ACCESS)
diff --git a/arch/x86/kernel/head_64.S b/arch/x86/kernel/head_64.S
index 9d72cf5..b0d6697 100644
--- a/arch/x86/kernel/head_64.S
+++ b/arch/x86/kernel/head_64.S
@@ -23,6 +23,7 @@
#include "../entry/calling.h"
#include <asm/export.h>
#include <asm/nospec-branch.h>
+#include <asm/fixmap.h>
#ifdef CONFIG_PARAVIRT
#include <asm/asm-offsets.h>
@@ -493,13 +494,20 @@
KERNEL_IMAGE_SIZE/PMD_SIZE)
NEXT_PAGE(level2_fixmap_pgt)
- .fill 506,8,0
- .quad level1_fixmap_pgt - __START_KERNEL_map + _PAGE_TABLE
- /* 8MB reserved for vsyscalls + a 2MB hole = 4 + 1 entries */
- .fill 5,8,0
+ .fill (512 - 4 - FIXMAP_PMD_NUM),8,0
+ pgtno = 0
+ .rept (FIXMAP_PMD_NUM)
+ .quad level1_fixmap_pgt + (pgtno << PAGE_SHIFT) - __START_KERNEL_map \
+ + _PAGE_TABLE;
+ pgtno = pgtno + 1
+ .endr
+ /* 6 MB reserved space + a 2MB hole */
+ .fill 4,8,0
NEXT_PAGE(level1_fixmap_pgt)
+ .rept (FIXMAP_PMD_NUM)
.fill 512,8,0
+ .endr
#undef PMDS
diff --git a/arch/x86/kernel/tsc_msr.c b/arch/x86/kernel/tsc_msr.c
index 0fe720d..3f818ce 100644
--- a/arch/x86/kernel/tsc_msr.c
+++ b/arch/x86/kernel/tsc_msr.c
@@ -12,6 +12,7 @@
#include <asm/setup.h>
#include <asm/apic.h>
#include <asm/param.h>
+#include <asm/tsc.h>
#define MAX_NUM_FREQS 9
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 7e5119c..c17d389 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -16,7 +16,6 @@
#include <linux/export.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
-#include <asm/fpu/internal.h> /* For use_eager_fpu. Ugh! */
#include <asm/user.h>
#include <asm/fpu/xstate.h>
#include "cpuid.h"
@@ -114,8 +113,7 @@
if (best && (best->eax & (F(XSAVES) | F(XSAVEC))))
best->ebx = xstate_required_size(vcpu->arch.xcr0, true);
- if (use_eager_fpu())
- kvm_x86_ops->fpu_activate(vcpu);
+ kvm_x86_ops->fpu_activate(vcpu);
/*
* The existing code assumes virtual address is 48-bit in the canonical
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 203d423..5013ef1 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -7631,16 +7631,6 @@
copy_fpregs_to_fpstate(&vcpu->arch.guest_fpu);
__kernel_fpu_end();
++vcpu->stat.fpu_reload;
- /*
- * If using eager FPU mode, or if the guest is a frequent user
- * of the FPU, just leave the FPU active for next time.
- * Every 255 times fpu_counter rolls over to 0; a guest that uses
- * the FPU in bursts will revert to loading it on demand.
- */
- if (!use_eager_fpu()) {
- if (++vcpu->fpu_counter < 5)
- kvm_make_request(KVM_REQ_DEACTIVATE_FPU, vcpu);
- }
trace_kvm_fpu(0);
}
diff --git a/arch/x86/mm/numa_emulation.c b/arch/x86/mm/numa_emulation.c
index a8f90ce..dc6d990 100644
--- a/arch/x86/mm/numa_emulation.c
+++ b/arch/x86/mm/numa_emulation.c
@@ -60,7 +60,7 @@
eb->nid = nid;
if (emu_nid_to_phys[nid] == NUMA_NO_NODE)
- emu_nid_to_phys[nid] = nid;
+ emu_nid_to_phys[nid] = pb->nid;
pb->start += size;
if (pb->start >= pb->end) {
diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c
index e30baa8..8cbed30 100644
--- a/arch/x86/mm/pgtable.c
+++ b/arch/x86/mm/pgtable.c
@@ -536,6 +536,15 @@
{
unsigned long address = __fix_to_virt(idx);
+#ifdef CONFIG_X86_64
+ /*
+ * Ensure that the static initial page tables are covering the
+ * fixmap completely.
+ */
+ BUILD_BUG_ON(__end_of_permanent_fixed_addresses >
+ (FIXMAP_PMD_NUM * PTRS_PER_PTE));
+#endif
+
if (idx >= __end_of_fixed_addresses) {
BUG();
return;
diff --git a/arch/x86/mm/pkeys.c b/arch/x86/mm/pkeys.c
index 0bbec04..e2d2b3c 100644
--- a/arch/x86/mm/pkeys.c
+++ b/arch/x86/mm/pkeys.c
@@ -142,8 +142,7 @@
* Called from the FPU code when creating a fresh set of FPU
* registers. This is called from a very specific context where
* we know the FPU regstiers are safe for use and we can use PKRU
- * directly. The fact that PKRU is only available when we are
- * using eagerfpu mode makes this possible.
+ * directly.
*/
void copy_init_pkru_to_fpregs(void)
{
diff --git a/arch/x86/xen/mmu.c b/arch/x86/xen/mmu.c
index c92f75f..ebceaba 100644
--- a/arch/x86/xen/mmu.c
+++ b/arch/x86/xen/mmu.c
@@ -1936,7 +1936,7 @@
* L3_k[511] -> level2_fixmap_pgt */
convert_pfn_mfn(level3_kernel_pgt);
- /* L3_k[511][506] -> level1_fixmap_pgt */
+ /* L3_k[511][508-FIXMAP_PMD_NUM ... 507] -> level1_fixmap_pgt */
convert_pfn_mfn(level2_fixmap_pgt);
}
/* We get [511][511] and have Xen's version of level2_kernel_pgt */
@@ -1970,7 +1970,11 @@
set_page_prot(level2_ident_pgt, PAGE_KERNEL_RO);
set_page_prot(level2_kernel_pgt, PAGE_KERNEL_RO);
set_page_prot(level2_fixmap_pgt, PAGE_KERNEL_RO);
- set_page_prot(level1_fixmap_pgt, PAGE_KERNEL_RO);
+
+ for (i = 0; i < FIXMAP_PMD_NUM; i++) {
+ set_page_prot(level1_fixmap_pgt + i * PTRS_PER_PTE,
+ PAGE_KERNEL_RO);
+ }
/* Pin down new L4 */
pin_pagetable_pfn(MMUEXT_PIN_L4_TABLE,
diff --git a/arch/x86/xen/pmu.c b/arch/x86/xen/pmu.c
index b9fc525..0b29a43 100644
--- a/arch/x86/xen/pmu.c
+++ b/arch/x86/xen/pmu.c
@@ -477,7 +477,7 @@
irqreturn_t xen_pmu_irq_handler(int irq, void *dev_id)
{
int err, ret = IRQ_NONE;
- struct pt_regs regs;
+ struct pt_regs regs = {0};
const struct xen_pmu_data *xenpmu_data = get_xenpmu_data();
uint8_t xenpmu_flags = get_xenpmu_flags();
diff --git a/crypto/ablkcipher.c b/crypto/ablkcipher.c
index 860c9e5..3bc0e76 100644
--- a/crypto/ablkcipher.c
+++ b/crypto/ablkcipher.c
@@ -367,6 +367,7 @@
strncpy(rblkcipher.type, "ablkcipher", sizeof(rblkcipher.type));
strncpy(rblkcipher.geniv, alg->cra_ablkcipher.geniv ?: "<default>",
sizeof(rblkcipher.geniv));
+ rblkcipher.geniv[sizeof(rblkcipher.geniv) - 1] = '\0';
rblkcipher.blocksize = alg->cra_blocksize;
rblkcipher.min_keysize = alg->cra_ablkcipher.min_keysize;
@@ -441,6 +442,7 @@
strncpy(rblkcipher.type, "givcipher", sizeof(rblkcipher.type));
strncpy(rblkcipher.geniv, alg->cra_ablkcipher.geniv ?: "<built-in>",
sizeof(rblkcipher.geniv));
+ rblkcipher.geniv[sizeof(rblkcipher.geniv) - 1] = '\0';
rblkcipher.blocksize = alg->cra_blocksize;
rblkcipher.min_keysize = alg->cra_ablkcipher.min_keysize;
diff --git a/crypto/blkcipher.c b/crypto/blkcipher.c
index 27f9866..59a0936 100644
--- a/crypto/blkcipher.c
+++ b/crypto/blkcipher.c
@@ -510,6 +510,7 @@
strncpy(rblkcipher.type, "blkcipher", sizeof(rblkcipher.type));
strncpy(rblkcipher.geniv, alg->cra_blkcipher.geniv ?: "<default>",
sizeof(rblkcipher.geniv));
+ rblkcipher.geniv[sizeof(rblkcipher.geniv) - 1] = '\0';
rblkcipher.blocksize = alg->cra_blocksize;
rblkcipher.min_keysize = alg->cra_blkcipher.min_keysize;
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 9480d84..5960816 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -138,6 +138,8 @@
source "drivers/xen/Kconfig"
+source "drivers/vservices/Kconfig"
+
source "drivers/staging/Kconfig"
source "drivers/platform/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 06e2bb4..557cba5 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -10,6 +10,8 @@
obj-$(CONFIG_GENERIC_PHY) += phy/
+obj-$(CONFIG_VSERVICES_SUPPORT) += vservices/
+
# GPIO must come after pinctrl as gpios may need to mux pins etc
obj-$(CONFIG_PINCTRL) += pinctrl/
obj-$(CONFIG_GPIOLIB) += gpio/
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index dc259d2..574d08f 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -1362,8 +1362,10 @@
dpm_wait_for_children(dev, async);
- if (async_error)
+ if (async_error) {
+ dev->power.direct_complete = false;
goto Complete;
+ }
/*
* If a device configured to wake up the system from sleep states
@@ -1378,6 +1380,7 @@
pm_get_active_wakeup_sources(suspend_abort,
MAX_SUSPEND_ABORT_LEN);
log_suspend_abort_reason(suspend_abort);
+ dev->power.direct_complete = false;
async_error = -EBUSY;
goto Complete;
}
diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c
index e3d8e4c..a321d7d 100644
--- a/drivers/block/floppy.c
+++ b/drivers/block/floppy.c
@@ -3459,6 +3459,9 @@
(struct floppy_struct **)&outparam);
if (ret)
return ret;
+ memcpy(&inparam.g, outparam,
+ offsetof(struct floppy_struct, name));
+ outparam = &inparam.g;
break;
case FDMSGON:
UDP->flags |= FTD_MSG;
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 44bccb1..8dce1a8 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -349,6 +349,7 @@
{ USB_DEVICE(0x7392, 0xa611), .driver_info = BTUSB_REALTEK },
/* Additional Realtek 8723DE Bluetooth devices */
+ { USB_DEVICE(0x0bda, 0xb009), .driver_info = BTUSB_REALTEK },
{ USB_DEVICE(0x2ff8, 0xb011), .driver_info = BTUSB_REALTEK },
/* Additional Realtek 8821AE Bluetooth devices */
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index b0d0181..fac8f87 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -623,3 +623,11 @@
endmenu
+config OKL4_PIPE
+ bool "OKL4 Pipe Driver"
+ depends on OKL4_GUEST
+ default n
+ help
+ Virtual pipe driver for the OKL4 Microvisor. This driver allows
+ OKL4 Microvisor pipes to be exposed directly to user level as
+ character devices.
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 81283c4..95d8940 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -67,3 +67,5 @@
obj-$(CONFIG_MSM_ADSPRPC) += adsprpc_compat.o
endif
obj-$(CONFIG_MSM_RDBG) += rdbg.o
+obj-$(CONFIG_OKL4_PIPE) += okl4_pipe.o
+CFLAGS_okl4_pipe.o += -Werror
diff --git a/drivers/char/okl4_pipe.c b/drivers/char/okl4_pipe.c
new file mode 100644
index 0000000..e7a0d8a
--- /dev/null
+++ b/drivers/char/okl4_pipe.c
@@ -0,0 +1,677 @@
+/*
+ * drivers/char/okl4_pipe.c
+ *
+ * Copyright (c) 2015 General Dynamics
+ * Copyright (c) 2015 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * OKL4 Microvisor Pipes driver.
+ *
+ * Clients using this driver must have vclient names of the form
+ * "pipe%d", where %d is the pipe number, which must be
+ * unique and less than MAX_PIPES.
+ */
+
+/* #define DEBUG 1 */
+/* #define VERBOSE_DEBUG 1 */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/of.h>
+#include <asm/uaccess.h>
+#include <asm-generic/okl4_virq.h>
+
+#include <microvisor/microvisor.h>
+#if defined(CONFIG_OKL4_VIRTUALISATION)
+#include <asm/okl4-microvisor/okl4tags.h>
+#include <asm/okl4-microvisor/microvisor_bus.h>
+#include <asm/okl4-microvisor/virq.h>
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0)
+#define __devinit
+#define __devexit
+#define __devexit_p(x) x
+#endif
+
+#define DRIVER_NAME "okl4-pipe"
+#define DEVICE_NAME "okl4-pipe"
+
+#ifndef CONFIG_OF
+#error "okl4-pipe driver only supported on device tree kernels"
+#endif
+
+#define MAX_PIPES 8
+
+#ifdef CONFIG_OKL4_INTERLEAVED_PRIORITIES
+extern int vcpu_prio_normal;
+#endif
+
+static int okl4_pipe_major;
+static struct class *okl4_pipe_class;
+
+/* This can be extended if required */
+struct okl4_pipe_mv {
+ int pipe_id;
+};
+
+struct okl4_pipe {
+ struct okl4_pipe_data_buffer *write_buf;
+ okl4_kcap_t pipe_tx_kcap;
+ okl4_kcap_t pipe_rx_kcap;
+ int tx_irq;
+ int rx_irq;
+ size_t max_msg_size;
+ int ref_count;
+ struct mutex pipe_mutex;
+ spinlock_t pipe_lock;
+
+ struct platform_device *pdev;
+ struct cdev cdev;
+
+ bool reset;
+ bool tx_maybe_avail;
+ bool rx_maybe_avail;
+
+ wait_queue_head_t rx_wait_q;
+ wait_queue_head_t tx_wait_q;
+ wait_queue_head_t poll_wait_q;
+
+ char *rx_buf;
+ size_t rx_buf_count;
+};
+static struct okl4_pipe pipes[MAX_PIPES];
+
+static okl4_error_t
+okl4_pipe_control(okl4_kcap_t kcap, uint8_t control)
+{
+ okl4_pipe_control_t x = 0;
+
+ okl4_pipe_control_setdoop(&x, true);
+ okl4_pipe_control_setoperation(&x, control);
+ return _okl4_sys_pipe_control(kcap, x);
+}
+
+static irqreturn_t
+okl4_pipe_tx_irq(int irq, void *dev)
+{
+ struct okl4_pipe *pipe = dev;
+ okl4_pipe_state_t payload = okl4_get_virq_payload(irq);
+
+ spin_lock(&pipe->pipe_lock);
+ if (okl4_pipe_state_gettxavailable(&payload))
+ pipe->tx_maybe_avail = true;
+ if (okl4_pipe_state_getreset(&payload)) {
+ pipe->reset = true;
+ pipe->tx_maybe_avail = true;
+ }
+ spin_unlock(&pipe->pipe_lock);
+
+ wake_up_interruptible(&pipe->tx_wait_q);
+ wake_up_interruptible(&pipe->poll_wait_q);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t
+okl4_pipe_rx_irq(int irq, void *dev)
+{
+ struct okl4_pipe *pipe = dev;
+ okl4_pipe_state_t payload = okl4_get_virq_payload(irq);
+
+ spin_lock(&pipe->pipe_lock);
+ if (okl4_pipe_state_getrxavailable(&payload))
+ pipe->rx_maybe_avail = true;
+ if (okl4_pipe_state_getreset(&payload)) {
+ pipe->reset = true;
+ pipe->rx_maybe_avail = true;
+ }
+ spin_unlock(&pipe->pipe_lock);
+
+ wake_up_interruptible(&pipe->rx_wait_q);
+ wake_up_interruptible(&pipe->poll_wait_q);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t
+okl4_pipe_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct okl4_pipe_mv *priv = filp->private_data;
+ int pipe_id = priv->pipe_id;
+ struct okl4_pipe *pipe = &pipes[pipe_id];
+ struct _okl4_sys_pipe_recv_return recv_return;
+ uint32_t *buffer = NULL;
+ size_t recv = 0;
+
+ if (!count)
+ return 0;
+
+again:
+ if (pipe->reset)
+ return -EPIPE;
+
+ if (!pipe->rx_maybe_avail && (filp->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ if (wait_event_interruptible(pipe->rx_wait_q, pipe->rx_maybe_avail))
+ return -ERESTARTSYS;
+
+ if (mutex_lock_interruptible(&pipe->pipe_mutex))
+ return -ERESTARTSYS;
+
+ /* Receive buffered data first */
+ if (pipe->rx_buf_count) {
+ recv = min(pipe->rx_buf_count, count);
+
+ if (copy_to_user(buf, pipe->rx_buf, recv)) {
+ mutex_unlock(&pipe->pipe_mutex);
+ return -EFAULT;
+ }
+
+ pipe->rx_buf_count -= recv;
+
+ if (pipe->rx_buf_count) {
+ memmove(pipe->rx_buf, pipe->rx_buf + recv,
+ pipe->max_msg_size - recv);
+ }
+
+ buf += recv;
+ count -= recv;
+ if (!count) {
+ mutex_unlock(&pipe->pipe_mutex);
+ return recv;
+ }
+ }
+
+ buffer = kmalloc(pipe->max_msg_size + sizeof(uint32_t), GFP_KERNEL);
+
+ if (!buffer) {
+ mutex_unlock(&pipe->pipe_mutex);
+ return -ENOMEM;
+ }
+
+ while (count) {
+ okl4_error_t ret;
+ size_t size;
+
+ spin_lock_irq(&pipe->pipe_lock);
+ recv_return = _okl4_sys_pipe_recv(pipe->pipe_rx_kcap,
+ pipe->max_msg_size + sizeof(uint32_t),
+ (void *)buffer);
+ ret = recv_return.error;
+
+ if (ret == OKL4_ERROR_PIPE_NOT_READY ||
+ ret == OKL4_ERROR_PIPE_EMPTY) {
+ pipe->rx_maybe_avail = false;
+ if (!recv) {
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ spin_unlock_irq(&pipe->pipe_lock);
+ mutex_unlock(&pipe->pipe_mutex);
+ kfree(buffer);
+ goto again;
+ }
+ recv = -EAGAIN;
+ }
+ goto error;
+ } else if (ret != OKL4_OK) {
+ dev_err(&pipe->pdev->dev,
+ "pipe send returned error %d in okl4_pipe driver!\n",
+ (int)ret);
+ if (!recv)
+ recv = -ENXIO;
+ goto error;
+ }
+
+ spin_unlock_irq(&pipe->pipe_lock);
+
+ size = buffer[0];
+ if (size > pipe->max_msg_size) {
+ /* pipe error */
+ if (!recv)
+ recv = -EPROTO;
+ goto out;
+ }
+
+ /* Save extra received data */
+ if (size > count) {
+ pipe->rx_buf_count = size - count;
+ memcpy(pipe->rx_buf, (char*)&buffer[1] + count,
+ size - count);
+ size = count;
+ }
+
+ if (copy_to_user(buf, &buffer[1], size)) {
+ if (!recv)
+ recv = -EFAULT;
+ goto out;
+ }
+
+
+ count -= size;
+ buf += size;
+ recv += size;
+ }
+out:
+ mutex_unlock(&pipe->pipe_mutex);
+
+ kfree(buffer);
+ return recv;
+error:
+ spin_unlock_irq(&pipe->pipe_lock);
+ goto out;
+}
+
+static ssize_t
+okl4_pipe_write(struct file *filp, const char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct okl4_pipe_mv *priv = filp->private_data;
+ int pipe_id = priv->pipe_id;
+ struct okl4_pipe *pipe = &pipes[pipe_id];
+ uint32_t *buffer = NULL;
+ size_t sent = 0;
+
+ if (!count)
+ return 0;
+
+again:
+ if (pipe->reset)
+ return -EPIPE;
+
+ if (!pipe->tx_maybe_avail && (filp->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ if (wait_event_interruptible(pipe->tx_wait_q, pipe->tx_maybe_avail))
+ return -ERESTARTSYS;
+
+ if (mutex_lock_interruptible(&pipe->pipe_mutex))
+ return -ERESTARTSYS;
+
+ buffer = kmalloc(pipe->max_msg_size + sizeof(uint32_t), GFP_KERNEL);
+
+ if (!buffer) {
+ mutex_unlock(&pipe->pipe_mutex);
+ return -ENOMEM;
+ }
+
+ while (count) {
+ okl4_error_t ret;
+ size_t size = min(count, pipe->max_msg_size);
+ size_t pipe_size = roundup(size + sizeof(uint32_t),
+ sizeof(uint32_t));
+
+ if (copy_from_user(&buffer[1], buf, size)) {
+ if (!sent)
+ sent = -EFAULT;
+ break;
+ }
+
+ buffer[0] = size;
+
+ spin_lock_irq(&pipe->pipe_lock);
+ ret = _okl4_sys_pipe_send(pipe->pipe_tx_kcap, pipe_size,
+ (void *)buffer);
+ if (ret == OKL4_ERROR_PIPE_NOT_READY ||
+ ret == OKL4_ERROR_PIPE_FULL) {
+ pipe->tx_maybe_avail = false;
+ spin_unlock_irq(&pipe->pipe_lock);
+ if (!sent) {
+ if (filp->f_flags & O_NONBLOCK) {
+ sent = -EAGAIN;
+ break;
+ }
+ mutex_unlock(&pipe->pipe_mutex);
+ kfree(buffer);
+ goto again;
+ }
+ break;
+ } else if (ret != OKL4_OK) {
+ dev_err(&pipe->pdev->dev,
+ "pipe send returned error %d in okl4_pipe driver!\n",
+ (int)ret);
+ if (!sent)
+ sent = -ENXIO;
+ spin_unlock_irq(&pipe->pipe_lock);
+ break;
+ }
+ spin_unlock_irq(&pipe->pipe_lock);
+
+ count -= size;
+ buf += size;
+ sent += size;
+ }
+ mutex_unlock(&pipe->pipe_mutex);
+
+ kfree(buffer);
+ return sent;
+}
+
+
+static unsigned int
+okl4_pipe_poll(struct file *filp, struct poll_table_struct *poll_table)
+{
+ struct okl4_pipe_mv *priv = filp->private_data;
+ int pipe_id = priv->pipe_id;
+ struct okl4_pipe *pipe = &pipes[pipe_id];
+ unsigned int ret = 0;
+
+ poll_wait(filp, &pipe->poll_wait_q, poll_table);
+
+ spin_lock_irq(&pipe->pipe_lock);
+
+ if (pipe->rx_maybe_avail)
+ ret |= POLLIN | POLLRDNORM;
+ if (pipe->tx_maybe_avail)
+ ret |= POLLOUT | POLLWRNORM;
+ if (pipe->reset)
+ ret = POLLHUP;
+
+ spin_unlock_irq(&pipe->pipe_lock);
+
+ return ret;
+}
+
+static int
+okl4_pipe_open(struct inode *inode, struct file *filp)
+{
+ struct okl4_pipe *pipe = container_of(inode->i_cdev,
+ struct okl4_pipe, cdev);
+ struct okl4_pipe_mv *priv = dev_get_drvdata(&pipe->pdev->dev);
+
+ filp->private_data = priv;
+ if (!pipe->ref_count) {
+ pipe->rx_buf = kmalloc(pipe->max_msg_size, GFP_KERNEL);
+ if (!pipe->rx_buf)
+ return -ENOMEM;
+
+ mutex_init(&pipe->pipe_mutex);
+ spin_lock_init(&pipe->pipe_lock);
+
+ pipe->rx_buf_count = 0;
+ pipe->reset = false;
+ pipe->tx_maybe_avail = true;
+ pipe->rx_maybe_avail = true;
+
+ okl4_pipe_control(pipe->pipe_tx_kcap,
+ OKL4_PIPE_CONTROL_OP_SET_TX_READY);
+ okl4_pipe_control(pipe->pipe_rx_kcap,
+ OKL4_PIPE_CONTROL_OP_SET_RX_READY);
+ }
+ pipe->ref_count++;
+ return 0;
+}
+
+static int
+okl4_pipe_close(struct inode *inode, struct file *filp)
+{
+ struct okl4_pipe *pipe = container_of(inode->i_cdev,
+ struct okl4_pipe, cdev);
+
+ pipe->ref_count--;
+ if (!pipe->ref_count) {
+ okl4_pipe_control(pipe->pipe_rx_kcap,
+ OKL4_PIPE_CONTROL_OP_RESET);
+ okl4_pipe_control(pipe->pipe_tx_kcap,
+ OKL4_PIPE_CONTROL_OP_RESET);
+
+ if (pipe->rx_buf)
+ kfree(pipe->rx_buf);
+ pipe->rx_buf = NULL;
+ pipe->rx_buf_count = 0;
+ }
+
+ return 0;
+}
+
+struct file_operations okl4_pipe_fops = {
+ .owner = THIS_MODULE,
+ .read = okl4_pipe_read,
+ .write = okl4_pipe_write,
+ .open = okl4_pipe_open,
+ .release = okl4_pipe_close,
+ .poll = okl4_pipe_poll,
+};
+
+static int __devinit
+okl4_pipe_probe(struct platform_device *pdev)
+{
+ struct okl4_pipe *pipe;
+ int err, pipe_id;
+ struct okl4_pipe_mv *priv;
+ dev_t dev_num;
+ struct device *device = NULL;
+ u32 reg[2];
+ struct resource *irq;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct okl4_pipe_mv),
+ GFP_KERNEL);
+ if (priv == NULL) {
+ err = -ENOMEM;
+ goto fail_alloc_priv;
+ }
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ pipe_id = of_alias_get_id(pdev->dev.of_node, "pipe");
+ if (pipe_id < 0) {
+ err = -ENXIO;
+ goto fail_pipe_id;
+ }
+
+ if (pipe_id < 0 || pipe_id >= MAX_PIPES) {
+ err = -ENXIO;
+ goto fail_pipe_id;
+ }
+
+ if (of_property_read_u32_array(pdev->dev.of_node, "reg", reg, 2)) {
+ dev_err(&pdev->dev, "need 2 reg resources\n");
+ err = -ENODEV;
+ goto fail_pipe_id;
+ }
+
+ /* Populate the private structure */
+ priv->pipe_id = pipe_id;
+
+ pipe = &pipes[pipe_id];
+
+ /* Set up and register the pipe device */
+ pipe->pdev = pdev;
+ dev_set_name(&pdev->dev, "%s%d", DEVICE_NAME, (int)pipe_id);
+
+ pipe->ref_count = 0;
+ pipe->pipe_tx_kcap = reg[0];
+ pipe->pipe_rx_kcap = reg[1];
+ pipe->max_msg_size = 64;
+
+ irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!irq) {
+ dev_err(&pdev->dev, "no tx irq resource?\n");
+ err = -ENODEV;
+ goto fail_irq_resource;
+ }
+ pipe->tx_irq = irq->start;
+ irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
+ if (!irq) {
+ dev_err(&pdev->dev, "no rx irq resource?\n");
+ err = -ENODEV;
+ goto fail_irq_resource;
+ }
+ pipe->rx_irq = irq->start;
+
+ pipe->write_buf = kmalloc(sizeof(pipe->write_buf), GFP_KERNEL);
+ if (!pipe->write_buf) {
+ dev_err(&pdev->dev, "cannot allocate write buffer\n");
+ err = -ENOMEM;
+ goto fail_malloc_write;
+ }
+
+ init_waitqueue_head(&pipe->rx_wait_q);
+ init_waitqueue_head(&pipe->tx_wait_q);
+ init_waitqueue_head(&pipe->poll_wait_q);
+
+ err = devm_request_irq(&pdev->dev, pipe->rx_irq,
+ okl4_pipe_rx_irq, 0, dev_name(&pdev->dev),
+ pipe);
+ if (err) {
+ dev_err(&pdev->dev, "cannot register rx irq %d: %d\n",
+ (int)pipe->rx_irq, (int)err);
+ goto fail_request_rx_irq;
+ }
+
+ err = devm_request_irq(&pdev->dev, pipe->tx_irq,
+ okl4_pipe_tx_irq, 0, dev_name(&pdev->dev),
+ pipe);
+ if (err) {
+ dev_err(&pdev->dev, "cannot register tx irq %d: %d\n",
+ (int)pipe->tx_irq, (int)err);
+ goto fail_request_tx_irq;
+ }
+
+ dev_num = MKDEV(okl4_pipe_major, pipe_id);
+
+ cdev_init(&pipe->cdev, &okl4_pipe_fops);
+ pipe->cdev.owner = THIS_MODULE;
+ err = cdev_add(&pipe->cdev, dev_num, 1);
+ if (err) {
+ dev_err(&pdev->dev, "cannot add device: %d\n", (int)err);
+ goto fail_cdev_add;
+ }
+
+ device = device_create(okl4_pipe_class, NULL, dev_num, NULL,
+ DEVICE_NAME "%d", pipe_id);
+ if (IS_ERR(device)) {
+ err = PTR_ERR(device);
+ dev_err(&pdev->dev, "cannot create device: %d\n", (int)err);
+ goto fail_device_create;
+ }
+
+ return 0;
+
+fail_device_create:
+ cdev_del(&pipe->cdev);
+fail_cdev_add:
+ devm_free_irq(&pdev->dev, pipe->tx_irq, pipe);
+fail_request_tx_irq:
+ devm_free_irq(&pdev->dev, pipe->rx_irq, pipe);
+fail_request_rx_irq:
+ kfree(pipe->write_buf);
+fail_malloc_write:
+fail_irq_resource:
+fail_pipe_id:
+ dev_set_drvdata(&pdev->dev, NULL);
+ devm_kfree(&pdev->dev, priv);
+fail_alloc_priv:
+ return err;
+}
+
+static int __devexit
+okl4_pipe_remove(struct platform_device *pdev)
+{
+ struct okl4_pipe *pipe;
+ struct okl4_pipe_mv *priv = dev_get_drvdata(&pdev->dev);
+
+ if (priv->pipe_id < 0 || priv->pipe_id >= MAX_PIPES)
+ return -ENXIO;
+
+ pipe = &pipes[priv->pipe_id];
+
+ cdev_del(&pipe->cdev);
+
+ devm_free_irq(&pdev->dev, pipe->tx_irq, pipe);
+ devm_free_irq(&pdev->dev, pipe->rx_irq, pipe);
+
+ kfree(pipe->write_buf);
+
+ dev_set_drvdata(&pdev->dev, NULL);
+ devm_kfree(&pdev->dev, priv);
+
+ return 0;
+}
+
+static const struct of_device_id okl4_pipe_match[] = {
+ {
+ .compatible = "okl,pipe",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, okl4_pipe_match);
+
+static struct platform_driver okl4_pipe_driver = {
+ .probe = okl4_pipe_probe,
+ .remove = __devexit_p(okl4_pipe_remove),
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = okl4_pipe_match,
+ },
+};
+
+static int __init
+okl4_pipe_init(void)
+{
+ int err;
+ dev_t dev_num = 0;
+
+ err = alloc_chrdev_region(&dev_num, 0, MAX_PIPES, DEVICE_NAME);
+ if (err < 0) {
+ printk("%s: cannot allocate device region\n", __func__);
+ goto fail_alloc_chrdev_region;
+ }
+ okl4_pipe_major = MAJOR(dev_num);
+
+ okl4_pipe_class = class_create(THIS_MODULE, DEVICE_NAME);
+ if (IS_ERR(okl4_pipe_class)) {
+ err = PTR_ERR(okl4_pipe_class);
+ goto fail_class_create;
+ }
+
+ /* Register the driver with the microvisor bus */
+ err = platform_driver_register(&okl4_pipe_driver);
+ if (err)
+ goto fail_driver_register;
+
+ return 0;
+
+fail_driver_register:
+ class_destroy(okl4_pipe_class);
+fail_class_create:
+ unregister_chrdev_region(dev_num, MAX_PIPES);
+fail_alloc_chrdev_region:
+ return err;
+}
+
+static void __exit
+okl4_pipe_exit(void)
+{
+ dev_t dev_num = MKDEV(okl4_pipe_major, 0);
+
+ platform_driver_unregister(&okl4_pipe_driver);
+ class_destroy(okl4_pipe_class);
+ unregister_chrdev_region(dev_num, MAX_PIPES);
+}
+
+module_init(okl4_pipe_init);
+module_exit(okl4_pipe_exit);
+
+MODULE_DESCRIPTION("OKL4 pipe driver");
+MODULE_AUTHOR("John Clarke <johnc@cog.systems>");
diff --git a/drivers/crypto/mxs-dcp.c b/drivers/crypto/mxs-dcp.c
index 625ee50..decaed4 100644
--- a/drivers/crypto/mxs-dcp.c
+++ b/drivers/crypto/mxs-dcp.c
@@ -63,7 +63,7 @@
struct dcp_coherent_block *coh;
struct completion completion[DCP_MAX_CHANS];
- struct mutex mutex[DCP_MAX_CHANS];
+ spinlock_t lock[DCP_MAX_CHANS];
struct task_struct *thread[DCP_MAX_CHANS];
struct crypto_queue queue[DCP_MAX_CHANS];
};
@@ -349,13 +349,20 @@
int ret;
- do {
- __set_current_state(TASK_INTERRUPTIBLE);
+ while (!kthread_should_stop()) {
+ set_current_state(TASK_INTERRUPTIBLE);
- mutex_lock(&sdcp->mutex[chan]);
+ spin_lock(&sdcp->lock[chan]);
backlog = crypto_get_backlog(&sdcp->queue[chan]);
arq = crypto_dequeue_request(&sdcp->queue[chan]);
- mutex_unlock(&sdcp->mutex[chan]);
+ spin_unlock(&sdcp->lock[chan]);
+
+ if (!backlog && !arq) {
+ schedule();
+ continue;
+ }
+
+ set_current_state(TASK_RUNNING);
if (backlog)
backlog->complete(backlog, -EINPROGRESS);
@@ -363,11 +370,8 @@
if (arq) {
ret = mxs_dcp_aes_block_crypt(arq);
arq->complete(arq, ret);
- continue;
}
-
- schedule();
- } while (!kthread_should_stop());
+ }
return 0;
}
@@ -409,9 +413,9 @@
rctx->ecb = ecb;
actx->chan = DCP_CHAN_CRYPTO;
- mutex_lock(&sdcp->mutex[actx->chan]);
+ spin_lock(&sdcp->lock[actx->chan]);
ret = crypto_enqueue_request(&sdcp->queue[actx->chan], &req->base);
- mutex_unlock(&sdcp->mutex[actx->chan]);
+ spin_unlock(&sdcp->lock[actx->chan]);
wake_up_process(sdcp->thread[actx->chan]);
@@ -640,13 +644,20 @@
struct ahash_request *req;
int ret, fini;
- do {
- __set_current_state(TASK_INTERRUPTIBLE);
+ while (!kthread_should_stop()) {
+ set_current_state(TASK_INTERRUPTIBLE);
- mutex_lock(&sdcp->mutex[chan]);
+ spin_lock(&sdcp->lock[chan]);
backlog = crypto_get_backlog(&sdcp->queue[chan]);
arq = crypto_dequeue_request(&sdcp->queue[chan]);
- mutex_unlock(&sdcp->mutex[chan]);
+ spin_unlock(&sdcp->lock[chan]);
+
+ if (!backlog && !arq) {
+ schedule();
+ continue;
+ }
+
+ set_current_state(TASK_RUNNING);
if (backlog)
backlog->complete(backlog, -EINPROGRESS);
@@ -658,12 +669,8 @@
ret = dcp_sha_req_to_buf(arq);
fini = rctx->fini;
arq->complete(arq, ret);
- if (!fini)
- continue;
}
-
- schedule();
- } while (!kthread_should_stop());
+ }
return 0;
}
@@ -721,9 +728,9 @@
rctx->init = 1;
}
- mutex_lock(&sdcp->mutex[actx->chan]);
+ spin_lock(&sdcp->lock[actx->chan]);
ret = crypto_enqueue_request(&sdcp->queue[actx->chan], &req->base);
- mutex_unlock(&sdcp->mutex[actx->chan]);
+ spin_unlock(&sdcp->lock[actx->chan]);
wake_up_process(sdcp->thread[actx->chan]);
mutex_unlock(&actx->mutex);
@@ -979,7 +986,7 @@
platform_set_drvdata(pdev, sdcp);
for (i = 0; i < DCP_MAX_CHANS; i++) {
- mutex_init(&sdcp->mutex[i]);
+ spin_lock_init(&sdcp->lock[i]);
init_completion(&sdcp->completion[i]);
crypto_init_queue(&sdcp->queue[i], 50);
}
diff --git a/drivers/crypto/qat/qat_c3xxx/adf_drv.c b/drivers/crypto/qat/qat_c3xxx/adf_drv.c
index 640c3fc..ad9d6fb 100644
--- a/drivers/crypto/qat/qat_c3xxx/adf_drv.c
+++ b/drivers/crypto/qat/qat_c3xxx/adf_drv.c
@@ -123,7 +123,8 @@
struct adf_hw_device_data *hw_data;
char name[ADF_DEVICE_NAME_LENGTH];
unsigned int i, bar_nr;
- int ret, bar_mask;
+ unsigned long bar_mask;
+ int ret;
switch (ent->device) {
case ADF_C3XXX_PCI_DEVICE_ID:
@@ -235,8 +236,7 @@
/* Find and map all the device's BARS */
i = 0;
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
- for_each_set_bit(bar_nr, (const unsigned long *)&bar_mask,
- ADF_PCI_MAX_BARS * 2) {
+ for_each_set_bit(bar_nr, &bar_mask, ADF_PCI_MAX_BARS * 2) {
struct adf_bar *bar = &accel_pci_dev->pci_bars[i++];
bar->base_addr = pci_resource_start(pdev, bar_nr);
diff --git a/drivers/crypto/qat/qat_c3xxxvf/adf_drv.c b/drivers/crypto/qat/qat_c3xxxvf/adf_drv.c
index 949d77b..0dd8d2d 100644
--- a/drivers/crypto/qat/qat_c3xxxvf/adf_drv.c
+++ b/drivers/crypto/qat/qat_c3xxxvf/adf_drv.c
@@ -125,7 +125,8 @@
struct adf_hw_device_data *hw_data;
char name[ADF_DEVICE_NAME_LENGTH];
unsigned int i, bar_nr;
- int ret, bar_mask;
+ unsigned long bar_mask;
+ int ret;
switch (ent->device) {
case ADF_C3XXXIOV_PCI_DEVICE_ID:
@@ -215,8 +216,7 @@
/* Find and map all the device's BARS */
i = 0;
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
- for_each_set_bit(bar_nr, (const unsigned long *)&bar_mask,
- ADF_PCI_MAX_BARS * 2) {
+ for_each_set_bit(bar_nr, &bar_mask, ADF_PCI_MAX_BARS * 2) {
struct adf_bar *bar = &accel_pci_dev->pci_bars[i++];
bar->base_addr = pci_resource_start(pdev, bar_nr);
diff --git a/drivers/crypto/qat/qat_c62x/adf_drv.c b/drivers/crypto/qat/qat_c62x/adf_drv.c
index 5b2d78a..dcdb94c 100644
--- a/drivers/crypto/qat/qat_c62x/adf_drv.c
+++ b/drivers/crypto/qat/qat_c62x/adf_drv.c
@@ -123,7 +123,8 @@
struct adf_hw_device_data *hw_data;
char name[ADF_DEVICE_NAME_LENGTH];
unsigned int i, bar_nr;
- int ret, bar_mask;
+ unsigned long bar_mask;
+ int ret;
switch (ent->device) {
case ADF_C62X_PCI_DEVICE_ID:
@@ -235,8 +236,7 @@
/* Find and map all the device's BARS */
i = (hw_data->fuses & ADF_DEVICE_FUSECTL_MASK) ? 1 : 0;
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
- for_each_set_bit(bar_nr, (const unsigned long *)&bar_mask,
- ADF_PCI_MAX_BARS * 2) {
+ for_each_set_bit(bar_nr, &bar_mask, ADF_PCI_MAX_BARS * 2) {
struct adf_bar *bar = &accel_pci_dev->pci_bars[i++];
bar->base_addr = pci_resource_start(pdev, bar_nr);
diff --git a/drivers/crypto/qat/qat_c62xvf/adf_drv.c b/drivers/crypto/qat/qat_c62xvf/adf_drv.c
index 7540ce1..cd9e634 100644
--- a/drivers/crypto/qat/qat_c62xvf/adf_drv.c
+++ b/drivers/crypto/qat/qat_c62xvf/adf_drv.c
@@ -125,7 +125,8 @@
struct adf_hw_device_data *hw_data;
char name[ADF_DEVICE_NAME_LENGTH];
unsigned int i, bar_nr;
- int ret, bar_mask;
+ unsigned long bar_mask;
+ int ret;
switch (ent->device) {
case ADF_C62XIOV_PCI_DEVICE_ID:
@@ -215,8 +216,7 @@
/* Find and map all the device's BARS */
i = 0;
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
- for_each_set_bit(bar_nr, (const unsigned long *)&bar_mask,
- ADF_PCI_MAX_BARS * 2) {
+ for_each_set_bit(bar_nr, &bar_mask, ADF_PCI_MAX_BARS * 2) {
struct adf_bar *bar = &accel_pci_dev->pci_bars[i++];
bar->base_addr = pci_resource_start(pdev, bar_nr);
diff --git a/drivers/crypto/qat/qat_dh895xcc/adf_drv.c b/drivers/crypto/qat/qat_dh895xcc/adf_drv.c
index 4d2de28..3417443 100644
--- a/drivers/crypto/qat/qat_dh895xcc/adf_drv.c
+++ b/drivers/crypto/qat/qat_dh895xcc/adf_drv.c
@@ -123,7 +123,8 @@
struct adf_hw_device_data *hw_data;
char name[ADF_DEVICE_NAME_LENGTH];
unsigned int i, bar_nr;
- int ret, bar_mask;
+ unsigned long bar_mask;
+ int ret;
switch (ent->device) {
case ADF_DH895XCC_PCI_DEVICE_ID:
@@ -237,8 +238,7 @@
/* Find and map all the device's BARS */
i = 0;
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
- for_each_set_bit(bar_nr, (const unsigned long *)&bar_mask,
- ADF_PCI_MAX_BARS * 2) {
+ for_each_set_bit(bar_nr, &bar_mask, ADF_PCI_MAX_BARS * 2) {
struct adf_bar *bar = &accel_pci_dev->pci_bars[i++];
bar->base_addr = pci_resource_start(pdev, bar_nr);
diff --git a/drivers/crypto/qat/qat_dh895xccvf/adf_drv.c b/drivers/crypto/qat/qat_dh895xccvf/adf_drv.c
index 60df986..15de9cb 100644
--- a/drivers/crypto/qat/qat_dh895xccvf/adf_drv.c
+++ b/drivers/crypto/qat/qat_dh895xccvf/adf_drv.c
@@ -125,7 +125,8 @@
struct adf_hw_device_data *hw_data;
char name[ADF_DEVICE_NAME_LENGTH];
unsigned int i, bar_nr;
- int ret, bar_mask;
+ unsigned long bar_mask;
+ int ret;
switch (ent->device) {
case ADF_DH895XCCIOV_PCI_DEVICE_ID:
@@ -215,8 +216,7 @@
/* Find and map all the device's BARS */
i = 0;
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM);
- for_each_set_bit(bar_nr, (const unsigned long *)&bar_mask,
- ADF_PCI_MAX_BARS * 2) {
+ for_each_set_bit(bar_nr, &bar_mask, ADF_PCI_MAX_BARS * 2) {
struct adf_bar *bar = &accel_pci_dev->pci_bars[i++];
bar->base_addr = pci_resource_start(pdev, bar_nr);
diff --git a/drivers/devfreq/governor_gpubw_mon.c b/drivers/devfreq/governor_gpubw_mon.c
index 9c24eef..f7bb7eb 100644
--- a/drivers/devfreq/governor_gpubw_mon.c
+++ b/drivers/devfreq/governor_gpubw_mon.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -224,10 +224,11 @@
case DEVFREQ_GOV_SUSPEND:
{
struct devfreq_msm_adreno_tz_data *priv = devfreq->data;
-
- priv->bus.total_time = 0;
- priv->bus.gpu_time = 0;
- priv->bus.ram_time = 0;
+ if (priv) {
+ priv->bus.total_time = 0;
+ priv->bus.gpu_time = 0;
+ priv->bus.ram_time = 0;
+ }
}
break;
default:
diff --git a/drivers/edac/edac_mc_sysfs.c b/drivers/edac/edac_mc_sysfs.c
index bea71fb..7335a86 100644
--- a/drivers/edac/edac_mc_sysfs.c
+++ b/drivers/edac/edac_mc_sysfs.c
@@ -1059,14 +1059,14 @@
err = device_add(mci_pdev);
if (err < 0)
- goto out_dev_free;
+ goto out_put_device;
edac_dbg(0, "device %s created\n", dev_name(mci_pdev));
return 0;
- out_dev_free:
- kfree(mci_pdev);
+ out_put_device:
+ put_device(mci_pdev);
out:
return err;
}
diff --git a/drivers/edac/i7core_edac.c b/drivers/edac/i7core_edac.c
index 8a68a5e..b609320 100644
--- a/drivers/edac/i7core_edac.c
+++ b/drivers/edac/i7core_edac.c
@@ -1177,15 +1177,14 @@
rc = device_add(pvt->addrmatch_dev);
if (rc < 0)
- return rc;
+ goto err_put_addrmatch;
if (!pvt->is_registered) {
pvt->chancounts_dev = kzalloc(sizeof(*pvt->chancounts_dev),
GFP_KERNEL);
if (!pvt->chancounts_dev) {
- put_device(pvt->addrmatch_dev);
- device_del(pvt->addrmatch_dev);
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto err_del_addrmatch;
}
pvt->chancounts_dev->type = &all_channel_counts_type;
@@ -1199,9 +1198,18 @@
rc = device_add(pvt->chancounts_dev);
if (rc < 0)
- return rc;
+ goto err_put_chancounts;
}
return 0;
+
+err_put_chancounts:
+ put_device(pvt->chancounts_dev);
+err_del_addrmatch:
+ device_del(pvt->addrmatch_dev);
+err_put_addrmatch:
+ put_device(pvt->addrmatch_dev);
+
+ return rc;
}
static void i7core_delete_sysfs_devices(struct mem_ctl_info *mci)
@@ -1211,11 +1219,11 @@
edac_dbg(1, "\n");
if (!pvt->is_registered) {
- put_device(pvt->chancounts_dev);
device_del(pvt->chancounts_dev);
+ put_device(pvt->chancounts_dev);
}
- put_device(pvt->addrmatch_dev);
device_del(pvt->addrmatch_dev);
+ put_device(pvt->addrmatch_dev);
}
/****************************************************************************
diff --git a/drivers/gpio/gpio-adp5588.c b/drivers/gpio/gpio-adp5588.c
index c0f718b..c85407a 100644
--- a/drivers/gpio/gpio-adp5588.c
+++ b/drivers/gpio/gpio-adp5588.c
@@ -41,6 +41,8 @@
uint8_t int_en[3];
uint8_t irq_mask[3];
uint8_t irq_stat[3];
+ uint8_t int_input_en[3];
+ uint8_t int_lvl_cached[3];
};
static int adp5588_gpio_read(struct i2c_client *client, u8 reg)
@@ -173,12 +175,28 @@
struct adp5588_gpio *dev = irq_data_get_irq_chip_data(d);
int i;
- for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++)
+ for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) {
+ if (dev->int_input_en[i]) {
+ mutex_lock(&dev->lock);
+ dev->dir[i] &= ~dev->int_input_en[i];
+ dev->int_input_en[i] = 0;
+ adp5588_gpio_write(dev->client, GPIO_DIR1 + i,
+ dev->dir[i]);
+ mutex_unlock(&dev->lock);
+ }
+
+ if (dev->int_lvl_cached[i] != dev->int_lvl[i]) {
+ dev->int_lvl_cached[i] = dev->int_lvl[i];
+ adp5588_gpio_write(dev->client, GPIO_INT_LVL1 + i,
+ dev->int_lvl[i]);
+ }
+
if (dev->int_en[i] ^ dev->irq_mask[i]) {
dev->int_en[i] = dev->irq_mask[i];
adp5588_gpio_write(dev->client, GPIO_INT_EN1 + i,
dev->int_en[i]);
}
+ }
mutex_unlock(&dev->irq_lock);
}
@@ -221,9 +239,7 @@
else
return -EINVAL;
- adp5588_gpio_direction_input(&dev->gpio_chip, gpio);
- adp5588_gpio_write(dev->client, GPIO_INT_LVL1 + bank,
- dev->int_lvl[bank]);
+ dev->int_input_en[bank] |= bit;
return 0;
}
diff --git a/drivers/gpio/gpio-menz127.c b/drivers/gpio/gpio-menz127.c
index a1210e3..95061d2 100644
--- a/drivers/gpio/gpio-menz127.c
+++ b/drivers/gpio/gpio-menz127.c
@@ -56,9 +56,9 @@
rnd = fls(debounce) - 1;
if (rnd && (debounce & BIT(rnd - 1)))
- debounce = round_up(debounce, MEN_Z127_DB_MIN_US);
+ debounce = roundup(debounce, MEN_Z127_DB_MIN_US);
else
- debounce = round_down(debounce, MEN_Z127_DB_MIN_US);
+ debounce = rounddown(debounce, MEN_Z127_DB_MIN_US);
if (debounce > MEN_Z127_DB_MAX_US)
debounce = MEN_Z127_DB_MAX_US;
diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index 193f15d..aac8432 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -31,6 +31,7 @@
struct of_phandle_args *gpiospec = data;
return chip->gpiodev->dev.of_node == gpiospec->np &&
+ chip->of_xlate &&
chip->of_xlate(chip, gpiospec, NULL) >= 0;
}
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index dd00764..2ec402a 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -471,7 +471,7 @@
if (ret)
goto out_free_descs;
lh->descs[i] = desc;
- count = i;
+ count = i + 1;
if (lflags & GPIOHANDLE_REQUEST_ACTIVE_LOW)
set_bit(FLAG_ACTIVE_LOW, &desc->flags);
diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c
index 564362e..c8a5cf5 100644
--- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c
@@ -5551,6 +5551,11 @@
if (!(adev->pg_flags & AMD_PG_SUPPORT_GFX_PG))
return 0;
+ if (adev->pg_flags & (AMD_PG_SUPPORT_GFX_SMG |
+ AMD_PG_SUPPORT_RLC_SMU_HS |
+ AMD_PG_SUPPORT_CP |
+ AMD_PG_SUPPORT_GFX_DMG))
+ adev->gfx.rlc.funcs->enter_safe_mode(adev);
switch (adev->asic_type) {
case CHIP_CARRIZO:
case CHIP_STONEY:
@@ -5586,7 +5591,11 @@
default:
break;
}
-
+ if (adev->pg_flags & (AMD_PG_SUPPORT_GFX_SMG |
+ AMD_PG_SUPPORT_RLC_SMU_HS |
+ AMD_PG_SUPPORT_CP |
+ AMD_PG_SUPPORT_GFX_DMG))
+ adev->gfx.rlc.funcs->exit_safe_mode(adev);
return 0;
}
diff --git a/drivers/gpu/drm/amd/amdgpu/kv_dpm.c b/drivers/gpu/drm/amd/amdgpu/kv_dpm.c
index 71d2856..f61c489 100644
--- a/drivers/gpu/drm/amd/amdgpu/kv_dpm.c
+++ b/drivers/gpu/drm/amd/amdgpu/kv_dpm.c
@@ -1350,8 +1350,6 @@
return ret;
}
- kv_update_current_ps(adev, adev->pm.dpm.boot_ps);
-
if (adev->irq.installed &&
amdgpu_is_internal_thermal_sensor(adev->pm.int_thermal_type)) {
ret = kv_set_thermal_temperature_range(adev, KV_TEMP_RANGE_MIN, KV_TEMP_RANGE_MAX);
@@ -3086,7 +3084,7 @@
else
adev->pm.dpm_enabled = true;
mutex_unlock(&adev->pm.mutex);
-
+ amdgpu_pm_compute_clocks(adev);
return ret;
}
diff --git a/drivers/gpu/drm/amd/amdgpu/si_dpm.c b/drivers/gpu/drm/amd/amdgpu/si_dpm.c
index 3fa8320..4826bef 100644
--- a/drivers/gpu/drm/amd/amdgpu/si_dpm.c
+++ b/drivers/gpu/drm/amd/amdgpu/si_dpm.c
@@ -6959,7 +6959,6 @@
si_enable_auto_throttle_source(adev, AMDGPU_DPM_AUTO_THROTTLE_SRC_THERMAL, true);
si_thermal_start_thermal_controller(adev);
- ni_update_current_ps(adev, boot_ps);
return 0;
}
@@ -7836,7 +7835,7 @@
else
adev->pm.dpm_enabled = true;
mutex_unlock(&adev->pm.mutex);
-
+ amdgpu_pm_compute_clocks(adev);
return ret;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index 56c288f..5bfae1f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -271,12 +271,16 @@
nv_connector->edid = NULL;
}
- /* Outputs are only polled while runtime active, so acquiring a
- * runtime PM ref here is unnecessary (and would deadlock upon
- * runtime suspend because it waits for polling to finish).
+ /* Outputs are only polled while runtime active, so resuming the
+ * device here is unnecessary (and would deadlock upon runtime suspend
+ * because it waits for polling to finish). We do however, want to
+ * prevent the autosuspend timer from elapsing during this operation
+ * if possible.
*/
- if (!drm_kms_helper_is_poll_worker()) {
- ret = pm_runtime_get_sync(connector->dev->dev);
+ if (drm_kms_helper_is_poll_worker()) {
+ pm_runtime_get_noresume(dev->dev);
+ } else {
+ ret = pm_runtime_get_sync(dev->dev);
if (ret < 0 && ret != -EACCES)
return conn_status;
}
@@ -354,10 +358,8 @@
out:
- if (!drm_kms_helper_is_poll_worker()) {
- pm_runtime_mark_last_busy(connector->dev->dev);
- pm_runtime_put_autosuspend(connector->dev->dev);
- }
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
return conn_status;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index 6526a33..3ddd409 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -367,8 +367,6 @@
pm_runtime_get_sync(drm->dev->dev);
drm_helper_hpd_irq_event(drm->dev);
- /* enable polling for external displays */
- drm_kms_helper_poll_enable(drm->dev);
pm_runtime_mark_last_busy(drm->dev->dev);
pm_runtime_put_sync(drm->dev->dev);
@@ -391,15 +389,29 @@
{
struct nouveau_drm *drm = container_of(nb, typeof(*drm), acpi_nb);
struct acpi_bus_event *info = data;
+ int ret;
if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) {
if (info->type == ACPI_VIDEO_NOTIFY_PROBE) {
- /*
- * This may be the only indication we receive of a
- * connector hotplug on a runtime suspended GPU,
- * schedule hpd_work to check.
- */
- schedule_work(&drm->hpd_work);
+ ret = pm_runtime_get(drm->dev->dev);
+ if (ret == 1 || ret == -EACCES) {
+ /* If the GPU is already awake, or in a state
+ * where we can't wake it up, it can handle
+ * it's own hotplug events.
+ */
+ pm_runtime_put_autosuspend(drm->dev->dev);
+ } else if (ret == 0) {
+ /* This may be the only indication we receive
+ * of a connector hotplug on a runtime
+ * suspended GPU, schedule hpd_work to check.
+ */
+ NV_DEBUG(drm, "ACPI requested connector reprobe\n");
+ schedule_work(&drm->hpd_work);
+ pm_runtime_put_noidle(drm->dev->dev);
+ } else {
+ NV_WARN(drm, "Dropped ACPI reprobe event due to RPM error: %d\n",
+ ret);
+ }
/* acpi-video should not generate keypresses for this */
return NOTIFY_BAD;
@@ -422,6 +434,11 @@
if (ret)
return ret;
+ /* enable connector detection and polling for connectors without HPD
+ * support
+ */
+ drm_kms_helper_poll_enable(dev);
+
/* enable hotplug interrupts */
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
struct nouveau_connector *conn = nouveau_connector(connector);
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm200.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm200.c
index a410c0d..6a1b81e 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm200.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm200.c
@@ -161,7 +161,8 @@
}
/* load and execute some other ucode image (bios therm?) */
- return pmu_load(init, 0x01, post, NULL, NULL);
+ pmu_load(init, 0x01, post, NULL, NULL);
+ return 0;
}
static const struct nvkm_devinit_func
diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
index aad2f4a..97828fa 100644
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
@@ -283,7 +283,6 @@
remote = of_graph_get_remote_port_parent(ep);
if (!remote) {
DRM_DEBUG_DRIVER("Error retrieving the output node\n");
- of_node_put(remote);
continue;
}
@@ -297,11 +296,13 @@
if (of_graph_parse_endpoint(ep, &endpoint)) {
DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
+ of_node_put(remote);
continue;
}
if (!endpoint.id) {
DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
+ of_node_put(remote);
continue;
}
}
diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c
index f8c9f6f..a2d8630 100644
--- a/drivers/gpu/drm/vc4/vc4_plane.c
+++ b/drivers/gpu/drm/vc4/vc4_plane.c
@@ -327,6 +327,9 @@
vc4_state->y_scaling[0] = vc4_get_scaling_mode(vc4_state->src_h[0],
vc4_state->crtc_h);
+ vc4_state->is_unity = (vc4_state->x_scaling[0] == VC4_SCALING_NONE &&
+ vc4_state->y_scaling[0] == VC4_SCALING_NONE);
+
if (num_planes > 1) {
vc4_state->is_yuv = true;
@@ -342,24 +345,17 @@
vc4_get_scaling_mode(vc4_state->src_h[1],
vc4_state->crtc_h);
- /* YUV conversion requires that scaling be enabled,
- * even on a plane that's otherwise 1:1. Choose TPZ
- * for simplicity.
+ /* YUV conversion requires that horizontal scaling be enabled,
+ * even on a plane that's otherwise 1:1. Looks like only PPF
+ * works in that case, so let's pick that one.
*/
- if (vc4_state->x_scaling[0] == VC4_SCALING_NONE)
- vc4_state->x_scaling[0] = VC4_SCALING_TPZ;
- if (vc4_state->y_scaling[0] == VC4_SCALING_NONE)
- vc4_state->y_scaling[0] = VC4_SCALING_TPZ;
+ if (vc4_state->is_unity)
+ vc4_state->x_scaling[0] = VC4_SCALING_PPF;
} else {
vc4_state->x_scaling[1] = VC4_SCALING_NONE;
vc4_state->y_scaling[1] = VC4_SCALING_NONE;
}
- vc4_state->is_unity = (vc4_state->x_scaling[0] == VC4_SCALING_NONE &&
- vc4_state->y_scaling[0] == VC4_SCALING_NONE &&
- vc4_state->x_scaling[1] == VC4_SCALING_NONE &&
- vc4_state->y_scaling[1] == VC4_SCALING_NONE);
-
/* No configuring scaling on the cursor plane, since it gets
non-vblank-synced updates, and scaling requires requires
LBM changes which have to be vblank-synced.
@@ -614,7 +610,10 @@
vc4_dlist_write(vc4_state, SCALER_CSC2_ITR_R_601_5);
}
- if (!vc4_state->is_unity) {
+ if (vc4_state->x_scaling[0] != VC4_SCALING_NONE ||
+ vc4_state->x_scaling[1] != VC4_SCALING_NONE ||
+ vc4_state->y_scaling[0] != VC4_SCALING_NONE ||
+ vc4_state->y_scaling[1] != VC4_SCALING_NONE) {
/* LBM Base Address. */
if (vc4_state->y_scaling[0] != VC4_SCALING_NONE ||
vc4_state->y_scaling[1] != VC4_SCALING_NONE) {
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index cb2e85c..a8b8058 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -333,7 +333,8 @@
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
- if (usage->hid == (HID_UP_CUSTOM | 0x0003)) {
+ if (usage->hid == (HID_UP_CUSTOM | 0x0003) ||
+ usage->hid == (HID_UP_MSVENDOR | 0x0003)) {
/* The fn key on Apple USB keyboards */
set_bit(EV_REP, hi->input->evbit);
hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_FN);
@@ -479,6 +480,12 @@
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI),
.driver_data = APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_ANSI),
+ .driver_data = APPLE_HAS_FN },
+ { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_ANSI),
+ .driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
.driver_data = APPLE_HAS_FN },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 2059d9d..48856a0 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2064,6 +2064,10 @@
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 0280e28..a784464 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -83,6 +83,7 @@
#define USB_DEVICE_ID_ANTON_TOUCH_PAD 0x3101
#define USB_VENDOR_ID_APPLE 0x05ac
+#define BT_VENDOR_ID_APPLE 0x004c
#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE 0x0304
#define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
@@ -152,6 +153,7 @@
#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO 0x0256
#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS 0x0257
#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI 0x0267
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_ANSI 0x026c
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI 0x0290
#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO 0x0291
#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS 0x0292
@@ -888,6 +890,7 @@
#define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17
#define USB_DEVICE_ID_SAITEK_PS1000 0x0621
#define USB_DEVICE_ID_SAITEK_RAT7_OLD 0x0ccb
+#define USB_DEVICE_ID_SAITEK_RAT7_CONTAGION 0x0ccd
#define USB_DEVICE_ID_SAITEK_RAT7 0x0cd7
#define USB_DEVICE_ID_SAITEK_RAT9 0x0cfa
#define USB_DEVICE_ID_SAITEK_MMO7 0x0cd0
@@ -927,6 +930,8 @@
#define USB_DEVICE_ID_SONY_PS3_BDREMOTE 0x0306
#define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268
#define USB_DEVICE_ID_SONY_PS4_CONTROLLER 0x05c4
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 0x09cc
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE 0x0ba0
#define USB_DEVICE_ID_SONY_MOTION_CONTROLLER 0x03d5
#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f
#define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER 0x0002
diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c
index 1b0084d..28373da 100644
--- a/drivers/hid/hid-ntrig.c
+++ b/drivers/hid/hid-ntrig.c
@@ -955,6 +955,8 @@
ret = sysfs_create_group(&hdev->dev.kobj,
&ntrig_attribute_group);
+ if (ret)
+ hid_err(hdev, "cannot create sysfs group\n");
return 0;
err_free:
diff --git a/drivers/hid/hid-saitek.c b/drivers/hid/hid-saitek.c
index 39e6426..683861f 100644
--- a/drivers/hid/hid-saitek.c
+++ b/drivers/hid/hid-saitek.c
@@ -183,6 +183,8 @@
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_CONTAGION),
+ .driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT9),
.driver_data = SAITEK_RELEASE_MODE_RAT7 },
{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9),
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 1b1dccd..eee58d1 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -2581,6 +2581,12 @@
.driver_data = DUALSHOCK4_CONTROLLER_USB },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
.driver_data = DUALSHOCK4_CONTROLLER_BT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
+ .driver_data = DUALSHOCK4_CONTROLLER_USB },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
+ .driver_data = DUALSHOCK4_CONTROLLER_BT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE),
+ .driver_data = DUALSHOCK4_CONTROLLER_USB },
/* Nyko Core Controller for PS3 */
{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER),
.driver_data = SIXAXIS_CONTROLLER_USB | SINO_LITE_CONTROLLER },
diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c
index 3cefd1a..9c262d9 100644
--- a/drivers/hwmon/adt7475.c
+++ b/drivers/hwmon/adt7475.c
@@ -274,14 +274,18 @@
return clamp_val(reg, 0, 1023) & (0xff << 2);
}
-static u16 adt7475_read_word(struct i2c_client *client, int reg)
+static int adt7475_read_word(struct i2c_client *client, int reg)
{
- u16 val;
+ int val1, val2;
- val = i2c_smbus_read_byte_data(client, reg);
- val |= (i2c_smbus_read_byte_data(client, reg + 1) << 8);
+ val1 = i2c_smbus_read_byte_data(client, reg);
+ if (val1 < 0)
+ return val1;
+ val2 = i2c_smbus_read_byte_data(client, reg + 1);
+ if (val2 < 0)
+ return val2;
- return val;
+ return val1 | (val2 << 8);
}
static void adt7475_write_word(struct i2c_client *client, int reg, u16 val)
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c
index ac63e56..9ac6e16 100644
--- a/drivers/hwmon/ina2xx.c
+++ b/drivers/hwmon/ina2xx.c
@@ -17,7 +17,7 @@
* Bi-directional Current/Power Monitor with I2C Interface
* Datasheet: http://www.ti.com/product/ina230
*
- * Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
+ * Copyright (C) 2012 Lothar Felten <lothar.felten@gmail.com>
* Thanks to Jan Volkering
*
* This program is free software; you can redistribute it and/or modify
@@ -328,6 +328,15 @@
return 0;
}
+static ssize_t ina2xx_show_shunt(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ struct ina2xx_data *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%li\n", data->rshunt);
+}
+
static ssize_t ina2xx_store_shunt(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
@@ -402,7 +411,7 @@
/* shunt resistance */
static SENSOR_DEVICE_ATTR(shunt_resistor, S_IRUGO | S_IWUSR,
- ina2xx_show_value, ina2xx_store_shunt,
+ ina2xx_show_shunt, ina2xx_store_shunt,
INA2XX_CALIBRATION);
/* update interval (ina226 only) */
diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c
index 663017f..26f1691 100644
--- a/drivers/i2c/busses/i2c-i801.c
+++ b/drivers/i2c/busses/i2c-i801.c
@@ -1408,6 +1408,13 @@
}
#ifdef CONFIG_ACPI
+static bool i801_acpi_is_smbus_ioport(const struct i801_priv *priv,
+ acpi_physical_address address)
+{
+ return address >= priv->smba &&
+ address <= pci_resource_end(priv->pci_dev, SMBBAR);
+}
+
static acpi_status
i801_acpi_io_handler(u32 function, acpi_physical_address address, u32 bits,
u64 *value, void *handler_context, void *region_context)
@@ -1423,7 +1430,7 @@
*/
mutex_lock(&priv->acpi_lock);
- if (!priv->acpi_reserved) {
+ if (!priv->acpi_reserved && i801_acpi_is_smbus_ioport(priv, address)) {
priv->acpi_reserved = true;
dev_warn(&pdev->dev, "BIOS is accessing SMBus registers\n");
diff --git a/drivers/i2c/busses/i2c-uniphier-f.c b/drivers/i2c/busses/i2c-uniphier-f.c
index db9105e..0da4991 100644
--- a/drivers/i2c/busses/i2c-uniphier-f.c
+++ b/drivers/i2c/busses/i2c-uniphier-f.c
@@ -400,11 +400,8 @@
return ret;
for (msg = msgs; msg < emsg; msg++) {
- /* If next message is read, skip the stop condition */
- bool stop = !(msg + 1 < emsg && msg[1].flags & I2C_M_RD);
- /* but, force it if I2C_M_STOP is set */
- if (msg->flags & I2C_M_STOP)
- stop = true;
+ /* Emit STOP if it is the last message or I2C_M_STOP is set. */
+ bool stop = (msg + 1 == emsg) || (msg->flags & I2C_M_STOP);
ret = uniphier_fi2c_master_xfer_one(adap, msg, stop);
if (ret)
diff --git a/drivers/i2c/busses/i2c-uniphier.c b/drivers/i2c/busses/i2c-uniphier.c
index 56e92af..fdfcee92 100644
--- a/drivers/i2c/busses/i2c-uniphier.c
+++ b/drivers/i2c/busses/i2c-uniphier.c
@@ -247,11 +247,8 @@
return ret;
for (msg = msgs; msg < emsg; msg++) {
- /* If next message is read, skip the stop condition */
- bool stop = !(msg + 1 < emsg && msg[1].flags & I2C_M_RD);
- /* but, force it if I2C_M_STOP is set */
- if (msg->flags & I2C_M_STOP)
- stop = true;
+ /* Emit STOP if it is the last message or I2C_M_STOP is set. */
+ bool stop = (msg + 1 == emsg) || (msg->flags & I2C_M_STOP);
ret = uniphier_i2c_master_xfer_one(adap, msg, stop);
if (ret)
diff --git a/drivers/infiniband/core/rw.c b/drivers/infiniband/core/rw.c
index dbfd854..1d90a12 100644
--- a/drivers/infiniband/core/rw.c
+++ b/drivers/infiniband/core/rw.c
@@ -87,7 +87,7 @@
}
ret = ib_map_mr_sg(reg->mr, sg, nents, &offset, PAGE_SIZE);
- if (ret < nents) {
+ if (ret < 0 || ret < nents) {
ib_mr_pool_put(qp, &qp->rdma_mrs, reg->mr);
return -EINVAL;
}
diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c
index 3bef6d4..fa9ef8e 100644
--- a/drivers/infiniband/core/ucma.c
+++ b/drivers/infiniband/core/ucma.c
@@ -124,6 +124,8 @@
static DEFINE_IDR(ctx_idr);
static DEFINE_IDR(multicast_idr);
+static const struct file_operations ucma_fops;
+
static inline struct ucma_context *_ucma_find_context(int id,
struct ucma_file *file)
{
@@ -1545,6 +1547,10 @@
f = fdget(cmd.fd);
if (!f.file)
return -ENOENT;
+ if (f.file->f_op != &ucma_fops) {
+ ret = -EINVAL;
+ goto file_put;
+ }
/* Validate current fd and prevent destruction of id. */
ctx = ucma_get_ctx(f.file->private_data, cmd.id);
@@ -1714,6 +1720,8 @@
mutex_lock(&mut);
if (!ctx->closing) {
mutex_unlock(&mut);
+ ucma_put_ctx(ctx);
+ wait_for_completion(&ctx->comp);
/* rdma_destroy_id ensures that no event handlers are
* inflight for that id before releasing it.
*/
diff --git a/drivers/infiniband/hw/cxgb4/qp.c b/drivers/infiniband/hw/cxgb4/qp.c
index cc2243f..bb45eb2 100644
--- a/drivers/infiniband/hw/cxgb4/qp.c
+++ b/drivers/infiniband/hw/cxgb4/qp.c
@@ -1258,6 +1258,12 @@
t4_set_wq_in_error(&qhp->wq);
if (qhp->ibqp.uobject) {
+
+ /* for user qps, qhp->wq.flushed is protected by qhp->mutex */
+ if (qhp->wq.flushed)
+ return;
+
+ qhp->wq.flushed = 1;
t4_set_cq_in_error(&rchp->cq);
spin_lock_irqsave(&rchp->comp_handler_lock, flag);
(*rchp->ibcq.comp_handler)(&rchp->ibcq, rchp->ibcq.cq_context);
diff --git a/drivers/infiniband/hw/hfi1/pio.c b/drivers/infiniband/hw/hfi1/pio.c
index d89b874..c2982bb 100644
--- a/drivers/infiniband/hw/hfi1/pio.c
+++ b/drivers/infiniband/hw/hfi1/pio.c
@@ -88,6 +88,7 @@
unsigned long flags;
int write = 1; /* write sendctrl back */
int flush = 0; /* re-read sendctrl to make sure it is flushed */
+ int i;
spin_lock_irqsave(&dd->sendctrl_lock, flags);
@@ -97,9 +98,13 @@
reg |= SEND_CTRL_SEND_ENABLE_SMASK;
/* Fall through */
case PSC_DATA_VL_ENABLE:
+ mask = 0;
+ for (i = 0; i < ARRAY_SIZE(dd->vld); i++)
+ if (!dd->vld[i].mtu)
+ mask |= BIT_ULL(i);
/* Disallow sending on VLs not enabled */
- mask = (((~0ull) << num_vls) & SEND_CTRL_UNSUPPORTED_VL_MASK) <<
- SEND_CTRL_UNSUPPORTED_VL_SHIFT;
+ mask = (mask & SEND_CTRL_UNSUPPORTED_VL_MASK) <<
+ SEND_CTRL_UNSUPPORTED_VL_SHIFT;
reg = (reg & ~SEND_CTRL_UNSUPPORTED_VL_SMASK) | mask;
break;
case PSC_GLOBAL_DISABLE:
diff --git a/drivers/infiniband/hw/hfi1/user_sdma.c b/drivers/infiniband/hw/hfi1/user_sdma.c
index 77697d6..018a415 100644
--- a/drivers/infiniband/hw/hfi1/user_sdma.c
+++ b/drivers/infiniband/hw/hfi1/user_sdma.c
@@ -956,7 +956,7 @@
if (ACCESS_ONCE(iovec->offset) == iovec->iov.iov_len) {
if (++req->iov_idx == req->data_iovs) {
ret = -EFAULT;
- goto free_txreq;
+ goto free_tx;
}
iovec = &req->iovs[req->iov_idx];
WARN_ON(iovec->offset);
diff --git a/drivers/infiniband/hw/hfi1/verbs.c b/drivers/infiniband/hw/hfi1/verbs.c
index 01a380e..14ddb75 100644
--- a/drivers/infiniband/hw/hfi1/verbs.c
+++ b/drivers/infiniband/hw/hfi1/verbs.c
@@ -1511,12 +1511,18 @@
struct hfi1_pportdata *ppd;
struct hfi1_devdata *dd;
u8 sc5;
+ u8 sl;
/* test the mapping for validity */
ibp = to_iport(ibdev, ah_attr->port_num);
ppd = ppd_from_ibp(ibp);
- sc5 = ibp->sl_to_sc[ah_attr->sl];
dd = dd_from_ppd(ppd);
+
+ sl = ah_attr->sl;
+ if (sl >= ARRAY_SIZE(ibp->sl_to_sc))
+ return -EINVAL;
+
+ sc5 = ibp->sl_to_sc[sl];
if (sc_to_vlt(dd, sc5) > num_vls && sc_to_vlt(dd, sc5) != 0xf)
return -EINVAL;
return 0;
diff --git a/drivers/infiniband/ulp/srp/ib_srp.c b/drivers/infiniband/ulp/srp/ib_srp.c
index 463ea59..6463590 100644
--- a/drivers/infiniband/ulp/srp/ib_srp.c
+++ b/drivers/infiniband/ulp/srp/ib_srp.c
@@ -2639,7 +2639,7 @@
{
struct srp_target_port *target = host_to_target(scmnd->device->host);
struct srp_rdma_ch *ch;
- int i;
+ int i, j;
u8 status;
shost_printk(KERN_ERR, target->scsi_host, "SRP reset_device called\n");
@@ -2653,8 +2653,8 @@
for (i = 0; i < target->ch_count; i++) {
ch = &target->ch[i];
- for (i = 0; i < target->req_ring_size; ++i) {
- struct srp_request *req = &ch->req_ring[i];
+ for (j = 0; j < target->req_ring_size; ++j) {
+ struct srp_request *req = &ch->req_ring[j];
srp_finish_req(ch, req, scmnd->device, DID_RESET << 16);
}
diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
index 4e77adb..c120afd 100644
--- a/drivers/input/mouse/elantech.c
+++ b/drivers/input/mouse/elantech.c
@@ -1176,6 +1176,8 @@
static const char * const middle_button_pnp_ids[] = {
"LEN2131", /* ThinkPad P52 w/ NFC */
"LEN2132", /* ThinkPad P52 */
+ "LEN2133", /* ThinkPad P72 w/ NFC */
+ "LEN2134", /* ThinkPad P72 */
NULL
};
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c
index 0c910a8..16199b3 100644
--- a/drivers/iommu/amd_iommu.c
+++ b/drivers/iommu/amd_iommu.c
@@ -2452,9 +2452,9 @@
}
if (amd_iommu_unmap_flush) {
- dma_ops_free_iova(dma_dom, dma_addr, pages);
domain_flush_tlb(&dma_dom->domain);
domain_flush_complete(&dma_dom->domain);
+ dma_ops_free_iova(dma_dom, dma_addr, pages);
} else {
queue_add(dma_dom, dma_addr, pages);
}
diff --git a/drivers/leds/leds-qpnp-vibrator.c b/drivers/leds/leds-qpnp-vibrator.c
index cc2615d..81f54f0 100644
--- a/drivers/leds/leds-qpnp-vibrator.c
+++ b/drivers/leds/leds-qpnp-vibrator.c
@@ -391,7 +391,6 @@
(vib->vib_play_ms % 1000) * 1000000),
HRTIMER_MODE_REL);
}
- vib->vib_play_ms = 0;
mutex_unlock(&vib->lock);
schedule_work(&vib->work);
diff --git a/drivers/md/dm-cache-metadata.c b/drivers/md/dm-cache-metadata.c
index a184c98..62eb4b7 100644
--- a/drivers/md/dm-cache-metadata.c
+++ b/drivers/md/dm-cache-metadata.c
@@ -1262,8 +1262,8 @@
if (hints_valid) {
r = dm_array_cursor_next(&cmd->hint_cursor);
if (r) {
- DMERR("dm_array_cursor_next for hint failed");
- goto out;
+ dm_array_cursor_end(&cmd->hint_cursor);
+ hints_valid = false;
}
}
}
diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index bed056c..f3993a4 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -3390,8 +3390,13 @@
static bool can_resize(struct cache *cache, dm_cblock_t new_size)
{
- if (from_cblock(new_size) > from_cblock(cache->cache_size))
- return true;
+ if (from_cblock(new_size) > from_cblock(cache->cache_size)) {
+ if (cache->sized) {
+ DMERR("%s: unable to extend cache due to missing cache table reload",
+ cache_device_name(cache));
+ return false;
+ }
+ }
/*
* We can't drop a dirty block when shrinking the cache.
diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c
index ee75e35..3f389b2 100644
--- a/drivers/md/dm-raid.c
+++ b/drivers/md/dm-raid.c
@@ -2880,6 +2880,11 @@
set_bit(RT_FLAG_UPDATE_SBS, &rs->runtime_flags);
rs_set_new(rs);
} else if (rs_is_recovering(rs)) {
+ /* Rebuild particular devices */
+ if (test_bit(__CTR_FLAG_REBUILD, &rs->ctr_flags)) {
+ set_bit(RT_FLAG_UPDATE_SBS, &rs->runtime_flags);
+ rs_setup_recovery(rs, MaxSector);
+ }
/* A recovering raid set may be resized */
; /* skip setup rs */
} else if (rs_is_reshaping(rs)) {
diff --git a/drivers/md/dm-thin-metadata.c b/drivers/md/dm-thin-metadata.c
index e976f4f..149fbac 100644
--- a/drivers/md/dm-thin-metadata.c
+++ b/drivers/md/dm-thin-metadata.c
@@ -190,6 +190,12 @@
sector_t data_block_size;
/*
+ * We reserve a section of the metadata for commit overhead.
+ * All reported space does *not* include this.
+ */
+ dm_block_t metadata_reserve;
+
+ /*
* Set if a transaction has to be aborted but the attempt to roll back
* to the previous (good) transaction failed. The only pool metadata
* operation possible in this state is the closing of the device.
@@ -827,6 +833,20 @@
return dm_tm_commit(pmd->tm, sblock);
}
+static void __set_metadata_reserve(struct dm_pool_metadata *pmd)
+{
+ int r;
+ dm_block_t total;
+ dm_block_t max_blocks = 4096; /* 16M */
+
+ r = dm_sm_get_nr_blocks(pmd->metadata_sm, &total);
+ if (r) {
+ DMERR("could not get size of metadata device");
+ pmd->metadata_reserve = max_blocks;
+ } else
+ pmd->metadata_reserve = min(max_blocks, div_u64(total, 10));
+}
+
struct dm_pool_metadata *dm_pool_metadata_open(struct block_device *bdev,
sector_t data_block_size,
bool format_device)
@@ -860,6 +880,8 @@
return ERR_PTR(r);
}
+ __set_metadata_reserve(pmd);
+
return pmd;
}
@@ -1831,6 +1853,13 @@
down_read(&pmd->root_lock);
if (!pmd->fail_io)
r = dm_sm_get_nr_free(pmd->metadata_sm, result);
+
+ if (!r) {
+ if (*result < pmd->metadata_reserve)
+ *result = 0;
+ else
+ *result -= pmd->metadata_reserve;
+ }
up_read(&pmd->root_lock);
return r;
@@ -1943,8 +1972,11 @@
int r = -EINVAL;
down_write(&pmd->root_lock);
- if (!pmd->fail_io)
+ if (!pmd->fail_io) {
r = __resize_space_map(pmd->metadata_sm, new_count);
+ if (!r)
+ __set_metadata_reserve(pmd);
+ }
up_write(&pmd->root_lock);
return r;
diff --git a/drivers/md/dm-thin.c b/drivers/md/dm-thin.c
index 5a03e15..e697283 100644
--- a/drivers/md/dm-thin.c
+++ b/drivers/md/dm-thin.c
@@ -200,7 +200,13 @@
enum pool_mode {
PM_WRITE, /* metadata may be changed */
PM_OUT_OF_DATA_SPACE, /* metadata may be changed, though data may not be allocated */
+
+ /*
+ * Like READ_ONLY, except may switch back to WRITE on metadata resize. Reported as READ_ONLY.
+ */
+ PM_OUT_OF_METADATA_SPACE,
PM_READ_ONLY, /* metadata may not be changed */
+
PM_FAIL, /* all I/O fails */
};
@@ -1386,7 +1392,35 @@
static void requeue_bios(struct pool *pool);
-static void check_for_space(struct pool *pool)
+static bool is_read_only_pool_mode(enum pool_mode mode)
+{
+ return (mode == PM_OUT_OF_METADATA_SPACE || mode == PM_READ_ONLY);
+}
+
+static bool is_read_only(struct pool *pool)
+{
+ return is_read_only_pool_mode(get_pool_mode(pool));
+}
+
+static void check_for_metadata_space(struct pool *pool)
+{
+ int r;
+ const char *ooms_reason = NULL;
+ dm_block_t nr_free;
+
+ r = dm_pool_get_free_metadata_block_count(pool->pmd, &nr_free);
+ if (r)
+ ooms_reason = "Could not get free metadata blocks";
+ else if (!nr_free)
+ ooms_reason = "No free metadata blocks";
+
+ if (ooms_reason && !is_read_only(pool)) {
+ DMERR("%s", ooms_reason);
+ set_pool_mode(pool, PM_OUT_OF_METADATA_SPACE);
+ }
+}
+
+static void check_for_data_space(struct pool *pool)
{
int r;
dm_block_t nr_free;
@@ -1412,14 +1446,16 @@
{
int r;
- if (get_pool_mode(pool) >= PM_READ_ONLY)
+ if (get_pool_mode(pool) >= PM_OUT_OF_METADATA_SPACE)
return -EINVAL;
r = dm_pool_commit_metadata(pool->pmd);
if (r)
metadata_operation_failed(pool, "dm_pool_commit_metadata", r);
- else
- check_for_space(pool);
+ else {
+ check_for_metadata_space(pool);
+ check_for_data_space(pool);
+ }
return r;
}
@@ -1485,6 +1521,19 @@
return r;
}
+ r = dm_pool_get_free_metadata_block_count(pool->pmd, &free_blocks);
+ if (r) {
+ metadata_operation_failed(pool, "dm_pool_get_free_metadata_block_count", r);
+ return r;
+ }
+
+ if (!free_blocks) {
+ /* Let's commit before we use up the metadata reserve. */
+ r = commit(pool);
+ if (r)
+ return r;
+ }
+
return 0;
}
@@ -1516,6 +1565,7 @@
case PM_OUT_OF_DATA_SPACE:
return pool->pf.error_if_no_space ? -ENOSPC : 0;
+ case PM_OUT_OF_METADATA_SPACE:
case PM_READ_ONLY:
case PM_FAIL:
return -EIO;
@@ -2479,8 +2529,9 @@
error_retry_list(pool);
break;
+ case PM_OUT_OF_METADATA_SPACE:
case PM_READ_ONLY:
- if (old_mode != new_mode)
+ if (!is_read_only_pool_mode(old_mode))
notify_of_pool_mode_change(pool, "read-only");
dm_pool_metadata_read_only(pool->pmd);
pool->process_bio = process_bio_read_only;
@@ -3418,6 +3469,10 @@
DMINFO("%s: growing the metadata device from %llu to %llu blocks",
dm_device_name(pool->pool_md),
sb_metadata_dev_size, metadata_dev_size);
+
+ if (get_pool_mode(pool) == PM_OUT_OF_METADATA_SPACE)
+ set_pool_mode(pool, PM_WRITE);
+
r = dm_pool_resize_metadata_dev(pool->pmd, metadata_dev_size);
if (r) {
metadata_operation_failed(pool, "dm_pool_resize_metadata_dev", r);
@@ -3721,7 +3776,7 @@
struct pool_c *pt = ti->private;
struct pool *pool = pt->pool;
- if (get_pool_mode(pool) >= PM_READ_ONLY) {
+ if (get_pool_mode(pool) >= PM_OUT_OF_METADATA_SPACE) {
DMERR("%s: unable to service pool target messages in READ_ONLY or FAIL mode",
dm_device_name(pool->pool_md));
return -EOPNOTSUPP;
@@ -3795,6 +3850,7 @@
dm_block_t nr_blocks_data;
dm_block_t nr_blocks_metadata;
dm_block_t held_root;
+ enum pool_mode mode;
char buf[BDEVNAME_SIZE];
char buf2[BDEVNAME_SIZE];
struct pool_c *pt = ti->private;
@@ -3865,9 +3921,10 @@
else
DMEMIT("- ");
- if (pool->pf.mode == PM_OUT_OF_DATA_SPACE)
+ mode = get_pool_mode(pool);
+ if (mode == PM_OUT_OF_DATA_SPACE)
DMEMIT("out_of_data_space ");
- else if (pool->pf.mode == PM_READ_ONLY)
+ else if (is_read_only_pool_mode(mode))
DMEMIT("ro ");
else
DMEMIT("rw ");
diff --git a/drivers/md/md-cluster.c b/drivers/md/md-cluster.c
index fcc2b57..e870b09 100644
--- a/drivers/md/md-cluster.c
+++ b/drivers/md/md-cluster.c
@@ -302,15 +302,6 @@
while (cinfo->recovery_map) {
slot = fls64((u64)cinfo->recovery_map) - 1;
- /* Clear suspend_area associated with the bitmap */
- spin_lock_irq(&cinfo->suspend_lock);
- list_for_each_entry_safe(s, tmp, &cinfo->suspend_list, list)
- if (slot == s->slot) {
- list_del(&s->list);
- kfree(s);
- }
- spin_unlock_irq(&cinfo->suspend_lock);
-
snprintf(str, 64, "bitmap%04d", slot);
bm_lockres = lockres_init(mddev, str, NULL, 1);
if (!bm_lockres) {
@@ -329,6 +320,16 @@
pr_err("md-cluster: Could not copy data from bitmap %d\n", slot);
goto clear_bit;
}
+
+ /* Clear suspend_area associated with the bitmap */
+ spin_lock_irq(&cinfo->suspend_lock);
+ list_for_each_entry_safe(s, tmp, &cinfo->suspend_list, list)
+ if (slot == s->slot) {
+ list_del(&s->list);
+ kfree(s);
+ }
+ spin_unlock_irq(&cinfo->suspend_lock);
+
if (hi > 0) {
if (lo < mddev->recovery_cp)
mddev->recovery_cp = lo;
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 8fd2ac3..0e52852 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -4381,11 +4381,12 @@
allow_barrier(conf);
}
+ raise_barrier(conf, 0);
read_more:
/* Now schedule reads for blocks from sector_nr to last */
r10_bio = mempool_alloc(conf->r10buf_pool, GFP_NOIO);
r10_bio->state = 0;
- raise_barrier(conf, sectors_done != 0);
+ raise_barrier(conf, 1);
atomic_set(&r10_bio->remaining, 0);
r10_bio->mddev = mddev;
r10_bio->sector = sector_nr;
@@ -4492,6 +4493,8 @@
if (sector_nr <= last)
goto read_more;
+ lower_barrier(conf);
+
/* Now that we have done the whole section we can
* update reshape_progress
*/
diff --git a/drivers/media/i2c/soc_camera/ov772x.c b/drivers/media/i2c/soc_camera/ov772x.c
index 7e68762..fa1cb24 100644
--- a/drivers/media/i2c/soc_camera/ov772x.c
+++ b/drivers/media/i2c/soc_camera/ov772x.c
@@ -834,7 +834,7 @@
* set COM8
*/
if (priv->band_filter) {
- ret = ov772x_mask_set(client, COM8, BNDF_ON_OFF, 1);
+ ret = ov772x_mask_set(client, COM8, BNDF_ON_OFF, BNDF_ON_OFF);
if (!ret)
ret = ov772x_mask_set(client, BDBASE,
0xff, 256 - priv->band_filter);
diff --git a/drivers/media/platform/exynos4-is/fimc-isp-video.c b/drivers/media/platform/exynos4-is/fimc-isp-video.c
index 400ce0c..e00fa03 100644
--- a/drivers/media/platform/exynos4-is/fimc-isp-video.c
+++ b/drivers/media/platform/exynos4-is/fimc-isp-video.c
@@ -384,12 +384,17 @@
struct v4l2_pix_format_mplane *pixm,
const struct fimc_fmt **fmt)
{
- *fmt = fimc_isp_find_format(&pixm->pixelformat, NULL, 2);
+ const struct fimc_fmt *__fmt;
+
+ __fmt = fimc_isp_find_format(&pixm->pixelformat, NULL, 2);
+
+ if (fmt)
+ *fmt = __fmt;
pixm->colorspace = V4L2_COLORSPACE_SRGB;
pixm->field = V4L2_FIELD_NONE;
- pixm->num_planes = (*fmt)->memplanes;
- pixm->pixelformat = (*fmt)->fourcc;
+ pixm->num_planes = __fmt->memplanes;
+ pixm->pixelformat = __fmt->fourcc;
/*
* TODO: double check with the docmentation these width/height
* constraints are correct.
diff --git a/drivers/media/platform/fsl-viu.c b/drivers/media/platform/fsl-viu.c
index ae8c6b3..7f0ed5a 100644
--- a/drivers/media/platform/fsl-viu.c
+++ b/drivers/media/platform/fsl-viu.c
@@ -1417,7 +1417,7 @@
sizeof(struct viu_reg), DRV_NAME)) {
dev_err(&op->dev, "Error while requesting mem region\n");
ret = -EBUSY;
- goto err;
+ goto err_irq;
}
/* remap registers */
@@ -1425,7 +1425,7 @@
if (!viu_regs) {
dev_err(&op->dev, "Can't map register set\n");
ret = -ENOMEM;
- goto err;
+ goto err_irq;
}
/* Prepare our private structure */
@@ -1433,7 +1433,7 @@
if (!viu_dev) {
dev_err(&op->dev, "Can't allocate private structure\n");
ret = -ENOMEM;
- goto err;
+ goto err_irq;
}
viu_dev->vr = viu_regs;
@@ -1449,16 +1449,21 @@
ret = v4l2_device_register(viu_dev->dev, &viu_dev->v4l2_dev);
if (ret < 0) {
dev_err(&op->dev, "v4l2_device_register() failed: %d\n", ret);
- goto err;
+ goto err_irq;
}
ad = i2c_get_adapter(0);
+ if (!ad) {
+ ret = -EFAULT;
+ dev_err(&op->dev, "couldn't get i2c adapter\n");
+ goto err_v4l2;
+ }
v4l2_ctrl_handler_init(&viu_dev->hdl, 5);
if (viu_dev->hdl.error) {
ret = viu_dev->hdl.error;
dev_err(&op->dev, "couldn't register control\n");
- goto err_vdev;
+ goto err_i2c;
}
/* This control handler will inherit the control(s) from the
sub-device(s). */
@@ -1476,7 +1481,7 @@
vdev = video_device_alloc();
if (vdev == NULL) {
ret = -ENOMEM;
- goto err_vdev;
+ goto err_hdl;
}
*vdev = viu_template;
@@ -1497,7 +1502,7 @@
ret = video_register_device(viu_dev->vdev, VFL_TYPE_GRABBER, -1);
if (ret < 0) {
video_device_release(viu_dev->vdev);
- goto err_vdev;
+ goto err_unlock;
}
/* enable VIU clock */
@@ -1505,12 +1510,12 @@
if (IS_ERR(clk)) {
dev_err(&op->dev, "failed to lookup the clock!\n");
ret = PTR_ERR(clk);
- goto err_clk;
+ goto err_vdev;
}
ret = clk_prepare_enable(clk);
if (ret) {
dev_err(&op->dev, "failed to enable the clock!\n");
- goto err_clk;
+ goto err_vdev;
}
viu_dev->clk = clk;
@@ -1521,7 +1526,7 @@
if (request_irq(viu_dev->irq, viu_intr, 0, "viu", (void *)viu_dev)) {
dev_err(&op->dev, "Request VIU IRQ failed.\n");
ret = -ENODEV;
- goto err_irq;
+ goto err_clk;
}
mutex_unlock(&viu_dev->lock);
@@ -1529,16 +1534,19 @@
dev_info(&op->dev, "Freescale VIU Video Capture Board\n");
return ret;
-err_irq:
- clk_disable_unprepare(viu_dev->clk);
err_clk:
- video_unregister_device(viu_dev->vdev);
+ clk_disable_unprepare(viu_dev->clk);
err_vdev:
- v4l2_ctrl_handler_free(&viu_dev->hdl);
+ video_unregister_device(viu_dev->vdev);
+err_unlock:
mutex_unlock(&viu_dev->lock);
+err_hdl:
+ v4l2_ctrl_handler_free(&viu_dev->hdl);
+err_i2c:
i2c_put_adapter(ad);
+err_v4l2:
v4l2_device_unregister(&viu_dev->v4l2_dev);
-err:
+err_irq:
irq_dispose_mapping(viu_irq);
return ret;
}
diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c
index 15a86bb..1e98b48 100644
--- a/drivers/media/platform/omap3isp/isp.c
+++ b/drivers/media/platform/omap3isp/isp.c
@@ -304,7 +304,7 @@
static int isp_xclk_init(struct isp_device *isp)
{
struct device_node *np = isp->dev->of_node;
- struct clk_init_data init;
+ struct clk_init_data init = { 0 };
unsigned int i;
for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i)
diff --git a/drivers/media/platform/s3c-camif/camif-capture.c b/drivers/media/platform/s3c-camif/camif-capture.c
index 5c9db09..d9710b5 100644
--- a/drivers/media/platform/s3c-camif/camif-capture.c
+++ b/drivers/media/platform/s3c-camif/camif-capture.c
@@ -117,6 +117,8 @@
if (camif->sensor.power_count == !on)
err = v4l2_subdev_call(sensor->sd, core, s_power, on);
+ if (err == -ENOIOCTLCMD)
+ err = 0;
if (!err)
sensor->power_count += on ? 1 : -1;
diff --git a/drivers/media/usb/tm6000/tm6000-dvb.c b/drivers/media/usb/tm6000/tm6000-dvb.c
index 0426b21..ee88ae8 100644
--- a/drivers/media/usb/tm6000/tm6000-dvb.c
+++ b/drivers/media/usb/tm6000/tm6000-dvb.c
@@ -273,6 +273,11 @@
ret = dvb_register_adapter(&dvb->adapter, "Trident TVMaster 6000 DVB-T",
THIS_MODULE, &dev->udev->dev, adapter_nr);
+ if (ret < 0) {
+ pr_err("tm6000: couldn't register the adapter!\n");
+ goto err;
+ }
+
dvb->adapter.priv = dev;
if (dvb->frontend) {
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index b5589d5..48503f30 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -163,14 +163,27 @@
}
}
+static size_t uvc_video_ctrl_size(struct uvc_streaming *stream)
+{
+ /*
+ * Return the size of the video probe and commit controls, which depends
+ * on the protocol version.
+ */
+ if (stream->dev->uvc_version < 0x0110)
+ return 26;
+ else if (stream->dev->uvc_version < 0x0150)
+ return 34;
+ else
+ return 48;
+}
+
static int uvc_get_video_ctrl(struct uvc_streaming *stream,
struct uvc_streaming_control *ctrl, int probe, __u8 query)
{
+ __u16 size = uvc_video_ctrl_size(stream);
__u8 *data;
- __u16 size;
int ret;
- size = stream->dev->uvc_version >= 0x0110 ? 34 : 26;
if ((stream->dev->quirks & UVC_QUIRK_PROBE_DEF) &&
query == UVC_GET_DEF)
return -EIO;
@@ -225,7 +238,7 @@
ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);
- if (size == 34) {
+ if (size >= 34) {
ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
ctrl->bmFramingInfo = data[30];
ctrl->bPreferedVersion = data[31];
@@ -254,11 +267,10 @@
static int uvc_set_video_ctrl(struct uvc_streaming *stream,
struct uvc_streaming_control *ctrl, int probe)
{
+ __u16 size = uvc_video_ctrl_size(stream);
__u8 *data;
- __u16 size;
int ret;
- size = stream->dev->uvc_version >= 0x0110 ? 34 : 26;
data = kzalloc(size, GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
@@ -275,7 +287,7 @@
put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);
- if (size == 34) {
+ if (size >= 34) {
put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
data[30] = ctrl->bmFramingInfo;
data[31] = ctrl->bPreferedVersion;
diff --git a/drivers/media/v4l2-core/v4l2-event.c b/drivers/media/v4l2-core/v4l2-event.c
index 8d3171c..567d868 100644
--- a/drivers/media/v4l2-core/v4l2-event.c
+++ b/drivers/media/v4l2-core/v4l2-event.c
@@ -119,14 +119,6 @@
if (sev == NULL)
return;
- /*
- * If the event has been added to the fh->subscribed list, but its
- * add op has not completed yet elems will be 0, treat this as
- * not being subscribed.
- */
- if (!sev->elems)
- return;
-
/* Increase event sequence number on fh. */
fh->sequence++;
@@ -212,6 +204,7 @@
struct v4l2_subscribed_event *sev, *found_ev;
unsigned long flags;
unsigned i;
+ int ret = 0;
if (sub->type == V4L2_EVENT_ALL)
return -EINVAL;
@@ -229,31 +222,36 @@
sev->flags = sub->flags;
sev->fh = fh;
sev->ops = ops;
+ sev->elems = elems;
+
+ mutex_lock(&fh->subscribe_lock);
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
found_ev = v4l2_event_subscribed(fh, sub->type, sub->id);
- if (!found_ev)
- list_add(&sev->list, &fh->subscribed);
spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
if (found_ev) {
+ /* Already listening */
kfree(sev);
- return 0; /* Already listening */
+ goto out_unlock;
}
if (sev->ops && sev->ops->add) {
- int ret = sev->ops->add(sev, elems);
+ ret = sev->ops->add(sev, elems);
if (ret) {
- sev->ops = NULL;
- v4l2_event_unsubscribe(fh, sub);
- return ret;
+ kfree(sev);
+ goto out_unlock;
}
}
- /* Mark as ready for use */
- sev->elems = elems;
+ spin_lock_irqsave(&fh->vdev->fh_lock, flags);
+ list_add(&sev->list, &fh->subscribed);
+ spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
- return 0;
+out_unlock:
+ mutex_unlock(&fh->subscribe_lock);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(v4l2_event_subscribe);
@@ -292,6 +290,8 @@
return 0;
}
+ mutex_lock(&fh->subscribe_lock);
+
spin_lock_irqsave(&fh->vdev->fh_lock, flags);
sev = v4l2_event_subscribed(fh, sub->type, sub->id);
@@ -309,6 +309,8 @@
if (sev && sev->ops && sev->ops->del)
sev->ops->del(sev);
+ mutex_unlock(&fh->subscribe_lock);
+
kfree(sev);
return 0;
diff --git a/drivers/media/v4l2-core/v4l2-fh.c b/drivers/media/v4l2-core/v4l2-fh.c
index c183f09..0c5e690 100644
--- a/drivers/media/v4l2-core/v4l2-fh.c
+++ b/drivers/media/v4l2-core/v4l2-fh.c
@@ -50,6 +50,7 @@
INIT_LIST_HEAD(&fh->available);
INIT_LIST_HEAD(&fh->subscribed);
fh->sequence = -1;
+ mutex_init(&fh->subscribe_lock);
}
EXPORT_SYMBOL_GPL(v4l2_fh_init);
@@ -95,6 +96,7 @@
return;
v4l_disable_media_source(fh->vdev);
v4l2_event_unsubscribe_all(fh);
+ mutex_destroy(&fh->subscribe_lock);
fh->vdev = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_fh_exit);
diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c
index 87a1337..eb57610 100644
--- a/drivers/misc/tsl2550.c
+++ b/drivers/misc/tsl2550.c
@@ -177,7 +177,7 @@
} else
lux = 0;
else
- return -EAGAIN;
+ return 0;
/* LUX range check */
return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux;
diff --git a/drivers/misc/vmw_balloon.c b/drivers/misc/vmw_balloon.c
index 518e2de..5e9122cd 100644
--- a/drivers/misc/vmw_balloon.c
+++ b/drivers/misc/vmw_balloon.c
@@ -45,6 +45,7 @@
#include <linux/seq_file.h>
#include <linux/vmw_vmci_defs.h>
#include <linux/vmw_vmci_api.h>
+#include <linux/io.h>
#include <asm/hypervisor.h>
MODULE_AUTHOR("VMware, Inc.");
diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c
index f735ab4..5927db04 100644
--- a/drivers/misc/vmw_vmci/vmci_queue_pair.c
+++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c
@@ -755,7 +755,7 @@
retval = get_user_pages_fast((uintptr_t) produce_uva,
produce_q->kernel_if->num_pages, 1,
produce_q->kernel_if->u.h.header_page);
- if (retval < produce_q->kernel_if->num_pages) {
+ if (retval < (int)produce_q->kernel_if->num_pages) {
pr_debug("get_user_pages_fast(produce) failed (retval=%d)",
retval);
qp_release_pages(produce_q->kernel_if->u.h.header_page,
@@ -767,7 +767,7 @@
retval = get_user_pages_fast((uintptr_t) consume_uva,
consume_q->kernel_if->num_pages, 1,
consume_q->kernel_if->u.h.header_page);
- if (retval < consume_q->kernel_if->num_pages) {
+ if (retval < (int)consume_q->kernel_if->num_pages) {
pr_debug("get_user_pages_fast(consume) failed (retval=%d)",
retval);
qp_release_pages(consume_q->kernel_if->u.h.header_page,
diff --git a/drivers/net/appletalk/ipddp.c b/drivers/net/appletalk/ipddp.c
index 2e46496..4e98e5a 100644
--- a/drivers/net/appletalk/ipddp.c
+++ b/drivers/net/appletalk/ipddp.c
@@ -284,8 +284,12 @@
case SIOCFINDIPDDPRT:
spin_lock_bh(&ipddp_route_lock);
rp = __ipddp_find_route(&rcp);
- if (rp)
- memcpy(&rcp2, rp, sizeof(rcp2));
+ if (rp) {
+ memset(&rcp2, 0, sizeof(rcp2));
+ rcp2.ip = rp->ip;
+ rcp2.at = rp->at;
+ rcp2.flags = rp->flags;
+ }
spin_unlock_bh(&ipddp_route_lock);
if (rp) {
diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.c b/drivers/net/ethernet/amazon/ena/ena_netdev.c
index 0d9ce08..1d92e03 100644
--- a/drivers/net/ethernet/amazon/ena/ena_netdev.c
+++ b/drivers/net/ethernet/amazon/ena/ena_netdev.c
@@ -422,7 +422,7 @@
return -ENOMEM;
}
- dma = dma_map_page(rx_ring->dev, page, 0, PAGE_SIZE,
+ dma = dma_map_page(rx_ring->dev, page, 0, ENA_PAGE_SIZE,
DMA_FROM_DEVICE);
if (unlikely(dma_mapping_error(rx_ring->dev, dma))) {
u64_stats_update_begin(&rx_ring->syncp);
@@ -439,7 +439,7 @@
rx_info->page_offset = 0;
ena_buf = &rx_info->ena_buf;
ena_buf->paddr = dma;
- ena_buf->len = PAGE_SIZE;
+ ena_buf->len = ENA_PAGE_SIZE;
return 0;
}
@@ -456,7 +456,7 @@
return;
}
- dma_unmap_page(rx_ring->dev, ena_buf->paddr, PAGE_SIZE,
+ dma_unmap_page(rx_ring->dev, ena_buf->paddr, ENA_PAGE_SIZE,
DMA_FROM_DEVICE);
__free_page(page);
@@ -849,10 +849,10 @@
do {
dma_unmap_page(rx_ring->dev,
dma_unmap_addr(&rx_info->ena_buf, paddr),
- PAGE_SIZE, DMA_FROM_DEVICE);
+ ENA_PAGE_SIZE, DMA_FROM_DEVICE);
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, rx_info->page,
- rx_info->page_offset, len, PAGE_SIZE);
+ rx_info->page_offset, len, ENA_PAGE_SIZE);
netif_dbg(rx_ring->adapter, rx_status, rx_ring->netdev,
"rx skb updated. len %d. data_len %d\n",
diff --git a/drivers/net/ethernet/amazon/ena/ena_netdev.h b/drivers/net/ethernet/amazon/ena/ena_netdev.h
index c5eaf76..008f2d5 100644
--- a/drivers/net/ethernet/amazon/ena/ena_netdev.h
+++ b/drivers/net/ethernet/amazon/ena/ena_netdev.h
@@ -321,4 +321,15 @@
int ena_get_sset_count(struct net_device *netdev, int sset);
+/* The ENA buffer length fields is 16 bit long. So when PAGE_SIZE == 64kB the
+ * driver passas 0.
+ * Since the max packet size the ENA handles is ~9kB limit the buffer length to
+ * 16kB.
+ */
+#if PAGE_SIZE > SZ_16K
+#define ENA_PAGE_SIZE SZ_16K
+#else
+#define ENA_PAGE_SIZE PAGE_SIZE
+#endif
+
#endif /* !(ENA_H) */
diff --git a/drivers/net/ethernet/cadence/macb.c b/drivers/net/ethernet/cadence/macb.c
index ec09fce..2e15856 100644
--- a/drivers/net/ethernet/cadence/macb.c
+++ b/drivers/net/ethernet/cadence/macb.c
@@ -517,7 +517,7 @@
if (!(status & MACB_BIT(TGO)))
return 0;
- usleep_range(10, 250);
+ udelay(250);
} while (time_before(halt_time, timeout));
return -ETIMEDOUT;
diff --git a/drivers/net/ethernet/hisilicon/hns/hnae.h b/drivers/net/ethernet/hisilicon/hns/hnae.h
index e093cbf..f9d6845 100644
--- a/drivers/net/ethernet/hisilicon/hns/hnae.h
+++ b/drivers/net/ethernet/hisilicon/hns/hnae.h
@@ -213,10 +213,10 @@
/* priv data for the desc, e.g. skb when use with ip stack*/
void *priv;
- u16 page_offset;
- u16 reuse_flag;
+ u32 page_offset;
+ u32 length; /* length of the buffer */
- u16 length; /* length of the buffer */
+ u16 reuse_flag;
/* desc type, used by the ring user to mark the type of the priv data */
u16 type;
diff --git a/drivers/net/ethernet/hisilicon/hns/hns_enet.c b/drivers/net/ethernet/hisilicon/hns/hns_enet.c
index 111e1aa..8a2a07e 100644
--- a/drivers/net/ethernet/hisilicon/hns/hns_enet.c
+++ b/drivers/net/ethernet/hisilicon/hns/hns_enet.c
@@ -529,7 +529,7 @@
}
skb_add_rx_frag(skb, i, desc_cb->priv, desc_cb->page_offset + pull_len,
- size - pull_len, truesize - pull_len);
+ size - pull_len, truesize);
/* avoid re-using remote pages,flag default unreuse */
if (unlikely(page_to_nid(desc_cb->priv) != numa_node_id()))
diff --git a/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c b/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c
index 6be0cae..4cd1633 100644
--- a/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c
+++ b/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c
@@ -243,7 +243,9 @@
}
if (h->dev->ops->adjust_link) {
+ netif_carrier_off(net_dev);
h->dev->ops->adjust_link(h, (int)speed, cmd->base.duplex);
+ netif_carrier_on(net_dev);
return 0;
}
diff --git a/drivers/net/ethernet/hp/hp100.c b/drivers/net/ethernet/hp/hp100.c
index 631dbc7..0988bf1 100644
--- a/drivers/net/ethernet/hp/hp100.c
+++ b/drivers/net/ethernet/hp/hp100.c
@@ -2636,7 +2636,7 @@
/* Wait for link to drop */
time = jiffies + (HZ / 10);
do {
- if (~(hp100_inb(VG_LAN_CFG_1) & HP100_LINK_UP_ST))
+ if (!(hp100_inb(VG_LAN_CFG_1) & HP100_LINK_UP_ST))
break;
if (!in_interrupt())
schedule_timeout_interruptible(1);
diff --git a/drivers/net/ethernet/intel/e1000/e1000_ethtool.c b/drivers/net/ethernet/intel/e1000/e1000_ethtool.c
index 975eeb8..e84574b 100644
--- a/drivers/net/ethernet/intel/e1000/e1000_ethtool.c
+++ b/drivers/net/ethernet/intel/e1000/e1000_ethtool.c
@@ -645,14 +645,14 @@
adapter->tx_ring = tx_old;
e1000_free_all_rx_resources(adapter);
e1000_free_all_tx_resources(adapter);
- kfree(tx_old);
- kfree(rx_old);
adapter->rx_ring = rxdr;
adapter->tx_ring = txdr;
err = e1000_up(adapter);
if (err)
goto err_setup;
}
+ kfree(tx_old);
+ kfree(rx_old);
clear_bit(__E1000_RESETTING, &adapter->flags);
return 0;
@@ -665,7 +665,8 @@
err_alloc_rx:
kfree(txdr);
err_alloc_tx:
- e1000_up(adapter);
+ if (netif_running(adapter->netdev))
+ e1000_up(adapter);
err_setup:
clear_bit(__E1000_RESETTING, &adapter->flags);
return err;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dev.c b/drivers/net/ethernet/mellanox/mlx5/core/dev.c
index a9dbc28..524fff2 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/dev.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/dev.c
@@ -288,16 +288,17 @@
}
}
-static u16 mlx5_gen_pci_id(struct mlx5_core_dev *dev)
+static u32 mlx5_gen_pci_id(struct mlx5_core_dev *dev)
{
- return (u16)((dev->pdev->bus->number << 8) |
+ return (u32)((pci_domain_nr(dev->pdev->bus) << 16) |
+ (dev->pdev->bus->number << 8) |
PCI_SLOT(dev->pdev->devfn));
}
/* Must be called with intf_mutex held */
struct mlx5_core_dev *mlx5_get_next_phys_dev(struct mlx5_core_dev *dev)
{
- u16 pci_id = mlx5_gen_pci_id(dev);
+ u32 pci_id = mlx5_gen_pci_id(dev);
struct mlx5_core_dev *res = NULL;
struct mlx5_core_dev *tmp_dev;
struct mlx5_priv *priv;
diff --git a/drivers/net/ethernet/qlogic/qed/qed_mcp.c b/drivers/net/ethernet/qlogic/qed/qed_mcp.c
index eaa242d..e175fcd 100644
--- a/drivers/net/ethernet/qlogic/qed/qed_mcp.c
+++ b/drivers/net/ethernet/qlogic/qed/qed_mcp.c
@@ -97,18 +97,57 @@
return 0;
}
+/* Maximum of 1 sec to wait for the SHMEM ready indication */
+#define QED_MCP_SHMEM_RDY_MAX_RETRIES 20
+#define QED_MCP_SHMEM_RDY_ITER_MS 50
+
static int qed_load_mcp_offsets(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
struct qed_mcp_info *p_info = p_hwfn->mcp_info;
+ u8 cnt = QED_MCP_SHMEM_RDY_MAX_RETRIES;
+ u8 msec = QED_MCP_SHMEM_RDY_ITER_MS;
u32 drv_mb_offsize, mfw_mb_offsize;
u32 mcp_pf_id = MCP_PF_ID(p_hwfn);
p_info->public_base = qed_rd(p_hwfn, p_ptt, MISC_REG_SHARED_MEM_ADDR);
- if (!p_info->public_base)
- return 0;
+ if (!p_info->public_base) {
+ DP_NOTICE(p_hwfn,
+ "The address of the MCP scratch-pad is not configured\n");
+ return -EINVAL;
+ }
p_info->public_base |= GRCBASE_MCP;
+ /* Get the MFW MB address and number of supported messages */
+ mfw_mb_offsize = qed_rd(p_hwfn, p_ptt,
+ SECTION_OFFSIZE_ADDR(p_info->public_base,
+ PUBLIC_MFW_MB));
+ p_info->mfw_mb_addr = SECTION_ADDR(mfw_mb_offsize, mcp_pf_id);
+ p_info->mfw_mb_length = (u16)qed_rd(p_hwfn, p_ptt,
+ p_info->mfw_mb_addr +
+ offsetof(struct public_mfw_mb,
+ sup_msgs));
+
+ /* The driver can notify that there was an MCP reset, and might read the
+ * SHMEM values before the MFW has completed initializing them.
+ * To avoid this, the "sup_msgs" field in the MFW mailbox is used as a
+ * data ready indication.
+ */
+ while (!p_info->mfw_mb_length && --cnt) {
+ msleep(msec);
+ p_info->mfw_mb_length =
+ (u16)qed_rd(p_hwfn, p_ptt,
+ p_info->mfw_mb_addr +
+ offsetof(struct public_mfw_mb, sup_msgs));
+ }
+
+ if (!cnt) {
+ DP_NOTICE(p_hwfn,
+ "Failed to get the SHMEM ready notification after %d msec\n",
+ QED_MCP_SHMEM_RDY_MAX_RETRIES * msec);
+ return -EBUSY;
+ }
+
/* Calculate the driver and MFW mailbox address */
drv_mb_offsize = qed_rd(p_hwfn, p_ptt,
SECTION_OFFSIZE_ADDR(p_info->public_base,
@@ -118,13 +157,6 @@
"drv_mb_offsiz = 0x%x, drv_mb_addr = 0x%x mcp_pf_id = 0x%x\n",
drv_mb_offsize, p_info->drv_mb_addr, mcp_pf_id);
- /* Set the MFW MB address */
- mfw_mb_offsize = qed_rd(p_hwfn, p_ptt,
- SECTION_OFFSIZE_ADDR(p_info->public_base,
- PUBLIC_MFW_MB));
- p_info->mfw_mb_addr = SECTION_ADDR(mfw_mb_offsize, mcp_pf_id);
- p_info->mfw_mb_length = (u16)qed_rd(p_hwfn, p_ptt, p_info->mfw_mb_addr);
-
/* Get the current driver mailbox sequence before sending
* the first command
*/
@@ -1198,31 +1230,61 @@
return rc;
}
+/* A maximal 100 msec waiting time for the MCP to halt */
+#define QED_MCP_HALT_SLEEP_MS 10
+#define QED_MCP_HALT_MAX_RETRIES 10
+
int qed_mcp_halt(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
- u32 resp = 0, param = 0;
+ u32 resp = 0, param = 0, cpu_state, cnt = 0;
int rc;
rc = qed_mcp_cmd(p_hwfn, p_ptt, DRV_MSG_CODE_MCP_HALT, 0, &resp,
¶m);
- if (rc)
+ if (rc) {
DP_ERR(p_hwfn, "MCP response failure, aborting\n");
+ return rc;
+ }
- return rc;
+ do {
+ msleep(QED_MCP_HALT_SLEEP_MS);
+ cpu_state = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_STATE);
+ if (cpu_state & MCP_REG_CPU_STATE_SOFT_HALTED)
+ break;
+ } while (++cnt < QED_MCP_HALT_MAX_RETRIES);
+
+ if (cnt == QED_MCP_HALT_MAX_RETRIES) {
+ DP_NOTICE(p_hwfn,
+ "Failed to halt the MCP [CPU_MODE = 0x%08x, CPU_STATE = 0x%08x]\n",
+ qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_MODE), cpu_state);
+ return -EBUSY;
+ }
+
+ return 0;
}
+#define QED_MCP_RESUME_SLEEP_MS 10
+
int qed_mcp_resume(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt)
{
- u32 value, cpu_mode;
+ u32 cpu_mode, cpu_state;
qed_wr(p_hwfn, p_ptt, MCP_REG_CPU_STATE, 0xffffffff);
- value = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_MODE);
- value &= ~MCP_REG_CPU_MODE_SOFT_HALT;
- qed_wr(p_hwfn, p_ptt, MCP_REG_CPU_MODE, value);
cpu_mode = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_MODE);
+ cpu_mode &= ~MCP_REG_CPU_MODE_SOFT_HALT;
+ qed_wr(p_hwfn, p_ptt, MCP_REG_CPU_MODE, cpu_mode);
+ msleep(QED_MCP_RESUME_SLEEP_MS);
+ cpu_state = qed_rd(p_hwfn, p_ptt, MCP_REG_CPU_STATE);
- return (cpu_mode & MCP_REG_CPU_MODE_SOFT_HALT) ? -EAGAIN : 0;
+ if (cpu_state & MCP_REG_CPU_STATE_SOFT_HALTED) {
+ DP_NOTICE(p_hwfn,
+ "Failed to resume the MCP [CPU_MODE = 0x%08x, CPU_STATE = 0x%08x]\n",
+ cpu_mode, cpu_state);
+ return -EBUSY;
+ }
+
+ return 0;
}
int qed_mcp_set_led(struct qed_hwfn *p_hwfn,
diff --git a/drivers/net/ethernet/qlogic/qed/qed_reg_addr.h b/drivers/net/ethernet/qlogic/qed/qed_reg_addr.h
index b414a05..56be1d6 100644
--- a/drivers/net/ethernet/qlogic/qed/qed_reg_addr.h
+++ b/drivers/net/ethernet/qlogic/qed/qed_reg_addr.h
@@ -510,6 +510,7 @@
0
#define MCP_REG_CPU_STATE \
0xe05004UL
+#define MCP_REG_CPU_STATE_SOFT_HALTED (0x1UL << 10)
#define MCP_REG_CPU_EVENT_MASK \
0xe05008UL
#define PGLUE_B_REG_PF_BAR0_SIZE \
diff --git a/drivers/net/ethernet/realtek/r8169.c b/drivers/net/ethernet/realtek/r8169.c
index f65e8cd..20f5c0c 100644
--- a/drivers/net/ethernet/realtek/r8169.c
+++ b/drivers/net/ethernet/realtek/r8169.c
@@ -760,7 +760,7 @@
};
enum rtl_flag {
- RTL_FLAG_TASK_ENABLED,
+ RTL_FLAG_TASK_ENABLED = 0,
RTL_FLAG_TASK_SLOW_PENDING,
RTL_FLAG_TASK_RESET_PENDING,
RTL_FLAG_TASK_PHY_PENDING,
@@ -7637,7 +7637,8 @@
rtl8169_update_counters(dev);
rtl_lock_work(tp);
- clear_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags);
+ /* Clear all task flags */
+ bitmap_zero(tp->wk.flags, RTL_FLAG_MAX);
rtl8169_down(dev);
rtl_unlock_work(tp);
@@ -7820,7 +7821,9 @@
rtl_lock_work(tp);
napi_disable(&tp->napi);
- clear_bit(RTL_FLAG_TASK_ENABLED, tp->wk.flags);
+ /* Clear all task flags */
+ bitmap_zero(tp->wk.flags, RTL_FLAG_MAX);
+
rtl_unlock_work(tp);
rtl_pll_power_down(tp);
diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c
index 2e5150b..7a14e81 100644
--- a/drivers/net/phy/xilinx_gmii2rgmii.c
+++ b/drivers/net/phy/xilinx_gmii2rgmii.c
@@ -40,8 +40,11 @@
{
struct gmii2rgmii *priv = phydev->priv;
u16 val = 0;
+ int err;
- priv->phy_drv->read_status(phydev);
+ err = priv->phy_drv->read_status(phydev);
+ if (err < 0)
+ return err;
val = mdiobus_read(phydev->mdio.bus, priv->addr, XILINX_GMII2RGMII_REG);
val &= ~XILINX_GMII2RGMII_SPEED_MASK;
@@ -81,6 +84,11 @@
return -EPROBE_DEFER;
}
+ if (!priv->phy_dev->drv) {
+ dev_info(dev, "Attached phy not ready\n");
+ return -EPROBE_DEFER;
+ }
+
priv->addr = mdiodev->addr;
priv->phy_drv = priv->phy_dev->drv;
memcpy(&priv->conv_phy_drv, priv->phy_dev->drv,
diff --git a/drivers/net/wireless/ath/ath10k/debug.c b/drivers/net/wireless/ath/ath10k/debug.c
index 0dadc60..b106a06 100644
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2005-2011 Atheros Communications Inc.
* Copyright (c) 2011-2013 Qualcomm Atheros, Inc.
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -161,6 +162,8 @@
void ath10k_debug_print_board_info(struct ath10k *ar)
{
char boardinfo[100];
+ const struct firmware *board;
+ u32 crc;
if (ar->id.bmi_ids_valid)
scnprintf(boardinfo, sizeof(boardinfo), "%d:%d",
@@ -168,11 +171,16 @@
else
scnprintf(boardinfo, sizeof(boardinfo), "N/A");
+ board = ar->normal_mode_fw.board;
+ if (!IS_ERR_OR_NULL(board))
+ crc = crc32_le(0, board->data, board->size);
+ else
+ crc = 0;
+
ath10k_info(ar, "board_file api %d bmi_id %s crc32 %08x",
ar->bd_api,
boardinfo,
- crc32_le(0, ar->normal_mode_fw.board->data,
- ar->normal_mode_fw.board->size));
+ crc);
}
void ath10k_debug_print_boot_info(struct ath10k *ar)
diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c
index ba1fe61..a3c2180 100644
--- a/drivers/net/wireless/ath/ath10k/htt_rx.c
+++ b/drivers/net/wireless/ath/ath10k/htt_rx.c
@@ -214,11 +214,12 @@
spin_lock_bh(&htt->rx_ring.lock);
ret = ath10k_htt_rx_ring_fill_n(htt, (htt->rx_ring.fill_level -
htt->rx_ring.fill_cnt));
- spin_unlock_bh(&htt->rx_ring.lock);
if (ret)
ath10k_htt_rx_ring_free(htt);
+ spin_unlock_bh(&htt->rx_ring.lock);
+
return ret;
}
@@ -230,7 +231,9 @@
skb_queue_purge(&htt->rx_in_ord_compl_q);
skb_queue_purge(&htt->tx_fetch_ind_q);
+ spin_lock_bh(&htt->rx_ring.lock);
ath10k_htt_rx_ring_free(htt);
+ spin_unlock_bh(&htt->rx_ring.lock);
dma_free_coherent(htt->ar->dev,
(htt->rx_ring.size *
diff --git a/drivers/net/wireless/ath/ath10k/trace.h b/drivers/net/wireless/ath/ath10k/trace.h
index e0d00ce..5b974bb 100644
--- a/drivers/net/wireless/ath/ath10k/trace.h
+++ b/drivers/net/wireless/ath/ath10k/trace.h
@@ -152,10 +152,9 @@
);
TRACE_EVENT(ath10k_wmi_cmd,
- TP_PROTO(struct ath10k *ar, int id, const void *buf, size_t buf_len,
- int ret),
+ TP_PROTO(struct ath10k *ar, int id, const void *buf, size_t buf_len),
- TP_ARGS(ar, id, buf, buf_len, ret),
+ TP_ARGS(ar, id, buf, buf_len),
TP_STRUCT__entry(
__string(device, dev_name(ar->dev))
@@ -163,7 +162,6 @@
__field(unsigned int, id)
__field(size_t, buf_len)
__dynamic_array(u8, buf, buf_len)
- __field(int, ret)
),
TP_fast_assign(
@@ -171,17 +169,15 @@
__assign_str(driver, dev_driver_string(ar->dev));
__entry->id = id;
__entry->buf_len = buf_len;
- __entry->ret = ret;
memcpy(__get_dynamic_array(buf), buf, buf_len);
),
TP_printk(
- "%s %s id %d len %zu ret %d",
+ "%s %s id %d len %zu",
__get_str(driver),
__get_str(device),
__entry->id,
- __entry->buf_len,
- __entry->ret
+ __entry->buf_len
)
);
diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
index f69b98f..642a441 100644
--- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
+++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
@@ -1486,10 +1486,10 @@
bssid_len = arg->n_bssids * sizeof(struct wmi_mac_addr);
ie_len = roundup(arg->ie_len, 4);
len = (sizeof(*tlv) + sizeof(*cmd)) +
- (arg->n_channels ? sizeof(*tlv) + chan_len : 0) +
- (arg->n_ssids ? sizeof(*tlv) + ssid_len : 0) +
- (arg->n_bssids ? sizeof(*tlv) + bssid_len : 0) +
- (arg->ie_len ? sizeof(*tlv) + ie_len : 0);
+ sizeof(*tlv) + chan_len +
+ sizeof(*tlv) + ssid_len +
+ sizeof(*tlv) + bssid_len +
+ sizeof(*tlv) + ie_len;
skb = ath10k_wmi_alloc_skb(ar, len);
if (!skb)
diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
index e518b64..75f7a7b 100644
--- a/drivers/net/wireless/ath/ath10k/wmi.c
+++ b/drivers/net/wireless/ath/ath10k/wmi.c
@@ -1711,8 +1711,8 @@
cmd_hdr->cmd_id = __cpu_to_le32(cmd);
memset(skb_cb, 0, sizeof(*skb_cb));
+ trace_ath10k_wmi_cmd(ar, cmd_id, skb->data, skb->len);
ret = ath10k_htc_send(&ar->htc, ar->wmi.eid, skb);
- trace_ath10k_wmi_cmd(ar, cmd_id, skb->data, skb->len, ret);
if (ret)
goto err_pull;
diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c
index 95e9641..4bb36dc 100644
--- a/drivers/net/wireless/mac80211_hwsim.c
+++ b/drivers/net/wireless/mac80211_hwsim.c
@@ -2569,9 +2569,6 @@
IEEE80211_VHT_CAP_SHORT_GI_80 |
IEEE80211_VHT_CAP_SHORT_GI_160 |
IEEE80211_VHT_CAP_TXSTBC |
- IEEE80211_VHT_CAP_RXSTBC_1 |
- IEEE80211_VHT_CAP_RXSTBC_2 |
- IEEE80211_VHT_CAP_RXSTBC_3 |
IEEE80211_VHT_CAP_RXSTBC_4 |
IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
sband->vht_cap.vht_mcs.rx_mcs_map =
diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c
index ae87b39..2e92872 100644
--- a/drivers/net/wireless/rndis_wlan.c
+++ b/drivers/net/wireless/rndis_wlan.c
@@ -2928,6 +2928,8 @@
while (buflen >= sizeof(*auth_req)) {
auth_req = (void *)buf;
+ if (buflen < le32_to_cpu(auth_req->length))
+ return;
type = "unknown";
flags = le32_to_cpu(auth_req->flags);
pairwise_error = false;
diff --git a/drivers/net/wireless/ti/wlcore/cmd.c b/drivers/net/wireless/ti/wlcore/cmd.c
index 7f4da72..96f83f0 100644
--- a/drivers/net/wireless/ti/wlcore/cmd.c
+++ b/drivers/net/wireless/ti/wlcore/cmd.c
@@ -35,6 +35,7 @@
#include "wl12xx_80211.h"
#include "cmd.h"
#include "event.h"
+#include "ps.h"
#include "tx.h"
#include "hw_ops.h"
@@ -191,6 +192,10 @@
timeout_time = jiffies + msecs_to_jiffies(WL1271_EVENT_TIMEOUT);
+ ret = wl1271_ps_elp_wakeup(wl);
+ if (ret < 0)
+ return ret;
+
do {
if (time_after(jiffies, timeout_time)) {
wl1271_debug(DEBUG_CMD, "timeout waiting for event %d",
@@ -222,6 +227,7 @@
} while (!event);
out:
+ wl1271_ps_elp_sleep(wl);
kfree(events_vector);
return ret;
}
diff --git a/drivers/net/xen-netback/hash.c b/drivers/net/xen-netback/hash.c
index 3c4c58b..3b6fb5b 100644
--- a/drivers/net/xen-netback/hash.c
+++ b/drivers/net/xen-netback/hash.c
@@ -332,20 +332,22 @@
u32 xenvif_set_hash_mapping(struct xenvif *vif, u32 gref, u32 len,
u32 off)
{
- u32 *mapping = &vif->hash.mapping[off];
+ u32 *mapping = vif->hash.mapping;
struct gnttab_copy copy_op = {
.source.u.ref = gref,
.source.domid = vif->domid,
- .dest.u.gmfn = virt_to_gfn(mapping),
.dest.domid = DOMID_SELF,
- .dest.offset = xen_offset_in_page(mapping),
- .len = len * sizeof(u32),
+ .len = len * sizeof(*mapping),
.flags = GNTCOPY_source_gref
};
- if ((off + len > vif->hash.size) || copy_op.len > XEN_PAGE_SIZE)
+ if ((off + len < off) || (off + len > vif->hash.size) ||
+ len > XEN_PAGE_SIZE / sizeof(*mapping))
return XEN_NETIF_CTRL_STATUS_INVALID_PARAMETER;
+ copy_op.dest.u.gmfn = virt_to_gfn(mapping + off);
+ copy_op.dest.offset = xen_offset_in_page(mapping + off);
+
while (len-- != 0)
if (mapping[off++] >= vif->num_queues)
return XEN_NETIF_CTRL_STATUS_INVALID_PARAMETER;
diff --git a/drivers/net/xen-netfront.c b/drivers/net/xen-netfront.c
index 7f6af10..3c1adb3 100644
--- a/drivers/net/xen-netfront.c
+++ b/drivers/net/xen-netfront.c
@@ -906,7 +906,11 @@
BUG_ON(pull_to <= skb_headlen(skb));
__pskb_pull_tail(skb, pull_to - skb_headlen(skb));
}
- BUG_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS);
+ if (unlikely(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS)) {
+ queue->rx.rsp_cons = ++cons;
+ kfree_skb(nskb);
+ return ~0U;
+ }
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
skb_frag_page(nfrag),
@@ -1043,6 +1047,8 @@
skb->len += rx->status;
i = xennet_fill_frags(queue, skb, &tmpq);
+ if (unlikely(i == ~0U))
+ goto err;
if (rx->flags & XEN_NETRXF_csum_blank)
skb->ip_summed = CHECKSUM_PARTIAL;
diff --git a/drivers/nvme/target/rdma.c b/drivers/nvme/target/rdma.c
index 53bd325..2dfd877 100644
--- a/drivers/nvme/target/rdma.c
+++ b/drivers/nvme/target/rdma.c
@@ -65,6 +65,7 @@
struct nvmet_req req;
+ bool allocated;
u8 n_rdma;
u32 flags;
u32 invalidate_rkey;
@@ -167,11 +168,19 @@
unsigned long flags;
spin_lock_irqsave(&queue->rsps_lock, flags);
- rsp = list_first_entry(&queue->free_rsps,
+ rsp = list_first_entry_or_null(&queue->free_rsps,
struct nvmet_rdma_rsp, free_list);
- list_del(&rsp->free_list);
+ if (likely(rsp))
+ list_del(&rsp->free_list);
spin_unlock_irqrestore(&queue->rsps_lock, flags);
+ if (unlikely(!rsp)) {
+ rsp = kmalloc(sizeof(*rsp), GFP_KERNEL);
+ if (unlikely(!rsp))
+ return NULL;
+ rsp->allocated = true;
+ }
+
return rsp;
}
@@ -180,6 +189,11 @@
{
unsigned long flags;
+ if (rsp->allocated) {
+ kfree(rsp);
+ return;
+ }
+
spin_lock_irqsave(&rsp->queue->rsps_lock, flags);
list_add_tail(&rsp->free_list, &rsp->queue->free_rsps);
spin_unlock_irqrestore(&rsp->queue->rsps_lock, flags);
@@ -755,6 +769,15 @@
cmd->queue = queue;
rsp = nvmet_rdma_get_rsp(queue);
+ if (unlikely(!rsp)) {
+ /*
+ * we get here only under memory pressure,
+ * silently drop and have the host retry
+ * as we can't even fail it.
+ */
+ nvmet_rdma_post_recv(queue->dev, cmd);
+ return;
+ }
rsp->queue = queue;
rsp->cmd = cmd;
rsp->flags = 0;
diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c
index 90b5a89..0a1ebbb 100644
--- a/drivers/of/unittest.c
+++ b/drivers/of/unittest.c
@@ -548,6 +548,9 @@
struct of_phandle_args args;
int i, rc;
+ if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
+ return;
+
np = of_find_node_by_path("/testcase-data/interrupts/interrupts0");
if (!np) {
pr_err("missing testcase data\n");
@@ -622,6 +625,9 @@
struct of_phandle_args args;
int i, rc;
+ if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)
+ return;
+
np = of_find_node_by_path("/testcase-data/interrupts/interrupts-extended0");
if (!np) {
pr_err("missing testcase data\n");
@@ -778,15 +784,19 @@
pdev = of_find_device_by_node(np);
unittest(pdev, "device 1 creation failed\n");
- irq = platform_get_irq(pdev, 0);
- unittest(irq == -EPROBE_DEFER, "device deferred probe failed - %d\n", irq);
+ if (!(of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)) {
+ irq = platform_get_irq(pdev, 0);
+ unittest(irq == -EPROBE_DEFER,
+ "device deferred probe failed - %d\n", irq);
- /* Test that a parsing failure does not return -EPROBE_DEFER */
- np = of_find_node_by_path("/testcase-data/testcase-device2");
- pdev = of_find_device_by_node(np);
- unittest(pdev, "device 2 creation failed\n");
- irq = platform_get_irq(pdev, 0);
- unittest(irq < 0 && irq != -EPROBE_DEFER, "device parsing error failed - %d\n", irq);
+ /* Test that a parsing failure does not return -EPROBE_DEFER */
+ np = of_find_node_by_path("/testcase-data/testcase-device2");
+ pdev = of_find_device_by_node(np);
+ unittest(pdev, "device 2 creation failed\n");
+ irq = platform_get_irq(pdev, 0);
+ unittest(irq < 0 && irq != -EPROBE_DEFER,
+ "device parsing error failed - %d\n", irq);
+ }
np = of_find_node_by_path("/testcase-data/platform-tests");
unittest(np, "No testcase data in device tree\n");
diff --git a/drivers/pci/host/pci-aardvark.c b/drivers/pci/host/pci-aardvark.c
index 11bad82..1dbd09c 100644
--- a/drivers/pci/host/pci-aardvark.c
+++ b/drivers/pci/host/pci-aardvark.c
@@ -976,6 +976,7 @@
return -ENOMEM;
}
+ pci_bus_size_bridges(bus);
pci_bus_assign_resources(bus);
list_for_each_entry(child, &bus->children, node)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 6b3c5c4..ccbbd4c 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -1114,12 +1114,12 @@
EXPORT_SYMBOL(pci_save_state);
static void pci_restore_config_dword(struct pci_dev *pdev, int offset,
- u32 saved_val, int retry)
+ u32 saved_val, int retry, bool force)
{
u32 val;
pci_read_config_dword(pdev, offset, &val);
- if (val == saved_val)
+ if (!force && val == saved_val)
return;
for (;;) {
@@ -1138,25 +1138,36 @@
}
static void pci_restore_config_space_range(struct pci_dev *pdev,
- int start, int end, int retry)
+ int start, int end, int retry,
+ bool force)
{
int index;
for (index = end; index >= start; index--)
pci_restore_config_dword(pdev, 4 * index,
pdev->saved_config_space[index],
- retry);
+ retry, force);
}
static void pci_restore_config_space(struct pci_dev *pdev)
{
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL) {
- pci_restore_config_space_range(pdev, 10, 15, 0);
+ pci_restore_config_space_range(pdev, 10, 15, 0, false);
/* Restore BARs before the command register. */
- pci_restore_config_space_range(pdev, 4, 9, 10);
- pci_restore_config_space_range(pdev, 0, 3, 0);
+ pci_restore_config_space_range(pdev, 4, 9, 10, false);
+ pci_restore_config_space_range(pdev, 0, 3, 0, false);
+ } else if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
+ pci_restore_config_space_range(pdev, 12, 15, 0, false);
+
+ /*
+ * Force rewriting of prefetch registers to avoid S3 resume
+ * issues on Intel PCI bridges that occur when these
+ * registers are not explicitly written.
+ */
+ pci_restore_config_space_range(pdev, 9, 11, 0, true);
+ pci_restore_config_space_range(pdev, 0, 8, 0, false);
} else {
- pci_restore_config_space_range(pdev, 0, 15, 0);
+ pci_restore_config_space_range(pdev, 0, 15, 0, false);
}
}
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index a05d143..c7a695c 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -4236,11 +4236,6 @@
*
* 0x9d10-0x9d1b PCI Express Root port #{1-12}
*
- * The 300 series chipset suffers from the same bug so include those root
- * ports here as well.
- *
- * 0xa32c-0xa343 PCI Express Root port #{0-24}
- *
* [1] http://www.intel.com/content/www/us/en/chipsets/100-series-chipset-datasheet-vol-2.html
* [2] http://www.intel.com/content/www/us/en/chipsets/100-series-chipset-datasheet-vol-1.html
* [3] http://www.intel.com/content/www/us/en/chipsets/100-series-chipset-spec-update.html
@@ -4258,7 +4253,6 @@
case 0xa110 ... 0xa11f: case 0xa167 ... 0xa16a: /* Sunrise Point */
case 0xa290 ... 0xa29f: case 0xa2e7 ... 0xa2ee: /* Union Point */
case 0x9d10 ... 0x9d1b: /* 7th & 8th Gen Mobile */
- case 0xa32c ... 0xa343: /* 300 series */
return true;
}
diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c
index 0056294..fe41993 100644
--- a/drivers/platform/x86/alienware-wmi.c
+++ b/drivers/platform/x86/alienware-wmi.c
@@ -518,6 +518,7 @@
if (obj && obj->type == ACPI_TYPE_INTEGER)
*out_data = (u32) obj->integer.value;
}
+ kfree(output.pointer);
return status;
}
diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c
index 102f95a..e9e749f 100644
--- a/drivers/power/reset/vexpress-poweroff.c
+++ b/drivers/power/reset/vexpress-poweroff.c
@@ -35,6 +35,7 @@
}
static struct device *vexpress_power_off_device;
+static atomic_t vexpress_restart_nb_refcnt = ATOMIC_INIT(0);
static void vexpress_power_off(void)
{
@@ -99,10 +100,13 @@
int err;
vexpress_restart_device = dev;
- err = register_restart_handler(&vexpress_restart_nb);
- if (err) {
- dev_err(dev, "cannot register restart handler (err=%d)\n", err);
- return err;
+ if (atomic_inc_return(&vexpress_restart_nb_refcnt) == 1) {
+ err = register_restart_handler(&vexpress_restart_nb);
+ if (err) {
+ dev_err(dev, "cannot register restart handler (err=%d)\n", err);
+ atomic_dec(&vexpress_restart_nb_refcnt);
+ return err;
+ }
}
device_create_file(dev, &dev_attr_active);
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 077d237..77b6885 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -14,6 +14,7 @@
#include <linux/types.h>
#include <linux/init.h>
#include <linux/slab.h>
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/notifier.h>
#include <linux/err.h>
@@ -141,8 +142,13 @@
struct power_supply *psy = container_of(work, struct power_supply,
deferred_register_work.work);
- if (psy->dev.parent)
- mutex_lock(&psy->dev.parent->mutex);
+ if (psy->dev.parent) {
+ while (!mutex_trylock(&psy->dev.parent->mutex)) {
+ if (psy->removing)
+ return;
+ msleep(10);
+ }
+ }
psy_register_cooler(psy->dev.parent, psy);
power_supply_changed(psy);
@@ -948,6 +954,7 @@
void power_supply_unregister(struct power_supply *psy)
{
WARN_ON(atomic_dec_return(&psy->use_cnt));
+ psy->removing = true;
cancel_work_sync(&psy->changed_work);
cancel_delayed_work_sync(&psy->deferred_register_work);
sysfs_remove_link(&psy->dev.kobj, "powers");
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 1a25499..0d81304 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -4458,13 +4458,13 @@
!rdev->desc->fixed_uV)
rdev->is_switch = true;
+ dev_set_drvdata(&rdev->dev, rdev);
ret = device_register(&rdev->dev);
if (ret != 0) {
put_device(&rdev->dev);
goto unset_supplies;
}
- dev_set_drvdata(&rdev->dev, rdev);
rdev_init_debugfs(rdev);
rdev->proxy_consumer = regulator_proxy_consumer_register(dev,
config->of_node);
diff --git a/drivers/s390/net/qeth_core_main.c b/drivers/s390/net/qeth_core_main.c
index 258a728..a5e6030 100644
--- a/drivers/s390/net/qeth_core_main.c
+++ b/drivers/s390/net/qeth_core_main.c
@@ -23,6 +23,7 @@
#include <linux/netdevice.h>
#include <linux/netdev_features.h>
#include <linux/skbuff.h>
+#include <linux/vmalloc.h>
#include <net/iucv/af_iucv.h>
#include <net/dsfield.h>
@@ -4715,7 +4716,7 @@
priv.buffer_len = oat_data.buffer_len;
priv.response_len = 0;
- priv.buffer = kzalloc(oat_data.buffer_len, GFP_KERNEL);
+ priv.buffer = vzalloc(oat_data.buffer_len);
if (!priv.buffer) {
rc = -ENOMEM;
goto out;
@@ -4756,7 +4757,7 @@
rc = -EFAULT;
out_free:
- kfree(priv.buffer);
+ vfree(priv.buffer);
out:
return rc;
}
diff --git a/drivers/s390/net/qeth_l2_main.c b/drivers/s390/net/qeth_l2_main.c
index e94e957..58404e6 100644
--- a/drivers/s390/net/qeth_l2_main.c
+++ b/drivers/s390/net/qeth_l2_main.c
@@ -491,7 +491,7 @@
default:
dev_kfree_skb_any(skb);
QETH_CARD_TEXT(card, 3, "inbunkno");
- QETH_DBF_HEX(CTRL, 3, hdr, QETH_DBF_CTRL_LEN);
+ QETH_DBF_HEX(CTRL, 3, hdr, sizeof(*hdr));
continue;
}
work_done++;
diff --git a/drivers/s390/net/qeth_l3_main.c b/drivers/s390/net/qeth_l3_main.c
index 4ca161b..efefe07 100644
--- a/drivers/s390/net/qeth_l3_main.c
+++ b/drivers/s390/net/qeth_l3_main.c
@@ -1836,7 +1836,7 @@
default:
dev_kfree_skb_any(skb);
QETH_CARD_TEXT(card, 3, "inbunkno");
- QETH_DBF_HEX(CTRL, 3, hdr, QETH_DBF_CTRL_LEN);
+ QETH_DBF_HEX(CTRL, 3, hdr, sizeof(*hdr));
continue;
}
work_done++;
diff --git a/drivers/scsi/bnx2i/bnx2i_hwi.c b/drivers/scsi/bnx2i/bnx2i_hwi.c
index 42921db..4ca1050 100644
--- a/drivers/scsi/bnx2i/bnx2i_hwi.c
+++ b/drivers/scsi/bnx2i/bnx2i_hwi.c
@@ -2742,6 +2742,8 @@
BNX2X_DOORBELL_PCI_BAR);
reg_off = (1 << BNX2X_DB_SHIFT) * (cid_num & 0x1FFFF);
ep->qp.ctx_base = ioremap_nocache(reg_base + reg_off, 4);
+ if (!ep->qp.ctx_base)
+ return -ENOMEM;
goto arm_cq;
}
diff --git a/drivers/scsi/ibmvscsi/ibmvscsi.c b/drivers/scsi/ibmvscsi/ibmvscsi.c
index d9534ee..e173022 100644
--- a/drivers/scsi/ibmvscsi/ibmvscsi.c
+++ b/drivers/scsi/ibmvscsi/ibmvscsi.c
@@ -93,7 +93,7 @@
static int max_events = IBMVSCSI_MAX_REQUESTS_DEFAULT + 2;
static int fast_fail = 1;
static int client_reserve = 1;
-static char partition_name[97] = "UNKNOWN";
+static char partition_name[96] = "UNKNOWN";
static unsigned int partition_number = -1;
static struct scsi_transport_template *ibmvscsi_transport_template;
@@ -259,7 +259,7 @@
ppartition_name = of_get_property(of_root, "ibm,partition-name", NULL);
if (ppartition_name)
- strncpy(partition_name, ppartition_name,
+ strlcpy(partition_name, ppartition_name,
sizeof(partition_name));
p_number_ptr = of_get_property(of_root, "ibm,partition-no", NULL);
if (p_number_ptr)
diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c
index 35cbd36..090fdcd 100644
--- a/drivers/scsi/megaraid/megaraid_sas_base.c
+++ b/drivers/scsi/megaraid/megaraid_sas_base.c
@@ -6193,6 +6193,9 @@
goto fail_init_mfi;
}
+ if (megasas_get_ctrl_info(instance) != DCMD_SUCCESS)
+ goto fail_init_mfi;
+
tasklet_init(&instance->isr_tasklet, instance->instancet->tasklet,
(unsigned long)instance);
diff --git a/drivers/soc/qcom/bg_rsb.c b/drivers/soc/qcom/bg_rsb.c
index 1b5830a..4cd4638 100644
--- a/drivers/soc/qcom/bg_rsb.c
+++ b/drivers/soc/qcom/bg_rsb.c
@@ -135,6 +135,8 @@
bool calibration_needed;
bool is_calibrd;
+
+ bool is_cnfgrd;
};
static void *bgrsb_drv;
@@ -415,6 +417,7 @@
pr_err("Failed to unvote LDO-11 on BG down\n");
}
+ dev->is_cnfgrd = false;
pr_info("RSB current state is : %d\n", dev->bgrsb_current_state);
if (dev->bgrsb_current_state == BGRSB_STATE_INIT) {
@@ -452,6 +455,9 @@
else
pr_err("Failed to unvote LDO-11 on BG Glink down\n");
}
+
+ dev->is_cnfgrd = false;
+
if (dev->handle)
glink_close(dev->handle);
dev->handle = NULL;
@@ -562,6 +568,8 @@
dev->bgrsb_current_state = BGRSB_STATE_INIT;
return;
}
+
+ dev->is_cnfgrd = true;
dev->bgrsb_current_state = BGRSB_STATE_RSB_CONFIGURED;
pr_debug("RSB Cofigured\n");
}
@@ -592,6 +600,7 @@
dev->bgrsb_current_state = BGRSB_STATE_INIT;
return;
}
+ dev->is_cnfgrd = true;
dev->bgrsb_current_state = BGRSB_STATE_RSB_CONFIGURED;
pr_debug("Glink RSB Cofigured\n");
}
@@ -715,6 +724,11 @@
container_of(work, struct bgrsb_priv,
rsb_calibration_work);
+ if (!dev->is_cnfgrd) {
+ pr_err("RSB is not configured\n");
+ return;
+ }
+
req.cmd_id = 0x03;
req.data = dev->calbrtion_cpi;
@@ -744,6 +758,11 @@
container_of(work, struct bgrsb_priv,
bttn_configr_work);
+ if (!dev->is_cnfgrd) {
+ pr_err("RSB is not configured\n");
+ return;
+ }
+
req.cmd_id = 0x05;
req.data = dev->bttn_configs;
@@ -993,7 +1012,8 @@
goto ret_success;
if (dev->bgrsb_current_state == BGRSB_STATE_INIT) {
- if (bgrsb_ldo_work(dev, BGRSB_ENABLE_LDO11) == 0) {
+ if (dev->is_cnfgrd &&
+ bgrsb_ldo_work(dev, BGRSB_ENABLE_LDO11) == 0) {
dev->bgrsb_current_state = BGRSB_STATE_RSB_CONFIGURED;
pr_debug("RSB Cofigured\n");
goto ret_success;
diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c
index a816f07..093c9cf 100644
--- a/drivers/spi/spi-rspi.c
+++ b/drivers/spi/spi-rspi.c
@@ -597,11 +597,13 @@
ret = wait_event_interruptible_timeout(rspi->wait,
rspi->dma_callbacked, HZ);
- if (ret > 0 && rspi->dma_callbacked)
+ if (ret > 0 && rspi->dma_callbacked) {
ret = 0;
- else if (!ret) {
- dev_err(&rspi->master->dev, "DMA timeout\n");
- ret = -ETIMEDOUT;
+ } else {
+ if (!ret) {
+ dev_err(&rspi->master->dev, "DMA timeout\n");
+ ret = -ETIMEDOUT;
+ }
if (tx)
dmaengine_terminate_all(rspi->master->dma_tx);
if (rx)
@@ -1313,12 +1315,36 @@
MODULE_DEVICE_TABLE(platform, spi_driver_ids);
+#ifdef CONFIG_PM_SLEEP
+static int rspi_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct rspi_data *rspi = platform_get_drvdata(pdev);
+
+ return spi_master_suspend(rspi->master);
+}
+
+static int rspi_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct rspi_data *rspi = platform_get_drvdata(pdev);
+
+ return spi_master_resume(rspi->master);
+}
+
+static SIMPLE_DEV_PM_OPS(rspi_pm_ops, rspi_suspend, rspi_resume);
+#define DEV_PM_OPS &rspi_pm_ops
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver rspi_driver = {
.probe = rspi_probe,
.remove = rspi_remove,
.id_table = spi_driver_ids,
.driver = {
.name = "renesas_spi",
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(rspi_of_match),
},
};
diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index cbf02eb..711ea52 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -373,7 +373,8 @@
static void sh_msiof_reset_str(struct sh_msiof_spi_priv *p)
{
- sh_msiof_write(p, STR, sh_msiof_read(p, STR));
+ sh_msiof_write(p, STR,
+ sh_msiof_read(p, STR) & ~(STR_TDREQ | STR_RDREQ));
}
static void sh_msiof_spi_write_fifo_8(struct sh_msiof_spi_priv *p,
@@ -1275,12 +1276,37 @@
};
MODULE_DEVICE_TABLE(platform, spi_driver_ids);
+#ifdef CONFIG_PM_SLEEP
+static int sh_msiof_spi_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sh_msiof_spi_priv *p = platform_get_drvdata(pdev);
+
+ return spi_master_suspend(p->master);
+}
+
+static int sh_msiof_spi_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sh_msiof_spi_priv *p = platform_get_drvdata(pdev);
+
+ return spi_master_resume(p->master);
+}
+
+static SIMPLE_DEV_PM_OPS(sh_msiof_spi_pm_ops, sh_msiof_spi_suspend,
+ sh_msiof_spi_resume);
+#define DEV_PM_OPS &sh_msiof_spi_pm_ops
+#else
+#define DEV_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
static struct platform_driver sh_msiof_spi_drv = {
.probe = sh_msiof_spi_probe,
.remove = sh_msiof_spi_remove,
.id_table = spi_driver_ids,
.driver = {
.name = "spi_sh_msiof",
+ .pm = DEV_PM_OPS,
.of_match_table = of_match_ptr(sh_msiof_match),
},
};
diff --git a/drivers/spi/spi-tegra20-slink.c b/drivers/spi/spi-tegra20-slink.c
index 85c91f5..af2880d 100644
--- a/drivers/spi/spi-tegra20-slink.c
+++ b/drivers/spi/spi-tegra20-slink.c
@@ -1063,6 +1063,24 @@
goto exit_free_master;
}
+ /* disabled clock may cause interrupt storm upon request */
+ tspi->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(tspi->clk)) {
+ ret = PTR_ERR(tspi->clk);
+ dev_err(&pdev->dev, "Can not get clock %d\n", ret);
+ goto exit_free_master;
+ }
+ ret = clk_prepare(tspi->clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Clock prepare failed %d\n", ret);
+ goto exit_free_master;
+ }
+ ret = clk_enable(tspi->clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Clock enable failed %d\n", ret);
+ goto exit_free_master;
+ }
+
spi_irq = platform_get_irq(pdev, 0);
tspi->irq = spi_irq;
ret = request_threaded_irq(tspi->irq, tegra_slink_isr,
@@ -1071,14 +1089,7 @@
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register ISR for IRQ %d\n",
tspi->irq);
- goto exit_free_master;
- }
-
- tspi->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(tspi->clk)) {
- dev_err(&pdev->dev, "can not get clock\n");
- ret = PTR_ERR(tspi->clk);
- goto exit_free_irq;
+ goto exit_clk_disable;
}
tspi->rst = devm_reset_control_get(&pdev->dev, "spi");
@@ -1138,6 +1149,8 @@
tegra_slink_deinit_dma_param(tspi, true);
exit_free_irq:
free_irq(spi_irq, tspi);
+exit_clk_disable:
+ clk_disable(tspi->clk);
exit_free_master:
spi_master_put(master);
return ret;
@@ -1150,6 +1163,8 @@
free_irq(tspi->irq, tspi);
+ clk_disable(tspi->clk);
+
if (tspi->tx_dma_chan)
tegra_slink_deinit_dma_param(tspi, false);
diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c
index f4ffac4..5af176b 100644
--- a/drivers/staging/android/ashmem.c
+++ b/drivers/staging/android/ashmem.c
@@ -383,6 +383,12 @@
goto out;
}
+ /* requested mapping size larger than object size */
+ if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
/* requested protection bits must match our allowed protection mask */
if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
calc_vm_prot_bits(PROT_MASK, 0))) {
diff --git a/drivers/staging/rts5208/sd.c b/drivers/staging/rts5208/sd.c
index b0bbb36..9e63bdf 100644
--- a/drivers/staging/rts5208/sd.c
+++ b/drivers/staging/rts5208/sd.c
@@ -4976,7 +4976,7 @@
goto SD_Execute_Write_Cmd_Failed;
}
- rtsx_write_register(chip, SD_BYTE_CNT_L, 0xFF, 0x00);
+ retval = rtsx_write_register(chip, SD_BYTE_CNT_L, 0xFF, 0x00);
if (retval != STATUS_SUCCESS) {
rtsx_trace(chip);
goto SD_Execute_Write_Cmd_Failed;
diff --git a/drivers/target/iscsi/iscsi_target_auth.c b/drivers/target/iscsi/iscsi_target_auth.c
index 98f75e5..f0d9730 100644
--- a/drivers/target/iscsi/iscsi_target_auth.c
+++ b/drivers/target/iscsi/iscsi_target_auth.c
@@ -26,27 +26,6 @@
#include "iscsi_target_nego.h"
#include "iscsi_target_auth.h"
-static int chap_string_to_hex(unsigned char *dst, unsigned char *src, int len)
-{
- int j = DIV_ROUND_UP(len, 2), rc;
-
- rc = hex2bin(dst, src, j);
- if (rc < 0)
- pr_debug("CHAP string contains non hex digit symbols\n");
-
- dst[j] = '\0';
- return j;
-}
-
-static void chap_binaryhex_to_asciihex(char *dst, char *src, int src_len)
-{
- int i;
-
- for (i = 0; i < src_len; i++) {
- sprintf(&dst[i*2], "%02x", (int) src[i] & 0xff);
- }
-}
-
static void chap_gen_challenge(
struct iscsi_conn *conn,
int caller,
@@ -59,7 +38,7 @@
memset(challenge_asciihex, 0, CHAP_CHALLENGE_LENGTH * 2 + 1);
get_random_bytes(chap->challenge, CHAP_CHALLENGE_LENGTH);
- chap_binaryhex_to_asciihex(challenge_asciihex, chap->challenge,
+ bin2hex(challenge_asciihex, chap->challenge,
CHAP_CHALLENGE_LENGTH);
/*
* Set CHAP_C, and copy the generated challenge into c_str.
@@ -240,9 +219,16 @@
pr_err("Could not find CHAP_R.\n");
goto out;
}
+ if (strlen(chap_r) != MD5_SIGNATURE_SIZE * 2) {
+ pr_err("Malformed CHAP_R\n");
+ goto out;
+ }
+ if (hex2bin(client_digest, chap_r, MD5_SIGNATURE_SIZE) < 0) {
+ pr_err("Malformed CHAP_R\n");
+ goto out;
+ }
pr_debug("[server] Got CHAP_R=%s\n", chap_r);
- chap_string_to_hex(client_digest, chap_r, strlen(chap_r));
tfm = crypto_alloc_shash("md5", 0, 0);
if (IS_ERR(tfm)) {
@@ -286,7 +272,7 @@
goto out;
}
- chap_binaryhex_to_asciihex(response, server_digest, MD5_SIGNATURE_SIZE);
+ bin2hex(response, server_digest, MD5_SIGNATURE_SIZE);
pr_debug("[server] MD5 Server Digest: %s\n", response);
if (memcmp(server_digest, client_digest, MD5_SIGNATURE_SIZE) != 0) {
@@ -341,9 +327,7 @@
pr_err("Could not find CHAP_C.\n");
goto out;
}
- pr_debug("[server] Got CHAP_C=%s\n", challenge);
- challenge_len = chap_string_to_hex(challenge_binhex, challenge,
- strlen(challenge));
+ challenge_len = DIV_ROUND_UP(strlen(challenge), 2);
if (!challenge_len) {
pr_err("Unable to convert incoming challenge\n");
goto out;
@@ -352,6 +336,11 @@
pr_err("CHAP_C exceeds maximum binary size of 1024 bytes\n");
goto out;
}
+ if (hex2bin(challenge_binhex, challenge, challenge_len) < 0) {
+ pr_err("Malformed CHAP_C\n");
+ goto out;
+ }
+ pr_debug("[server] Got CHAP_C=%s\n", challenge);
/*
* During mutual authentication, the CHAP_C generated by the
* initiator must not match the original CHAP_C generated by
@@ -405,7 +394,7 @@
/*
* Convert response from binary hex to ascii hext.
*/
- chap_binaryhex_to_asciihex(response, digest, MD5_SIGNATURE_SIZE);
+ bin2hex(response, digest, MD5_SIGNATURE_SIZE);
*nr_out_len += sprintf(nr_out_ptr + *nr_out_len, "CHAP_R=0x%s",
response);
*nr_out_len += 1;
diff --git a/drivers/target/iscsi/iscsi_target_tpg.c b/drivers/target/iscsi/iscsi_target_tpg.c
index 63e1dcc..761b065 100644
--- a/drivers/target/iscsi/iscsi_target_tpg.c
+++ b/drivers/target/iscsi/iscsi_target_tpg.c
@@ -637,8 +637,7 @@
none = strstr(buf1, NONE);
if (none)
goto out;
- strncat(buf1, ",", strlen(","));
- strncat(buf1, NONE, strlen(NONE));
+ strlcat(buf1, "," NONE, sizeof(buf1));
if (iscsi_update_param_value(param, buf1) < 0)
return -EINVAL;
}
diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c
index c662cd7..fe811d7 100644
--- a/drivers/thermal/of-thermal.c
+++ b/drivers/thermal/of-thermal.c
@@ -396,10 +396,13 @@
mutex_lock(&tz->lock);
- if (mode == THERMAL_DEVICE_ENABLED)
+ if (mode == THERMAL_DEVICE_ENABLED) {
tz->polling_delay = data->polling_delay;
- else
+ tz->passive_delay = data->passive_delay;
+ } else {
tz->polling_delay = 0;
+ tz->passive_delay = 0;
+ }
mutex_unlock(&tz->lock);
diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 9510305..741e966 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -41,6 +41,9 @@
If unsure, say Y, or else you won't be able to do much with your new
shiny Linux system :-)
+config TTY_FLUSH_LOCAL_ECHO
+ bool
+
config CONSOLE_TRANSLATIONS
depends on VT
default y
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 0475f96..442a3130 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -128,6 +128,10 @@
#define MASK(x) ((x) & (N_TTY_BUF_SIZE - 1))
+#if defined(CONFIG_TTY_FLUSH_LOCAL_ECHO)
+static void continue_process_echoes(struct work_struct *work);
+#endif
+
static inline size_t read_cnt(struct n_tty_data *ldata)
{
return ldata->read_head - ldata->read_tail;
@@ -751,6 +755,16 @@
tail++;
}
+#if defined(CONFIG_TTY_FLUSH_LOCAL_ECHO)
+ if (ldata->echo_commit != tail) {
+ if (!tty->delayed_work) {
+ INIT_DELAYED_WORK(&tty->echo_delayed_work, continue_process_echoes);
+ schedule_delayed_work(&tty->echo_delayed_work, 1);
+ }
+ tty->delayed_work = 1;
+ }
+#endif
+
not_yet_stored:
ldata->echo_tail = tail;
return old_space - space;
@@ -817,6 +831,20 @@
mutex_unlock(&ldata->output_lock);
}
+#if defined(CONFIG_TTY_FLUSH_LOCAL_ECHO)
+static void continue_process_echoes(struct work_struct *work)
+{
+ struct tty_struct *tty =
+ container_of(work, struct tty_struct, echo_delayed_work.work);
+ struct n_tty_data *ldata = tty->disc_data;
+
+ mutex_lock(&ldata->output_lock);
+ tty->delayed_work = 0;
+ __process_echoes(tty);
+ mutex_unlock(&ldata->output_lock);
+}
+#endif
+
/**
* add_echo_byte - add a byte to the echo buffer
* @c: unicode byte to echo
diff --git a/drivers/tty/serial/8250/serial_cs.c b/drivers/tty/serial/8250/serial_cs.c
index 933c268..8106353 100644
--- a/drivers/tty/serial/8250/serial_cs.c
+++ b/drivers/tty/serial/8250/serial_cs.c
@@ -637,8 +637,10 @@
(link->has_func_id) &&
(link->socket->pcmcia_pfc == 0) &&
((link->func_id == CISTPL_FUNCID_MULTI) ||
- (link->func_id == CISTPL_FUNCID_SERIAL)))
- pcmcia_loop_config(link, serial_check_for_multi, info);
+ (link->func_id == CISTPL_FUNCID_SERIAL))) {
+ if (pcmcia_loop_config(link, serial_check_for_multi, info))
+ goto failed;
+ }
/*
* Apply any multi-port quirk.
diff --git a/drivers/tty/serial/cpm_uart/cpm_uart_core.c b/drivers/tty/serial/cpm_uart/cpm_uart_core.c
index d3e3d42..0040c29f 100644
--- a/drivers/tty/serial/cpm_uart/cpm_uart_core.c
+++ b/drivers/tty/serial/cpm_uart/cpm_uart_core.c
@@ -1068,8 +1068,8 @@
/* Get the address of the host memory buffer.
*/
bdp = pinfo->rx_cur;
- while (bdp->cbd_sc & BD_SC_EMPTY)
- ;
+ if (bdp->cbd_sc & BD_SC_EMPTY)
+ return NO_POLL_CHAR;
/* If the buffer address is in the CPM DPRAM, don't
* convert it.
@@ -1104,7 +1104,11 @@
poll_chars = 0;
}
if (poll_chars <= 0) {
- poll_chars = poll_wait_key(poll_buf, pinfo);
+ int ret = poll_wait_key(poll_buf, pinfo);
+
+ if (ret == NO_POLL_CHAR)
+ return ret;
+ poll_chars = ret;
pollp = poll_buf;
}
poll_chars--;
diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c
index 937f5e1..e2ec049 100644
--- a/drivers/tty/serial/fsl_lpuart.c
+++ b/drivers/tty/serial/fsl_lpuart.c
@@ -833,7 +833,8 @@
struct circ_buf *ring = &sport->rx_ring;
int ret, nent;
int bits, baud;
- struct tty_struct *tty = tty_port_tty_get(&sport->port.state->port);
+ struct tty_port *port = &sport->port.state->port;
+ struct tty_struct *tty = port->tty;
struct ktermios *termios = &tty->termios;
baud = tty_get_baud_rate(tty);
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c
index b24edf6..0d82be1 100644
--- a/drivers/tty/serial/imx.c
+++ b/drivers/tty/serial/imx.c
@@ -2197,6 +2197,14 @@
ret);
return ret;
}
+
+ ret = devm_request_irq(&pdev->dev, rtsirq, imx_rtsint, 0,
+ dev_name(&pdev->dev), sport);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request rts irq: %d\n",
+ ret);
+ return ret;
+ }
} else {
ret = devm_request_irq(&pdev->dev, rxirq, imx_int, 0,
dev_name(&pdev->dev), sport);
diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c
index 45b57c2..401c983 100644
--- a/drivers/tty/serial/mvebu-uart.c
+++ b/drivers/tty/serial/mvebu-uart.c
@@ -327,8 +327,10 @@
if ((termios->c_cflag & CREAD) == 0)
port->ignore_status_mask |= STAT_RX_RDY | STAT_BRK_ERR;
- if (old)
+ if (old) {
tty_termios_copy_hw(termios, old);
+ termios->c_cflag |= CS8;
+ }
baud = uart_get_baud_rate(port, termios, old, 0, 460800);
uart_update_timeout(port, termios->c_cflag, baud);
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 789c814..e6429d4 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -1475,6 +1475,7 @@
static int tty_reopen(struct tty_struct *tty)
{
struct tty_driver *driver = tty->driver;
+ int retval;
if (driver->type == TTY_DRIVER_TYPE_PTY &&
driver->subtype == PTY_TYPE_MASTER)
@@ -1488,10 +1489,14 @@
tty->count++;
- if (!tty->ldisc)
- return tty_ldisc_reinit(tty, tty->termios.c_line);
+ if (tty->ldisc)
+ return 0;
- return 0;
+ retval = tty_ldisc_reinit(tty, tty->termios.c_line);
+ if (retval)
+ tty->count--;
+
+ return retval;
}
/**
diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c
index f62c598..638eb9b 100644
--- a/drivers/tty/vt/vt_ioctl.c
+++ b/drivers/tty/vt/vt_ioctl.c
@@ -31,6 +31,8 @@
#include <asm/io.h>
#include <asm/uaccess.h>
+#include <linux/nospec.h>
+
#include <linux/kbd_kern.h>
#include <linux/vt_kern.h>
#include <linux/kbd_diacr.h>
@@ -703,6 +705,8 @@
if (vsa.console == 0 || vsa.console > MAX_NR_CONSOLES)
ret = -ENXIO;
else {
+ vsa.console = array_index_nospec(vsa.console,
+ MAX_NR_CONSOLES + 1);
vsa.console--;
console_lock();
ret = vc_allocate(vsa.console);
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
index adc0f78..9f00165 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -470,7 +470,7 @@
set_bit(WDM_RESPONDING, &desc->flags);
spin_unlock_irq(&desc->iuspin);
- rv = usb_submit_urb(desc->response, GFP_ATOMIC);
+ rv = usb_submit_urb(desc->response, GFP_KERNEL);
spin_lock_irq(&desc->iuspin);
if (rv) {
dev_err(&desc->intf->dev,
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index 893ebae..988240e 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -1450,10 +1450,13 @@
struct async *as = NULL;
struct usb_ctrlrequest *dr = NULL;
unsigned int u, totlen, isofrmlen;
- int i, ret, is_in, num_sgs = 0, ifnum = -1;
+ int i, ret, num_sgs = 0, ifnum = -1;
int number_of_packets = 0;
unsigned int stream_id = 0;
void *buf;
+ bool is_in;
+ bool allow_short = false;
+ bool allow_zero = false;
unsigned long mask = USBDEVFS_URB_SHORT_NOT_OK |
USBDEVFS_URB_BULK_CONTINUATION |
USBDEVFS_URB_NO_FSBR |
@@ -1487,6 +1490,8 @@
u = 0;
switch (uurb->type) {
case USBDEVFS_URB_TYPE_CONTROL:
+ if (is_in)
+ allow_short = true;
if (!usb_endpoint_xfer_control(&ep->desc))
return -EINVAL;
/* min 8 byte setup packet */
@@ -1527,6 +1532,10 @@
break;
case USBDEVFS_URB_TYPE_BULK:
+ if (!is_in)
+ allow_zero = true;
+ else
+ allow_short = true;
switch (usb_endpoint_type(&ep->desc)) {
case USB_ENDPOINT_XFER_CONTROL:
case USB_ENDPOINT_XFER_ISOC:
@@ -1547,6 +1556,10 @@
if (!usb_endpoint_xfer_int(&ep->desc))
return -EINVAL;
interrupt_urb:
+ if (!is_in)
+ allow_zero = true;
+ else
+ allow_short = true;
break;
case USBDEVFS_URB_TYPE_ISO:
@@ -1691,16 +1704,21 @@
u = (is_in ? URB_DIR_IN : URB_DIR_OUT);
if (uurb->flags & USBDEVFS_URB_ISO_ASAP)
u |= URB_ISO_ASAP;
- if (uurb->flags & USBDEVFS_URB_SHORT_NOT_OK && is_in)
+ if (allow_short && uurb->flags & USBDEVFS_URB_SHORT_NOT_OK)
u |= URB_SHORT_NOT_OK;
if (uurb->flags & USBDEVFS_URB_NO_FSBR)
u |= URB_NO_FSBR;
- if (uurb->flags & USBDEVFS_URB_ZERO_PACKET)
+ if (allow_zero && uurb->flags & USBDEVFS_URB_ZERO_PACKET)
u |= URB_ZERO_PACKET;
if (uurb->flags & USBDEVFS_URB_NO_INTERRUPT)
u |= URB_NO_INTERRUPT;
as->urb->transfer_flags = u;
+ if (!allow_short && uurb->flags & USBDEVFS_URB_SHORT_NOT_OK)
+ dev_warn(&ps->dev->dev, "Requested nonsensical USBDEVFS_URB_SHORT_NOT_OK.\n");
+ if (!allow_zero && uurb->flags & USBDEVFS_URB_ZERO_PACKET)
+ dev_warn(&ps->dev->dev, "Requested nonsensical USBDEVFS_URB_ZERO_PACKET.\n");
+
as->urb->transfer_buffer_length = uurb->buffer_length;
as->urb->setup_packet = (unsigned char *)dr;
dr = NULL;
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 5532246..7dae981 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -509,7 +509,6 @@
struct device *dev;
struct usb_device *udev;
int retval = 0;
- int lpm_disable_error = -ENODEV;
if (!iface)
return -ENODEV;
@@ -530,16 +529,6 @@
iface->condition = USB_INTERFACE_BOUND;
- /* See the comment about disabling LPM in usb_probe_interface(). */
- if (driver->disable_hub_initiated_lpm) {
- lpm_disable_error = usb_unlocked_disable_lpm(udev);
- if (lpm_disable_error) {
- dev_err(&iface->dev, "%s Failed to disable LPM for driver %s\n.",
- __func__, driver->name);
- return -ENOMEM;
- }
- }
-
/* Claimed interfaces are initially inactive (suspended) and
* runtime-PM-enabled, but only if the driver has autosuspend
* support. Otherwise they are marked active, to prevent the
@@ -558,9 +547,20 @@
if (device_is_registered(dev))
retval = device_bind_driver(dev);
- /* Attempt to re-enable USB3 LPM, if the disable was successful. */
- if (!lpm_disable_error)
- usb_unlocked_enable_lpm(udev);
+ if (retval) {
+ dev->driver = NULL;
+ usb_set_intfdata(iface, NULL);
+ iface->needs_remote_wakeup = 0;
+ iface->condition = USB_INTERFACE_UNBOUND;
+
+ /*
+ * Unbound interfaces are always runtime-PM-disabled
+ * and runtime-PM-suspended
+ */
+ if (driver->supports_autosuspend)
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ }
return retval;
}
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index bb2a4fe..82dfc60 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -91,6 +91,8 @@
struct usb_interface_cache *intf_cache = NULL;
int i;
+ if (!config)
+ return NULL;
for (i = 0; i < config->desc.bNumInterfaces; i++) {
if (config->intf_cache[i]->altsetting[0].desc.bInterfaceNumber
== iface_num) {
diff --git a/drivers/usb/gadget/udc/fotg210-udc.c b/drivers/usb/gadget/udc/fotg210-udc.c
index 6ba122c..95df2b3 100644
--- a/drivers/usb/gadget/udc/fotg210-udc.c
+++ b/drivers/usb/gadget/udc/fotg210-udc.c
@@ -1066,12 +1066,15 @@
static int fotg210_udc_remove(struct platform_device *pdev)
{
struct fotg210_udc *fotg210 = platform_get_drvdata(pdev);
+ int i;
usb_del_gadget_udc(&fotg210->gadget);
iounmap(fotg210->reg);
free_irq(platform_get_irq(pdev, 0), fotg210);
fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req);
+ for (i = 0; i < FOTG210_MAX_NUM_EP; i++)
+ kfree(fotg210->ep[i]);
kfree(fotg210);
return 0;
@@ -1102,7 +1105,7 @@
/* initialize udc */
fotg210 = kzalloc(sizeof(struct fotg210_udc), GFP_KERNEL);
if (fotg210 == NULL)
- goto err_alloc;
+ goto err;
for (i = 0; i < FOTG210_MAX_NUM_EP; i++) {
_ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL);
@@ -1114,7 +1117,7 @@
fotg210->reg = ioremap(res->start, resource_size(res));
if (fotg210->reg == NULL) {
pr_err("ioremap error.\n");
- goto err_map;
+ goto err_alloc;
}
spin_lock_init(&fotg210->lock);
@@ -1162,7 +1165,7 @@
fotg210->ep0_req = fotg210_ep_alloc_request(&fotg210->ep[0]->ep,
GFP_KERNEL);
if (fotg210->ep0_req == NULL)
- goto err_req;
+ goto err_map;
fotg210_init(fotg210);
@@ -1190,12 +1193,14 @@
fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req);
err_map:
- if (fotg210->reg)
- iounmap(fotg210->reg);
+ iounmap(fotg210->reg);
err_alloc:
+ for (i = 0; i < FOTG210_MAX_NUM_EP; i++)
+ kfree(fotg210->ep[i]);
kfree(fotg210);
+err:
return ret;
}
diff --git a/drivers/usb/host/xhci-mtk.c b/drivers/usb/host/xhci-mtk.c
index ce9e457..c108758 100644
--- a/drivers/usb/host/xhci-mtk.c
+++ b/drivers/usb/host/xhci-mtk.c
@@ -735,10 +735,10 @@
xhci_mtk_host_enable(mtk);
xhci_dbg(xhci, "%s: restart port polling\n", __func__);
- set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
- usb_hcd_poll_rh_status(hcd);
set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
usb_hcd_poll_rh_status(xhci->shared_hcd);
+ set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
+ usb_hcd_poll_rh_status(hcd);
return 0;
}
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index f6782a3..b514055 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -179,6 +179,8 @@
}
if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
(pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI ||
+ pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI ||
+ pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_XHCI ||
pdev->device == PCI_DEVICE_ID_INTEL_APL_XHCI ||
pdev->device == PCI_DEVICE_ID_INTEL_DNV_XHCI))
xhci->quirks |= XHCI_MISSING_CAS;
diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c
index e36c6c6..1e67234 100644
--- a/drivers/usb/misc/yurex.c
+++ b/drivers/usb/misc/yurex.c
@@ -423,6 +423,9 @@
spin_unlock_irqrestore(&dev->lock, flags);
mutex_unlock(&dev->io_mutex);
+ if (WARN_ON_ONCE(len >= sizeof(in_buffer)))
+ return -EIO;
+
return simple_read_from_buffer(buffer, count, ppos, in_buffer, len);
}
diff --git a/drivers/usb/serial/kobil_sct.c b/drivers/usb/serial/kobil_sct.c
index 813035f..7d25267 100644
--- a/drivers/usb/serial/kobil_sct.c
+++ b/drivers/usb/serial/kobil_sct.c
@@ -408,12 +408,20 @@
transfer_buffer_length,
KOBIL_TIMEOUT);
- dev_dbg(&port->dev, "%s - Send get_status_line_state URB returns: %i. Statusline: %02x\n",
- __func__, result, transfer_buffer[0]);
+ dev_dbg(&port->dev, "Send get_status_line_state URB returns: %i\n",
+ result);
+ if (result < 1) {
+ if (result >= 0)
+ result = -EIO;
+ goto out_free;
+ }
+
+ dev_dbg(&port->dev, "Statusline: %02x\n", transfer_buffer[0]);
result = 0;
if ((transfer_buffer[0] & SUSBCR_GSL_DSR) != 0)
result = TIOCM_DSR;
+out_free:
kfree(transfer_buffer);
return result;
}
diff --git a/drivers/usb/serial/usb-serial-simple.c b/drivers/usb/serial/usb-serial-simple.c
index 2674da4..6d6acf2 100644
--- a/drivers/usb/serial/usb-serial-simple.c
+++ b/drivers/usb/serial/usb-serial-simple.c
@@ -87,7 +87,8 @@
/* Motorola Tetra driver */
#define MOTOROLA_TETRA_IDS() \
- { USB_DEVICE(0x0cad, 0x9011) } /* Motorola Solutions TETRA PEI */
+ { USB_DEVICE(0x0cad, 0x9011) }, /* Motorola Solutions TETRA PEI */ \
+ { USB_DEVICE(0x0cad, 0x9012) } /* MTP6550 */
DEVICE(motorola_tetra, MOTOROLA_TETRA_IDS);
/* Novatel Wireless GPS driver */
diff --git a/drivers/usb/wusbcore/security.c b/drivers/usb/wusbcore/security.c
index 8c9421b..6bf86ca 100644
--- a/drivers/usb/wusbcore/security.c
+++ b/drivers/usb/wusbcore/security.c
@@ -230,7 +230,7 @@
result = usb_get_descriptor(usb_dev, USB_DT_SECURITY,
0, secd, sizeof(*secd));
- if (result < sizeof(*secd)) {
+ if (result < (int)sizeof(*secd)) {
dev_err(dev, "Can't read security descriptor or "
"not enough data: %d\n", result);
goto out;
diff --git a/drivers/uwb/hwa-rc.c b/drivers/uwb/hwa-rc.c
index 9a53912..5d3ba74 100644
--- a/drivers/uwb/hwa-rc.c
+++ b/drivers/uwb/hwa-rc.c
@@ -873,6 +873,7 @@
error_rc_add:
usb_put_intf(iface);
usb_put_dev(hwarc->usb_dev);
+ kfree(hwarc);
error_alloc:
uwb_rc_put(uwb_rc);
error_rc_alloc:
diff --git a/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c b/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c
index ef69273..a3edb20 100644
--- a/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c
+++ b/drivers/video/fbdev/omap2/omapfb/omapfb-ioctl.c
@@ -496,6 +496,9 @@
if (!access_ok(VERIFY_WRITE, mr->buffer, mr->buffer_size))
return -EFAULT;
+ if (mr->w > 4096 || mr->h > 4096)
+ return -EINVAL;
+
if (mr->w * mr->h * 3 > mr->buffer_size)
return -EINVAL;
@@ -509,7 +512,7 @@
mr->x, mr->y, mr->w, mr->h);
if (r > 0) {
- if (copy_to_user(mr->buffer, buf, mr->buffer_size))
+ if (copy_to_user(mr->buffer, buf, r))
r = -EFAULT;
}
diff --git a/drivers/vservices/Kconfig b/drivers/vservices/Kconfig
new file mode 100644
index 0000000..16b3bda
--- /dev/null
+++ b/drivers/vservices/Kconfig
@@ -0,0 +1,81 @@
+#
+# OKL4 Virtual Services framework
+#
+
+menuconfig VSERVICES_SUPPORT
+ tristate "OKL4 Virtual Services support"
+ default OKL4_GUEST || OKL4_VIRTUALISATION
+ select HOTPLUG
+ help
+ This option adds core support for OKL4 Virtual Services. The Virtual
+ Services framework is an inter-OS device/service sharing
+ protocol which is supported on OKL4 Microvisor virtualization
+ platforms. You will also need drivers from the following menu in
+ order to make use of it.
+
+if VSERVICES_SUPPORT
+
+config VSERVICES_CHAR_DEV
+ bool "Virtual Services user-space service API"
+ default y
+ help
+ Select this if you want to use user-space service drivers. You will
+ also need udev rules that create device nodes, and protocol code
+ generated by the OK Mill tool.
+
+config VSERVICES_DEBUG
+ bool "Virtual Services debugging support"
+ help
+ Select this if you want to enable Virtual Services core framework
+ debugging. The debug messages for various components of the Virtual
+ Services core framework can be toggled at runtime on a per-session
+ basis via sysfs. When Virtual Services debugging is enabled here,
+ but disabled at runtime it has a minimal performance impact.
+
+config VSERVICES_LOCK_DEBUG
+ bool "Debug Virtual Services state locks"
+ default DEBUG_KERNEL
+ help
+ This option enables some runtime checks that Virtual Services
+ state lock functions are used correctly in service drivers.
+
+config VSERVICES_SERVER
+ tristate "Virtual Services server support"
+ depends on SYSFS
+ default y
+ help
+ This option adds support for Virtual Services servers, which allows
+ exporting of services from this Linux to other environments. Servers
+ are created at runtime by writing to files in
+ /sys/bus/vservices-server.
+
+config VSERVICES_CLIENT
+ tristate "Virtual Services client support"
+ default y
+ help
+ This option adds support for Virtual Services clients, which allows
+ connecting to services exported from other environments.
+
+config VSERVICES_SKELETON_DRIVER
+ tristate "Virtual Services skeleton driver"
+ depends on VSERVICES_SERVER || VSERVICES_CLIENT
+ default n
+ help
+ This option adds support for a skeleton virtual service driver. This
+ driver can be used for templating or testing of virtual service
+ drivers. If unsure say N.
+
+config VSERVICES_NAMED_DEVICE
+ bool "Virtual Services use named device node in /dev"
+ default n
+ help
+ Select this if you want to use a named device name over a numeric
+ device name in /dev
+
+source "drivers/vservices/transport/Kconfig"
+
+source "drivers/vservices/protocol/Kconfig"
+
+source "drivers/vservices/Kconfig.stacks"
+
+endif # VSERVICES_SUPPORT
diff --git a/drivers/vservices/Kconfig.stacks b/drivers/vservices/Kconfig.stacks
new file mode 100644
index 0000000..97eba53
--- /dev/null
+++ b/drivers/vservices/Kconfig.stacks
@@ -0,0 +1,7 @@
+#
+# vServices drivers configuration
+#
+
+menu "Client and Server drivers"
+
+endmenu
diff --git a/drivers/vservices/Makefile b/drivers/vservices/Makefile
new file mode 100644
index 0000000..4ce9e48
--- /dev/null
+++ b/drivers/vservices/Makefile
@@ -0,0 +1,14 @@
+ccflags-y += -Werror
+ccflags-$(CONFIG_VSERVICES_DEBUG) += -DDEBUG
+
+obj-$(CONFIG_VSERVICES_SUPPORT) += vservices.o
+vservices-objs-$(CONFIG_VSERVICES_CHAR_DEV) += devio.o
+vservices-objs = session.o $(vservices-objs-y)
+
+obj-$(CONFIG_VSERVICES_CLIENT) += core_client.o
+obj-$(CONFIG_VSERVICES_SERVER) += core_server.o
+
+obj-$(CONFIG_VSERVICES_SKELETON_DRIVER) += vservices_skeleton_driver.o
+vservices_skeleton_driver-objs = skeleton_driver.o
+
+obj-$(CONFIG_VSERVICES_SUPPORT) += protocol/
diff --git a/drivers/vservices/compat.h b/drivers/vservices/compat.h
new file mode 100644
index 0000000..5f6926d
--- /dev/null
+++ b/drivers/vservices/compat.h
@@ -0,0 +1,59 @@
+/*
+ * drivers/vservices/compat.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * Wrapper functions/definitions for compatibility between differnet kernel
+ * versions.
+ */
+
+#ifndef _VSERVICES_COMPAT_H
+#define _VSERVICES_COMPAT_H
+
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+/* The INIT_WORK_ONSTACK macro has a slightly different name in older kernels */
+#ifndef INIT_WORK_ONSTACK
+#define INIT_WORK_ONSTACK(_work, _func) INIT_WORK_ON_STACK(_work, _func)
+#endif
+
+/*
+ * We require a workqueue with no concurrency. This is provided by
+ * create_singlethread_workqueue() in kernel prior to 2.6.36.
+ * In later versions, create_singlethread_workqueue() enables WQ_MEM_RECLAIM and
+ * thus WQ_RESCUER, which allows work items to be grabbed by a rescuer thread
+ * and run concurrently if the queue is running too slowly. We must use
+ * alloc_ordered_workqueue() instead, to disable the rescuer.
+ */
+static inline struct workqueue_struct *
+vs_create_workqueue(const char *name)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
+ return create_singlethread_workqueue(name);
+#else
+ return alloc_ordered_workqueue(name, 0);
+#endif
+}
+
+/*
+ * The max3 macro has only been present from 2.6.37
+ * (commit: f27c85c56b32c42bcc54a43189c1e00fdceb23ec)
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 37)
+#define max3(x, y, z) ({ \
+ typeof(x) _max1 = (x); \
+ typeof(y) _max2 = (y); \
+ typeof(z) _max3 = (z); \
+ (void) (&_max1 == &_max2); \
+ (void) (&_max1 == &_max3); \
+ _max1 > _max2 ? (_max1 > _max3 ? _max1 : _max3) : \
+ (_max2 > _max3 ? _max2 : _max3); })
+#endif
+
+#endif /* _VSERVICES_COMPAT_H */
diff --git a/drivers/vservices/core_client.c b/drivers/vservices/core_client.c
new file mode 100644
index 0000000..4cc78ac
--- /dev/null
+++ b/drivers/vservices/core_client.c
@@ -0,0 +1,733 @@
+/*
+ * drivers/vservices/core_client.c
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * Client side core service application driver. This is responsible for:
+ *
+ * - automatically connecting to the server when it becomes ready;
+ * - sending a reset command to the server if something has gone wrong; and
+ * - enumerating all the available services.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/err.h>
+#include <linux/module.h>
+
+#include <vservices/types.h>
+#include <vservices/transport.h>
+#include <vservices/session.h>
+#include <vservices/buffer.h>
+#include <vservices/service.h>
+
+#include <vservices/protocol/core/types.h>
+#include <vservices/protocol/core/common.h>
+#include <vservices/protocol/core/client.h>
+
+#include "session.h"
+#include "transport.h"
+#include "compat.h"
+
+struct core_client {
+ struct vs_client_core_state state;
+ struct vs_service_device *service;
+
+ struct list_head message_queue;
+ struct mutex message_queue_lock;
+ struct work_struct message_queue_work;
+};
+
+struct pending_reset {
+ struct vs_service_device *service;
+ struct list_head list;
+};
+
+#define to_core_client(x) container_of(x, struct core_client, state)
+#define dev_to_core_client(x) to_core_client(dev_get_drvdata(x))
+
+static int vs_client_core_fatal_error(struct vs_client_core_state *state)
+{
+ struct core_client *client = to_core_client(state);
+
+ /* Force a transport level reset */
+ dev_err(&client->service->dev," Fatal error - resetting session\n");
+ return -EPROTO;
+}
+
+static struct core_client *
+vs_client_session_core_client(struct vs_session_device *session)
+{
+ struct vs_service_device *core_service = session->core_service;
+
+ if (!core_service)
+ return NULL;
+
+ return dev_to_core_client(&core_service->dev);
+}
+
+static ssize_t client_core_reset_service_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *core_service = to_vs_service_device(dev);
+ struct vs_session_device *session =
+ vs_service_get_session(core_service);
+ struct vs_service_device *target;
+ vs_service_id_t service_id;
+ unsigned long val;
+ int err;
+
+ /* Writing a valid service id to this file resets that service */
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ service_id = val;
+ target = vs_session_get_service(session, service_id);
+ if (!target)
+ return -ENODEV;
+
+ err = vs_service_reset(target, core_service);
+
+ vs_put_service(target);
+ return err < 0 ? err : count;
+}
+
+static DEVICE_ATTR(reset_service, S_IWUSR, NULL,
+ client_core_reset_service_store);
+
+static struct attribute *client_core_dev_attrs[] = {
+ &dev_attr_reset_service.attr,
+ NULL,
+};
+
+static const struct attribute_group client_core_attr_group = {
+ .attrs = client_core_dev_attrs,
+};
+
+/*
+ * Protocol callbacks
+ */
+static int
+vs_client_core_handle_service_removed(struct vs_client_core_state *state,
+ u32 service_id)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_session_device *session =
+ vs_service_get_session(client->service);
+ struct vs_service_device *service;
+ int ret;
+
+ service = vs_session_get_service(session, service_id);
+ if (!service)
+ return -EINVAL;
+
+ ret = vs_service_handle_delete(service);
+ vs_put_service(service);
+ return ret;
+}
+
+static int vs_client_core_create_service(struct core_client *client,
+ struct vs_session_device *session, vs_service_id_t service_id,
+ struct vs_string *protocol_name_string,
+ struct vs_string *service_name_string)
+{
+ char *protocol_name, *service_name;
+ struct vs_service_device *service;
+ int ret = 0;
+
+ protocol_name = vs_string_dup(protocol_name_string, GFP_KERNEL);
+ if (!protocol_name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ service_name = vs_string_dup(service_name_string, GFP_KERNEL);
+ if (!service_name) {
+ ret = -ENOMEM;
+ goto out_free_protocol_name;
+ }
+
+ service = vs_service_register(session, client->service, service_id,
+ protocol_name, service_name, NULL);
+ if (IS_ERR(service)) {
+ ret = PTR_ERR(service);
+ goto out_free_service_name;
+ }
+
+ vs_service_start(service);
+
+out_free_service_name:
+ kfree(service_name);
+out_free_protocol_name:
+ kfree(protocol_name);
+out:
+ return ret;
+}
+
+static int
+vs_client_core_handle_service_created(struct vs_client_core_state *state,
+ u32 service_id, struct vs_string service_name,
+ struct vs_string protocol_name, struct vs_mbuf *mbuf)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_session_device *session =
+ vs_service_get_session(client->service);
+ int err;
+
+ vs_dev_debug(VS_DEBUG_CLIENT_CORE,
+ vs_service_get_session(client->service),
+ &client->service->dev, "Service info for %d received\n",
+ service_id);
+
+ err = vs_client_core_create_service(client, session, service_id,
+ &protocol_name, &service_name);
+ if (err)
+ dev_err(&session->dev,
+ "Failed to create service with id %d: %d\n",
+ service_id, err);
+
+ vs_client_core_core_free_service_created(state, &service_name,
+ &protocol_name, mbuf);
+
+ return err;
+}
+
+static int
+vs_client_core_send_service_reset(struct core_client *client,
+ struct vs_service_device *service)
+{
+ return vs_client_core_core_send_service_reset(&client->state,
+ service->id, GFP_KERNEL);
+}
+
+static int
+vs_client_core_queue_service_reset(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ struct core_client *client =
+ vs_client_session_core_client(session);
+ struct pending_reset *msg;
+
+ if (!client)
+ return -ENODEV;
+
+ vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev,
+ "Sending reset for service %d\n", service->id);
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ mutex_lock(&client->message_queue_lock);
+
+ /* put by message_queue_work */
+ msg->service = vs_get_service(service);
+ list_add_tail(&msg->list, &client->message_queue);
+
+ mutex_unlock(&client->message_queue_lock);
+ queue_work(client->service->work_queue, &client->message_queue_work);
+
+ return 0;
+}
+
+static int vs_core_client_tx_ready(struct vs_client_core_state *state)
+{
+ struct core_client *client = to_core_client(state);
+
+ queue_work(client->service->work_queue, &client->message_queue_work);
+
+ return 0;
+}
+
+static void message_queue_work(struct work_struct *work)
+{
+ struct core_client *client = container_of(work, struct core_client,
+ message_queue_work);
+ struct vs_session_device *session =
+ vs_service_get_session(client->service);
+ struct pending_reset *msg;
+ int err;
+
+ vs_service_state_lock(client->service);
+ if (!VSERVICE_CORE_STATE_IS_CONNECTED(client->state.state.core)) {
+ vs_service_state_unlock(client->service);
+ return;
+ }
+
+ vs_dev_debug(VS_DEBUG_CLIENT, session, &session->dev, "tx_ready\n");
+
+ mutex_lock(&client->message_queue_lock);
+ while (!list_empty(&client->message_queue)) {
+ msg = list_first_entry(&client->message_queue,
+ struct pending_reset, list);
+
+ err = vs_client_core_send_service_reset(client, msg->service);
+
+ /* If we're out of quota there's no point continuing */
+ if (err == -ENOBUFS)
+ break;
+
+ /* Any other error is fatal */
+ if (err < 0) {
+ dev_err(&client->service->dev,
+ "Failed to send pending reset for %d (%d) - resetting session",
+ msg->service->id, err);
+ vs_service_reset_nosync(client->service);
+ break;
+ }
+
+ /*
+ * The message sent successfully - remove it from the queue.
+ * The corresponding vs_get_service() was done when the pending
+ * message was enqueued.
+ */
+ vs_put_service(msg->service);
+ list_del(&msg->list);
+ kfree(msg);
+ }
+ mutex_unlock(&client->message_queue_lock);
+ vs_service_state_unlock(client->service);
+}
+
+static int
+vs_client_core_handle_server_ready(struct vs_client_core_state *state,
+ u32 service_id, u32 in_quota, u32 out_quota, u32 in_bit_offset,
+ u32 in_num_bits, u32 out_bit_offset, u32 out_num_bits)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_session_device *session;
+ struct vs_service_device *service;
+ int ret;
+
+ if (service_id == 0)
+ return -EPROTO;
+
+ if (!in_quota || !out_quota)
+ return -EINVAL;
+
+ session = vs_service_get_session(client->service);
+ service = vs_session_get_service(session, service_id);
+ if (!service)
+ return -EINVAL;
+
+ service->send_quota = in_quota;
+ service->recv_quota = out_quota;
+ service->notify_send_offset = in_bit_offset;
+ service->notify_send_bits = in_num_bits;
+ service->notify_recv_offset = out_bit_offset;
+ service->notify_recv_bits = out_num_bits;
+
+ ret = vs_service_enable(service);
+ vs_put_service(service);
+ return ret;
+}
+
+static int
+vs_client_core_handle_service_reset(struct vs_client_core_state *state,
+ u32 service_id)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_session_device *session;
+
+ if (service_id == 0)
+ return -EPROTO;
+
+ session = vs_service_get_session(client->service);
+
+ return vs_service_handle_reset(session, service_id, true);
+}
+
+static void vs_core_client_start(struct vs_client_core_state *state)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_session_device *session =
+ vs_service_get_session(client->service);
+
+ /* FIXME - start callback should return int */
+ vs_dev_debug(VS_DEBUG_CLIENT_CORE, session, &client->service->dev,
+ "Core client start\n");
+}
+
+static void vs_core_client_reset(struct vs_client_core_state *state)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_session_device *session =
+ vs_service_get_session(client->service);
+ struct pending_reset *msg;
+
+ /* Flush the pending resets - we're about to delete everything */
+ while (!list_empty(&client->message_queue)) {
+ msg = list_first_entry(&client->message_queue,
+ struct pending_reset, list);
+ vs_put_service(msg->service);
+ list_del(&msg->list);
+ kfree(msg);
+ }
+
+ vs_session_delete_noncore(session);
+
+ /* Return to the initial quotas, until the next startup message */
+ client->service->send_quota = 0;
+ client->service->recv_quota = 1;
+}
+
+static int vs_core_client_startup(struct vs_client_core_state *state,
+ u32 core_in_quota, u32 core_out_quota)
+{
+ struct core_client *client = to_core_client(state);
+ struct vs_service_device *service = state->service;
+ struct vs_session_device *session = vs_service_get_session(service);
+ int ret;
+
+ if (!core_in_quota || !core_out_quota)
+ return -EINVAL;
+
+ /*
+ * Update the service struct with our real quotas and tell the
+ * transport about the change
+ */
+
+ service->send_quota = core_in_quota;
+ service->recv_quota = core_out_quota;
+ ret = session->transport->vt->service_start(session->transport, service);
+ if (ret < 0)
+ return ret;
+
+ WARN_ON(!list_empty(&client->message_queue));
+
+ return vs_client_core_core_req_connect(state, GFP_KERNEL);
+}
+
+static struct vs_client_core_state *
+vs_core_client_alloc(struct vs_service_device *service)
+{
+ struct core_client *client;
+ int err;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ goto fail;
+
+ client->service = service;
+ INIT_LIST_HEAD(&client->message_queue);
+ INIT_WORK(&client->message_queue_work, message_queue_work);
+ mutex_init(&client->message_queue_lock);
+
+ err = sysfs_create_group(&service->dev.kobj, &client_core_attr_group);
+ if (err)
+ goto fail_free_client;
+
+ /*
+ * Default transport resources for the core service client. The
+ * server will inform us of the real quotas in the startup message.
+ * Note that it is important that the quotas never decrease, so these
+ * numbers are as small as possible.
+ */
+ service->send_quota = 0;
+ service->recv_quota = 1;
+ service->notify_send_bits = 0;
+ service->notify_send_offset = 0;
+ service->notify_recv_bits = 0;
+ service->notify_recv_offset = 0;
+
+ return &client->state;
+
+fail_free_client:
+ kfree(client);
+fail:
+ return NULL;
+}
+
+static void vs_core_client_release(struct vs_client_core_state *state)
+{
+ struct core_client *client = to_core_client(state);
+
+ sysfs_remove_group(&client->service->dev.kobj, &client_core_attr_group);
+ kfree(client);
+}
+
+static struct vs_client_core vs_core_client_driver = {
+ .alloc = vs_core_client_alloc,
+ .release = vs_core_client_release,
+ .start = vs_core_client_start,
+ .reset = vs_core_client_reset,
+ .tx_ready = vs_core_client_tx_ready,
+
+ .core = {
+ .nack_connect = vs_client_core_fatal_error,
+
+ /* FIXME: Jira ticket SDK-3074 - ryanm. */
+ .ack_disconnect = vs_client_core_fatal_error,
+ .nack_disconnect = vs_client_core_fatal_error,
+
+ .msg_service_created = vs_client_core_handle_service_created,
+ .msg_service_removed = vs_client_core_handle_service_removed,
+
+ .msg_startup = vs_core_client_startup,
+ /* FIXME: Jira ticket SDK-3074 - philipd. */
+ .msg_shutdown = vs_client_core_fatal_error,
+ .msg_server_ready = vs_client_core_handle_server_ready,
+ .msg_service_reset = vs_client_core_handle_service_reset,
+ },
+};
+
+/*
+ * Client bus driver
+ */
+static int vs_client_bus_match(struct device *dev, struct device_driver *driver)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_service_driver *vsdrv = to_vs_service_driver(driver);
+
+ /* Don't match anything to the devio driver; it's bound manually */
+ if (!vsdrv->protocol)
+ return 0;
+
+ WARN_ON_ONCE(service->is_server || vsdrv->is_server);
+
+ /* Match if the protocol strings are the same */
+ if (strcmp(service->protocol, vsdrv->protocol) == 0)
+ return 1;
+
+ return 0;
+}
+
+static ssize_t is_server_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", service->is_server);
+}
+
+static ssize_t id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", service->id);
+}
+
+static ssize_t dev_protocol_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", service->protocol ?: "");
+}
+
+static ssize_t service_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", service->name);
+}
+
+static ssize_t quota_in_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", service->send_quota);
+}
+
+static ssize_t quota_out_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", service->recv_quota);
+}
+
+static struct device_attribute vs_client_dev_attrs[] = {
+ __ATTR_RO(id),
+ __ATTR_RO(is_server),
+ __ATTR(protocol, S_IRUGO, dev_protocol_show, NULL),
+ __ATTR_RO(service_name),
+ __ATTR_RO(quota_in),
+ __ATTR_RO(quota_out),
+ __ATTR_NULL
+};
+
+static ssize_t protocol_show(struct device_driver *drv, char *buf)
+{
+ struct vs_service_driver *driver = to_vs_service_driver(drv);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", driver->protocol);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
+static struct driver_attribute vs_client_drv_attrs[] = {
+ __ATTR_RO(protocol),
+ __ATTR_NULL
+};
+#else
+static DRIVER_ATTR_RO(protocol);
+
+static struct attribute *vs_client_drv_attrs[] = {
+ &driver_attr_protocol.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(vs_client_drv);
+#endif
+
+struct bus_type vs_client_bus_type = {
+ .name = "vservices-client",
+ .dev_attrs = vs_client_dev_attrs,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
+ .drv_attrs = vs_client_drv_attrs,
+#else
+ .drv_groups = vs_client_drv_groups,
+#endif
+ .match = vs_client_bus_match,
+ .probe = vs_service_bus_probe,
+ .remove = vs_service_bus_remove,
+ .uevent = vs_service_bus_uevent,
+};
+EXPORT_SYMBOL(vs_client_bus_type);
+
+/*
+ * Client session driver
+ */
+static int vs_client_session_probe(struct device *dev)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+ struct vs_service_device *service;
+ char *protocol, *name;
+ int ret = 0;
+
+ if (session->is_server) {
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ /* create a service for the core protocol client */
+ protocol = kstrdup(VSERVICE_CORE_PROTOCOL_NAME, GFP_KERNEL);
+ if (!protocol) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ name = kstrdup("core", GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto fail_free_protocol;
+ }
+
+ service = vs_service_register(session, NULL, 0, protocol, name, NULL);
+ if (IS_ERR(service)) {
+ ret = PTR_ERR(service);
+ goto fail_free_name;
+ }
+
+fail_free_name:
+ kfree(name);
+fail_free_protocol:
+ kfree(protocol);
+fail:
+ return ret;
+}
+
+static int
+vs_client_session_send_service_reset(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ if (WARN_ON(service->id == 0))
+ return -EINVAL;
+
+ return vs_client_core_queue_service_reset(session, service);
+}
+
+static struct vs_session_driver vs_client_session_driver = {
+ .driver = {
+ .name = "vservices-client-session",
+ .owner = THIS_MODULE,
+ .bus = &vs_session_bus_type,
+ .probe = vs_client_session_probe,
+ .suppress_bind_attrs = true,
+ },
+ .is_server = false,
+ .service_bus = &vs_client_bus_type,
+ .service_local_reset = vs_client_session_send_service_reset,
+};
+
+static int __init vs_core_client_init(void)
+{
+ int ret;
+
+ ret = bus_register(&vs_client_bus_type);
+ if (ret)
+ goto fail_bus_register;
+
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ vs_devio_client_driver.driver.bus = &vs_client_bus_type;
+ vs_devio_client_driver.driver.owner = THIS_MODULE;
+ ret = driver_register(&vs_devio_client_driver.driver);
+ if (ret)
+ goto fail_devio_register;
+#endif
+
+ ret = driver_register(&vs_client_session_driver.driver);
+ if (ret)
+ goto fail_driver_register;
+
+ ret = vservice_core_client_register(&vs_core_client_driver,
+ "vs_core_client");
+ if (ret)
+ goto fail_core_register;
+
+ vservices_client_root = kobject_create_and_add("client-sessions",
+ vservices_root);
+ if (!vservices_client_root) {
+ ret = -ENOMEM;
+ goto fail_create_root;
+ }
+
+ return 0;
+
+fail_create_root:
+ vservice_core_client_unregister(&vs_core_client_driver);
+fail_core_register:
+ driver_unregister(&vs_client_session_driver.driver);
+fail_driver_register:
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ driver_unregister(&vs_devio_client_driver.driver);
+ vs_devio_client_driver.driver.bus = NULL;
+ vs_devio_client_driver.driver.owner = NULL;
+fail_devio_register:
+#endif
+ bus_unregister(&vs_client_bus_type);
+fail_bus_register:
+ return ret;
+}
+
+static void __exit vs_core_client_exit(void)
+{
+ kobject_put(vservices_client_root);
+ vservice_core_client_unregister(&vs_core_client_driver);
+ driver_unregister(&vs_client_session_driver.driver);
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ driver_unregister(&vs_devio_client_driver.driver);
+ vs_devio_client_driver.driver.bus = NULL;
+ vs_devio_client_driver.driver.owner = NULL;
+#endif
+ bus_unregister(&vs_client_bus_type);
+}
+
+subsys_initcall(vs_core_client_init);
+module_exit(vs_core_client_exit);
+
+MODULE_DESCRIPTION("OKL4 Virtual Services Core Client Driver");
+MODULE_AUTHOR("Open Kernel Labs, Inc");
diff --git a/drivers/vservices/core_server.c b/drivers/vservices/core_server.c
new file mode 100644
index 0000000..76ca83c
--- /dev/null
+++ b/drivers/vservices/core_server.c
@@ -0,0 +1,1651 @@
+/*
+ * drivers/vservices/core_server.c
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * Server side core service application driver
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/ctype.h>
+
+#include <vservices/types.h>
+#include <vservices/transport.h>
+#include <vservices/session.h>
+#include <vservices/buffer.h>
+#include <vservices/service.h>
+
+#include <vservices/protocol/core/types.h>
+#include <vservices/protocol/core/common.h>
+#include <vservices/protocol/core/server.h>
+
+#include "transport.h"
+#include "session.h"
+#include "compat.h"
+
+#define VSERVICE_CORE_SERVICE_NAME "core"
+
+struct core_server {
+ struct vs_server_core_state state;
+ struct vs_service_device *service;
+
+ /*
+ * A list of messages to send, a mutex protecting it, and a
+ * work item to process the list.
+ */
+ struct list_head message_queue;
+ struct mutex message_queue_lock;
+ struct work_struct message_queue_work;
+
+ struct mutex alloc_lock;
+
+ /* The following are all protected by alloc_lock. */
+ unsigned long *in_notify_map;
+ int in_notify_map_bits;
+
+ unsigned long *out_notify_map;
+ int out_notify_map_bits;
+
+ unsigned in_quota_remaining;
+ unsigned out_quota_remaining;
+};
+
+/*
+ * Used for message deferral when the core service is over quota.
+ */
+struct pending_message {
+ vservice_core_message_id_t type;
+ struct vs_service_device *service;
+ struct list_head list;
+};
+
+#define to_core_server(x) container_of(x, struct core_server, state)
+#define dev_to_core_server(x) to_core_server(dev_get_drvdata(x))
+
+static struct vs_session_device *
+vs_core_server_session(struct core_server *server)
+{
+ return vs_service_get_session(server->service);
+}
+
+static struct core_server *
+vs_server_session_core_server(struct vs_session_device *session)
+{
+ struct vs_service_device *core_service = session->core_service;
+
+ if (!core_service)
+ return NULL;
+
+ return dev_to_core_server(&core_service->dev);
+}
+
+static int vs_server_core_send_service_removed(struct core_server *server,
+ struct vs_service_device *service)
+{
+ return vs_server_core_core_send_service_removed(&server->state,
+ service->id, GFP_KERNEL);
+}
+
+static bool
+cancel_pending_created(struct core_server *server,
+ struct vs_service_device *service)
+{
+ struct pending_message *msg;
+
+ list_for_each_entry(msg, &server->message_queue, list) {
+ if (msg->type == VSERVICE_CORE_CORE_MSG_SERVICE_CREATED &&
+ msg->service == service) {
+ vs_put_service(msg->service);
+ list_del(&msg->list);
+ kfree(msg);
+
+ /* there can only be one */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int vs_server_core_queue_service_removed(struct core_server *server,
+ struct vs_service_device *service)
+{
+ struct pending_message *msg;
+
+ lockdep_assert_held(&service->ready_lock);
+
+ mutex_lock(&server->message_queue_lock);
+
+ /*
+ * If we haven't sent the notification that the service was created,
+ * nuke it and do nothing else.
+ *
+ * This is not just an optimisation; see below.
+ */
+ if (cancel_pending_created(server, service)) {
+ mutex_unlock(&server->message_queue_lock);
+ return 0;
+ }
+
+ /*
+ * Do nothing if the core state is not connected. We must avoid
+ * queueing service_removed messages on a reset service.
+ *
+ * Note that we cannot take the core server state lock here, because
+ * we may (or may not) have been called from a core service message
+ * handler. Thus, we must beware of races with changes to this
+ * condition:
+ *
+ * - It becomes true when the req_connect handler sends an
+ * ack_connect, *after* it queues service_created for each existing
+ * service (while holding the service ready lock). The handler sends
+ * ack_connect with the message queue lock held.
+ *
+ * - If we see the service as connected, then the req_connect
+ * handler has already queued and sent a service_created for this
+ * service, so it's ok for us to send a service_removed.
+ *
+ * - If we see it as disconnected, the req_connect handler hasn't
+ * taken the message queue lock to send ack_connect yet, and thus
+ * has not released the service state lock; so if it queued a
+ * service_created we caught it in the flush above before it was
+ * sent.
+ *
+ * - It becomes false before the reset / disconnect handlers are
+ * called and those will both flush the message queue afterwards.
+ *
+ * - If we see the service as connected, then the reset / disconnect
+ * handler is going to flush the message.
+ *
+ * - If we see it disconnected, the state change has occurred and
+ * implicitly had the same effect as this message, so doing
+ * nothing is correct.
+ *
+ * Note that ordering in all of the above cases is guaranteed by the
+ * message queue lock.
+ */
+ if (!VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) {
+ mutex_unlock(&server->message_queue_lock);
+ return 0;
+ }
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg) {
+ mutex_unlock(&server->message_queue_lock);
+ return -ENOMEM;
+ }
+
+ msg->type = VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED;
+ /* put by message_queue_work */
+ msg->service = vs_get_service(service);
+
+ list_add_tail(&msg->list, &server->message_queue);
+
+ mutex_unlock(&server->message_queue_lock);
+ queue_work(server->service->work_queue, &server->message_queue_work);
+
+ return 0;
+}
+
+static int vs_server_core_send_service_created(struct core_server *server,
+ struct vs_service_device *service)
+{
+ struct vs_session_device *session =
+ vs_service_get_session(server->service);
+
+ struct vs_mbuf *mbuf;
+ struct vs_string service_name, protocol_name;
+ size_t service_name_len, protocol_name_len;
+
+ int err;
+
+ mbuf = vs_server_core_core_alloc_service_created(&server->state,
+ &service_name, &protocol_name, GFP_KERNEL);
+
+ if (IS_ERR(mbuf))
+ return PTR_ERR(mbuf);
+
+ vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev,
+ "Sending service created message for %d (%s:%s)\n",
+ service->id, service->name, service->protocol);
+
+ service_name_len = strlen(service->name);
+ protocol_name_len = strlen(service->protocol);
+
+ if (service_name_len > vs_string_max_size(&service_name) ||
+ protocol_name_len > vs_string_max_size(&protocol_name)) {
+ dev_err(&session->dev,
+ "Invalid name/protocol for service %d (%s:%s)\n",
+ service->id, service->name,
+ service->protocol);
+ err = -EINVAL;
+ goto fail;
+ }
+
+ vs_string_copyin(&service_name, service->name);
+ vs_string_copyin(&protocol_name, service->protocol);
+
+ err = vs_server_core_core_send_service_created(&server->state,
+ service->id, service_name, protocol_name, mbuf);
+ if (err) {
+ dev_err(&session->dev,
+ "Fatal error sending service creation message for %d (%s:%s): %d\n",
+ service->id, service->name,
+ service->protocol, err);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ vs_server_core_core_free_service_created(&server->state,
+ &service_name, &protocol_name, mbuf);
+
+ return err;
+}
+
+static int vs_server_core_queue_service_created(struct core_server *server,
+ struct vs_service_device *service)
+{
+ struct pending_message *msg;
+
+ lockdep_assert_held(&service->ready_lock);
+ lockdep_assert_held(&server->service->state_mutex);
+
+ mutex_lock(&server->message_queue_lock);
+
+ /* Do nothing if the core state is disconnected. */
+ if (!VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) {
+ mutex_unlock(&server->message_queue_lock);
+ return 0;
+ }
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg) {
+ mutex_unlock(&server->message_queue_lock);
+ return -ENOMEM;
+ }
+
+ msg->type = VSERVICE_CORE_CORE_MSG_SERVICE_CREATED;
+ /* put by message_queue_work */
+ msg->service = vs_get_service(service);
+
+ list_add_tail(&msg->list, &server->message_queue);
+
+ mutex_unlock(&server->message_queue_lock);
+ queue_work(server->service->work_queue, &server->message_queue_work);
+
+ return 0;
+}
+
+static struct vs_service_device *
+__vs_server_core_register_service(struct vs_session_device *session,
+ vs_service_id_t service_id, struct vs_service_device *owner,
+ const char *name, const char *protocol, const void *plat_data)
+{
+ if (!session->is_server)
+ return ERR_PTR(-ENODEV);
+
+ if (!name || strnlen(name, VSERVICE_CORE_SERVICE_NAME_SIZE + 1) >
+ VSERVICE_CORE_SERVICE_NAME_SIZE || name[0] == '\n')
+ return ERR_PTR(-EINVAL);
+
+ /* The server core must only be registered as service_id zero */
+ if (service_id == 0 && (owner != NULL ||
+ strcmp(name, VSERVICE_CORE_SERVICE_NAME) != 0 ||
+ strcmp(protocol, VSERVICE_CORE_PROTOCOL_NAME) != 0))
+ return ERR_PTR(-EINVAL);
+
+ return vs_service_register(session, owner, service_id, protocol, name,
+ plat_data);
+}
+
+static struct vs_service_device *
+vs_server_core_create_service(struct core_server *server,
+ struct vs_session_device *session,
+ struct vs_service_device *owner, vs_service_id_t service_id,
+ const char *name, const char *protocol, const void *plat_data)
+{
+ struct vs_service_device *service;
+
+ service = __vs_server_core_register_service(session, service_id,
+ owner, name, protocol, plat_data);
+ if (IS_ERR(service))
+ return service;
+
+ if (protocol) {
+ vs_service_state_lock(server->service);
+ vs_service_start(service);
+ if (VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core))
+ vs_service_enable(service);
+ vs_service_state_unlock(server->service);
+ }
+
+ return service;
+}
+
+static int
+vs_server_core_send_service_reset_ready(struct core_server *server,
+ vservice_core_message_id_t type,
+ struct vs_service_device *service)
+{
+ bool is_reset = (type == VSERVICE_CORE_CORE_MSG_SERVICE_RESET);
+ struct vs_session_device *session __maybe_unused =
+ vs_service_get_session(server->service);
+ int err;
+
+ vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev,
+ "Sending %s for service %d\n",
+ is_reset ? "reset" : "ready", service->id);
+
+ if (is_reset)
+ err = vs_server_core_core_send_service_reset(&server->state,
+ service->id, GFP_KERNEL);
+ else
+ err = vs_server_core_core_send_server_ready(&server->state,
+ service->id, service->recv_quota,
+ service->send_quota,
+ service->notify_recv_offset,
+ service->notify_recv_bits,
+ service->notify_send_offset,
+ service->notify_send_bits,
+ GFP_KERNEL);
+
+ return err;
+}
+
+static bool
+cancel_pending_ready(struct core_server *server,
+ struct vs_service_device *service)
+{
+ struct pending_message *msg;
+
+ list_for_each_entry(msg, &server->message_queue, list) {
+ if (msg->type == VSERVICE_CORE_CORE_MSG_SERVER_READY &&
+ msg->service == service) {
+ vs_put_service(msg->service);
+ list_del(&msg->list);
+ kfree(msg);
+
+ /* there can only be one */
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int
+vs_server_core_queue_service_reset_ready(struct core_server *server,
+ vservice_core_message_id_t type,
+ struct vs_service_device *service)
+{
+ bool is_reset = (type == VSERVICE_CORE_CORE_MSG_SERVICE_RESET);
+ struct pending_message *msg;
+
+ mutex_lock(&server->message_queue_lock);
+
+ /*
+ * If this is a reset, and there is an outgoing ready in the
+ * queue, we must cancel it so it can't be sent with invalid
+ * transport resources, and then return immediately so we
+ * don't send a redundant reset.
+ */
+ if (is_reset && cancel_pending_ready(server, service)) {
+ mutex_unlock(&server->message_queue_lock);
+ return VS_SERVICE_ALREADY_RESET;
+ }
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg) {
+ mutex_unlock(&server->message_queue_lock);
+ return -ENOMEM;
+ }
+
+ msg->type = type;
+ /* put by message_queue_work */
+ msg->service = vs_get_service(service);
+ list_add_tail(&msg->list, &server->message_queue);
+
+ mutex_unlock(&server->message_queue_lock);
+ queue_work(server->service->work_queue, &server->message_queue_work);
+
+ return 0;
+}
+
+static int vs_core_server_tx_ready(struct vs_server_core_state *state)
+{
+ struct core_server *server = to_core_server(state);
+ struct vs_session_device *session __maybe_unused =
+ vs_service_get_session(server->service);
+
+ vs_dev_debug(VS_DEBUG_SERVER, session, &session->dev, "tx_ready\n");
+
+ queue_work(server->service->work_queue, &server->message_queue_work);
+
+ return 0;
+}
+
+static void message_queue_work(struct work_struct *work)
+{
+ struct core_server *server = container_of(work, struct core_server,
+ message_queue_work);
+ struct pending_message *msg;
+ int err;
+
+ vs_service_state_lock(server->service);
+
+ if (!VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core)) {
+ vs_service_state_unlock(server->service);
+ return;
+ }
+
+ /*
+ * If any pending message fails we exit the loop immediately so that
+ * we preserve the message order.
+ */
+ mutex_lock(&server->message_queue_lock);
+ while (!list_empty(&server->message_queue)) {
+ msg = list_first_entry(&server->message_queue,
+ struct pending_message, list);
+
+ switch (msg->type) {
+ case VSERVICE_CORE_CORE_MSG_SERVICE_CREATED:
+ err = vs_server_core_send_service_created(server,
+ msg->service);
+ break;
+
+ case VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED:
+ err = vs_server_core_send_service_removed(server,
+ msg->service);
+ break;
+
+ case VSERVICE_CORE_CORE_MSG_SERVICE_RESET:
+ case VSERVICE_CORE_CORE_MSG_SERVER_READY:
+ err = vs_server_core_send_service_reset_ready(
+ server, msg->type, msg->service);
+ break;
+
+ default:
+ dev_warn(&server->service->dev,
+ "Don't know how to handle pending message type %d\n",
+ msg->type);
+ err = 0;
+ break;
+ }
+
+ /*
+ * If we're out of quota we exit and wait for tx_ready to
+ * queue us again.
+ */
+ if (err == -ENOBUFS)
+ break;
+
+ /* Any other error is fatal */
+ if (err < 0) {
+ dev_err(&server->service->dev,
+ "Failed to send pending message type %d: %d - resetting session",
+ msg->type, err);
+ vs_service_reset_nosync(server->service);
+ break;
+ }
+
+ /*
+ * The message sent successfully - remove it from the
+ * queue. The corresponding vs_get_service() was done
+ * when the pending message was created.
+ */
+ vs_put_service(msg->service);
+ list_del(&msg->list);
+ kfree(msg);
+ }
+ mutex_unlock(&server->message_queue_lock);
+
+ vs_service_state_unlock(server->service);
+
+ return;
+}
+
+/*
+ * Core server sysfs interface
+ */
+static ssize_t server_core_create_service_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = to_vs_session_device(dev->parent);
+ struct core_server *server = dev_to_core_server(&service->dev);
+ struct vs_service_device *new_service;
+ char *p;
+ ssize_t ret = count;
+
+ /* FIXME - Buffer sizes are not defined in generated headers */
+ /* discard leading whitespace */
+ while (count && isspace(*buf)) {
+ buf++;
+ count--;
+ }
+ if (!count) {
+ dev_info(dev, "empty service name");
+ return -EINVAL;
+ }
+ /* discard trailing whitespace */
+ while (count && isspace(buf[count - 1]))
+ count--;
+
+ if (count > VSERVICE_CORE_SERVICE_NAME_SIZE) {
+ dev_info(dev, "service name too long (max %d)\n", VSERVICE_CORE_SERVICE_NAME_SIZE);
+ return -EINVAL;
+ }
+
+ p = kstrndup(buf, count, GFP_KERNEL);
+
+ /*
+ * Writing a service name to this file creates a new service. The
+ * service is created without a protocol. It will appear in sysfs
+ * but will not be bound to a driver until a valid protocol name
+ * has been written to the created devices protocol sysfs attribute.
+ */
+ new_service = vs_server_core_create_service(server, session, service,
+ VS_SERVICE_AUTO_ALLOCATE_ID, p, NULL, NULL);
+ if (IS_ERR(new_service))
+ ret = PTR_ERR(new_service);
+
+ kfree(p);
+
+ return ret;
+}
+
+static ssize_t server_core_reset_service_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *core_service = to_vs_service_device(dev);
+ struct vs_session_device *session =
+ vs_service_get_session(core_service);
+ struct vs_service_device *target;
+ vs_service_id_t service_id;
+ unsigned long val;
+ int err;
+
+ /*
+ * Writing a valid service_id to this file does a reset of that service
+ */
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ service_id = val;
+ target = vs_session_get_service(session, service_id);
+ if (!target)
+ return -EINVAL;
+
+ err = vs_service_reset(target, core_service);
+
+ vs_put_service(target);
+ return err < 0 ? err : count;
+}
+
+static ssize_t server_core_remove_service_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct vs_service_device *target;
+ vs_service_id_t service_id;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ service_id = val;
+ if (service_id == 0) {
+ /*
+ * We don't allow removing the core service this way. The
+ * core service will be removed when the session is removed.
+ */
+ return -EINVAL;
+ }
+
+ target = vs_session_get_service(session, service_id);
+ if (!target)
+ return -EINVAL;
+
+ err = vs_service_delete(target, service);
+
+ vs_put_service(target);
+ return err < 0 ? err : count;
+}
+
+static DEVICE_ATTR(create_service, S_IWUSR,
+ NULL, server_core_create_service_store);
+static DEVICE_ATTR(reset_service, S_IWUSR,
+ NULL, server_core_reset_service_store);
+static DEVICE_ATTR(remove_service, S_IWUSR,
+ NULL, server_core_remove_service_store);
+
+static struct attribute *server_core_dev_attrs[] = {
+ &dev_attr_create_service.attr,
+ &dev_attr_reset_service.attr,
+ &dev_attr_remove_service.attr,
+ NULL,
+};
+
+static const struct attribute_group server_core_attr_group = {
+ .attrs = server_core_dev_attrs,
+};
+
+static int init_transport_resource_allocation(struct core_server *server)
+{
+ struct vs_session_device *session = vs_core_server_session(server);
+ struct vs_transport *transport = session->transport;
+ size_t size;
+ int err;
+
+ mutex_init(&server->alloc_lock);
+ mutex_lock(&server->alloc_lock);
+
+ transport->vt->get_quota_limits(transport, &server->out_quota_remaining,
+ &server->in_quota_remaining);
+
+ transport->vt->get_notify_bits(transport, &server->out_notify_map_bits,
+ &server->in_notify_map_bits);
+
+ size = BITS_TO_LONGS(server->in_notify_map_bits) *
+ sizeof(unsigned long);
+ server->in_notify_map = kzalloc(size, GFP_KERNEL);
+ if (server->in_notify_map_bits && !server->in_notify_map) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ size = BITS_TO_LONGS(server->out_notify_map_bits) *
+ sizeof(unsigned long);
+ server->out_notify_map = kzalloc(size, GFP_KERNEL);
+ if (server->out_notify_map_bits && !server->out_notify_map) {
+ err = -ENOMEM;
+ goto fail_free_in_bits;
+ }
+
+ mutex_unlock(&server->alloc_lock);
+
+ return 0;
+
+fail_free_in_bits:
+ kfree(server->in_notify_map);
+fail:
+ mutex_unlock(&server->alloc_lock);
+ return err;
+}
+
+static int alloc_quota(unsigned minimum, unsigned best, unsigned set,
+ unsigned *remaining)
+{
+ unsigned quota;
+
+ if (set) {
+ quota = set;
+
+ if (quota > *remaining)
+ return -ENOSPC;
+ } else if (best) {
+ quota = min(best, *remaining);
+ } else {
+ quota = minimum;
+ }
+
+ if (quota < minimum)
+ return -ENOSPC;
+
+ *remaining -= quota;
+
+ return min_t(unsigned, quota, INT_MAX);
+}
+
+static int alloc_notify_bits(unsigned notify_count, unsigned long *map,
+ unsigned nr_bits)
+{
+ unsigned offset;
+
+ if (notify_count) {
+ offset = bitmap_find_next_zero_area(map, nr_bits, 0,
+ notify_count, 0);
+
+ if (offset >= nr_bits || offset > (unsigned)INT_MAX)
+ return -ENOSPC;
+
+ bitmap_set(map, offset, notify_count);
+ } else {
+ offset = 0;
+ }
+
+ return offset;
+}
+
+/*
+ * alloc_transport_resources - Allocates the quotas and notification bits for
+ * a service.
+ * @server: the core service state.
+ * @service: the service device to allocate resources for.
+ *
+ * This function allocates message quotas and notification bits. It is called
+ * for the core service in alloc(), and for every other service by the server
+ * bus probe() function.
+ */
+static int alloc_transport_resources(struct core_server *server,
+ struct vs_service_device *service)
+{
+ struct vs_session_device *session __maybe_unused =
+ vs_service_get_session(service);
+ unsigned in_bit_offset, out_bit_offset;
+ unsigned in_quota, out_quota;
+ int ret;
+ struct vs_service_driver *driver;
+
+ if (WARN_ON(!service->dev.driver))
+ return -ENODEV;
+
+ mutex_lock(&server->alloc_lock);
+
+ driver = to_vs_service_driver(service->dev.driver);
+
+ /* Quota allocations */
+ ret = alloc_quota(driver->in_quota_min, driver->in_quota_best,
+ service->in_quota_set, &server->in_quota_remaining);
+ if (ret < 0) {
+ dev_err(&service->dev, "cannot allocate in quota\n");
+ goto fail_in_quota;
+ }
+ in_quota = ret;
+
+ ret = alloc_quota(driver->out_quota_min, driver->out_quota_best,
+ service->out_quota_set, &server->out_quota_remaining);
+ if (ret < 0) {
+ dev_err(&service->dev, "cannot allocate out quota\n");
+ goto fail_out_quota;
+ }
+ out_quota = ret;
+
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev,
+ "%d: quota in: %u out: %u; remaining in: %u out: %u\n",
+ service->id, in_quota, out_quota,
+ server->in_quota_remaining,
+ server->out_quota_remaining);
+
+ /* Notification bit allocations */
+ ret = alloc_notify_bits(service->notify_recv_bits,
+ server->in_notify_map, server->in_notify_map_bits);
+ if (ret < 0) {
+ dev_err(&service->dev, "cannot allocate in notify bits\n");
+ goto fail_in_notify;
+ }
+ in_bit_offset = ret;
+
+ ret = alloc_notify_bits(service->notify_send_bits,
+ server->out_notify_map, server->out_notify_map_bits);
+ if (ret < 0) {
+ dev_err(&service->dev, "cannot allocate out notify bits\n");
+ goto fail_out_notify;
+ }
+ out_bit_offset = ret;
+
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev,
+ "notify bits in: %u/%u out: %u/%u\n",
+ in_bit_offset, service->notify_recv_bits,
+ out_bit_offset, service->notify_send_bits);
+
+ /* Fill in the device's allocations */
+ service->recv_quota = in_quota;
+ service->send_quota = out_quota;
+ service->notify_recv_offset = in_bit_offset;
+ service->notify_send_offset = out_bit_offset;
+
+ mutex_unlock(&server->alloc_lock);
+
+ return 0;
+
+fail_out_notify:
+ if (service->notify_recv_bits)
+ bitmap_clear(server->in_notify_map,
+ in_bit_offset, service->notify_recv_bits);
+fail_in_notify:
+ server->out_quota_remaining += out_quota;
+fail_out_quota:
+ server->in_quota_remaining += in_quota;
+fail_in_quota:
+
+ mutex_unlock(&server->alloc_lock);
+
+ service->recv_quota = 0;
+ service->send_quota = 0;
+ service->notify_recv_bits = 0;
+ service->notify_recv_offset = 0;
+ service->notify_send_bits = 0;
+ service->notify_send_offset = 0;
+
+ return ret;
+}
+
+/*
+ * free_transport_resources - Frees the quotas and notification bits for
+ * a non-core service.
+ * @server: the core service state.
+ * @service: the service device to free resources for.
+ *
+ * This function is called by the server to free message quotas and
+ * notification bits that were allocated by alloc_transport_resources. It must
+ * only be called when the target service is in reset, and must be called with
+ * the core service's state lock held.
+ */
+static int free_transport_resources(struct core_server *server,
+ struct vs_service_device *service)
+{
+ mutex_lock(&server->alloc_lock);
+
+ if (service->notify_recv_bits)
+ bitmap_clear(server->in_notify_map,
+ service->notify_recv_offset,
+ service->notify_recv_bits);
+
+ if (service->notify_send_bits)
+ bitmap_clear(server->out_notify_map,
+ service->notify_send_offset,
+ service->notify_send_bits);
+
+ server->in_quota_remaining += service->recv_quota;
+ server->out_quota_remaining += service->send_quota;
+
+ mutex_unlock(&server->alloc_lock);
+
+ service->recv_quota = 0;
+ service->send_quota = 0;
+ service->notify_recv_bits = 0;
+ service->notify_recv_offset = 0;
+ service->notify_send_bits = 0;
+ service->notify_send_offset = 0;
+
+ return 0;
+}
+
+static struct vs_server_core_state *
+vs_core_server_alloc(struct vs_service_device *service)
+{
+ struct core_server *server;
+ int err;
+
+ if (WARN_ON(service->id != 0))
+ goto fail;
+
+ server = kzalloc(sizeof(*server), GFP_KERNEL);
+ if (!server)
+ goto fail;
+
+ server->service = service;
+ INIT_LIST_HEAD(&server->message_queue);
+ INIT_WORK(&server->message_queue_work, message_queue_work);
+ mutex_init(&server->message_queue_lock);
+
+ err = init_transport_resource_allocation(server);
+ if (err)
+ goto fail_init_alloc;
+
+ err = alloc_transport_resources(server, service);
+ if (err)
+ goto fail_alloc_transport;
+
+ err = sysfs_create_group(&service->dev.kobj, &server_core_attr_group);
+ if (err)
+ goto fail_sysfs;
+
+ return &server->state;
+
+fail_sysfs:
+ free_transport_resources(server, service);
+fail_alloc_transport:
+ kfree(server->out_notify_map);
+ kfree(server->in_notify_map);
+fail_init_alloc:
+ kfree(server);
+fail:
+ return NULL;
+}
+
+static void vs_core_server_release(struct vs_server_core_state *state)
+{
+ struct core_server *server = to_core_server(state);
+ struct vs_session_device *session = vs_core_server_session(server);
+
+ /* Delete all the other services */
+ vs_session_delete_noncore(session);
+
+ sysfs_remove_group(&server->service->dev.kobj, &server_core_attr_group);
+ kfree(server->out_notify_map);
+ kfree(server->in_notify_map);
+ kfree(server);
+}
+
+/**
+ * vs_server_create_service - create and register a new vService server
+ * @session: the session to create the vService server on
+ * @parent: an existing server that is managing the new server
+ * @name: the name of the new service
+ * @protocol: the protocol for the new service
+ * @plat_data: value to be assigned to (struct device *)->platform_data
+ */
+struct vs_service_device *
+vs_server_create_service(struct vs_session_device *session,
+ struct vs_service_device *parent, const char *name,
+ const char *protocol, const void *plat_data)
+{
+ struct vs_service_device *core_service, *new_service;
+ struct core_server *server;
+
+ if (!session->is_server || !name || !protocol)
+ return NULL;
+
+ core_service = session->core_service;
+ if (!core_service)
+ return NULL;
+
+ device_lock(&core_service->dev);
+ if (!core_service->dev.driver) {
+ device_unlock(&core_service->dev);
+ return NULL;
+ }
+
+ server = dev_to_core_server(&core_service->dev);
+
+ if (!parent)
+ parent = core_service;
+
+ new_service = vs_server_core_create_service(server, session, parent,
+ VS_SERVICE_AUTO_ALLOCATE_ID, name, protocol, plat_data);
+
+ device_unlock(&core_service->dev);
+
+ if (IS_ERR(new_service))
+ return NULL;
+
+ return new_service;
+}
+EXPORT_SYMBOL(vs_server_create_service);
+
+/**
+ * vs_server_destroy_service - destroy and unregister a vService server. This
+ * function must _not_ be used from the target service's own workqueue.
+ * @service: The service to destroy
+ */
+int vs_server_destroy_service(struct vs_service_device *service,
+ struct vs_service_device *parent)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ if (!session->is_server || service->id == 0)
+ return -EINVAL;
+
+ if (!parent)
+ parent = session->core_service;
+
+ return vs_service_delete(service, parent);
+}
+EXPORT_SYMBOL(vs_server_destroy_service);
+
+static void __queue_service_created(struct vs_service_device *service,
+ void *data)
+{
+ struct core_server *server = (struct core_server *)data;
+
+ vs_server_core_queue_service_created(server, service);
+}
+
+static int vs_server_core_handle_connect(struct vs_server_core_state *state)
+{
+ struct core_server *server = to_core_server(state);
+ struct vs_session_device *session = vs_core_server_session(server);
+ int err;
+
+ /* Tell the other end that we've finished connecting. */
+ err = vs_server_core_core_send_ack_connect(state, GFP_KERNEL);
+ if (err)
+ return err;
+
+ /* Queue a service-created message for each existing service. */
+ vs_session_for_each_service(session, __queue_service_created, server);
+
+ /* Re-enable all the services. */
+ vs_session_enable_noncore(session);
+
+ return 0;
+}
+
+static void vs_core_server_disable_services(struct core_server *server)
+{
+ struct vs_session_device *session = vs_core_server_session(server);
+ struct pending_message *msg;
+
+ /* Disable all the other services */
+ vs_session_disable_noncore(session);
+
+ /* Flush all the pending service-readiness messages */
+ mutex_lock(&server->message_queue_lock);
+ while (!list_empty(&server->message_queue)) {
+ msg = list_first_entry(&server->message_queue,
+ struct pending_message, list);
+ vs_put_service(msg->service);
+ list_del(&msg->list);
+ kfree(msg);
+ }
+ mutex_unlock(&server->message_queue_lock);
+}
+
+static int vs_server_core_handle_disconnect(struct vs_server_core_state *state)
+{
+ struct core_server *server = to_core_server(state);
+
+ vs_core_server_disable_services(server);
+
+ return vs_server_core_core_send_ack_disconnect(state, GFP_KERNEL);
+}
+
+static int
+vs_server_core_handle_service_reset(struct vs_server_core_state *state,
+ unsigned service_id)
+{
+ struct core_server *server = to_core_server(state);
+ struct vs_session_device *session = vs_core_server_session(server);
+
+ if (service_id == 0)
+ return -EPROTO;
+
+ return vs_service_handle_reset(session, service_id, false);
+}
+
+static void vs_core_server_start(struct vs_server_core_state *state)
+{
+ struct core_server *server = to_core_server(state);
+ struct vs_session_device *session = vs_core_server_session(server);
+ int err;
+
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &server->service->dev,
+ "Core server start\n");
+
+ err = vs_server_core_core_send_startup(&server->state,
+ server->service->recv_quota,
+ server->service->send_quota, GFP_KERNEL);
+
+ if (err)
+ dev_err(&session->dev, "Failed to start core protocol: %d\n",
+ err);
+}
+
+static void vs_core_server_reset(struct vs_server_core_state *state)
+{
+ struct core_server *server = to_core_server(state);
+ struct vs_session_device *session = vs_core_server_session(server);
+
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &server->service->dev,
+ "Core server reset\n");
+
+ vs_core_server_disable_services(server);
+}
+
+static struct vs_server_core vs_core_server_driver = {
+ .alloc = vs_core_server_alloc,
+ .release = vs_core_server_release,
+ .start = vs_core_server_start,
+ .reset = vs_core_server_reset,
+ .tx_ready = vs_core_server_tx_ready,
+ .core = {
+ .req_connect = vs_server_core_handle_connect,
+ .req_disconnect = vs_server_core_handle_disconnect,
+ .msg_service_reset = vs_server_core_handle_service_reset,
+ },
+};
+
+/*
+ * Server bus driver
+ */
+static int vs_server_bus_match(struct device *dev, struct device_driver *driver)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_service_driver *vsdrv = to_vs_service_driver(driver);
+
+ /* Don't match anything to the devio driver; it's bound manually */
+ if (!vsdrv->protocol)
+ return 0;
+
+ WARN_ON_ONCE(!service->is_server || !vsdrv->is_server);
+
+ /* Don't match anything that doesn't have a protocol set yet */
+ if (!service->protocol)
+ return 0;
+
+ if (strcmp(service->protocol, vsdrv->protocol) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int vs_server_bus_probe(struct device *dev)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct core_server *server = vs_server_session_core_server(session);
+ int ret;
+
+ /*
+ * Set the notify counts for the service, unless the driver is the
+ * devio driver in which case it has already been done by the devio
+ * bind ioctl. The devio driver cannot be bound automatically.
+ */
+ struct vs_service_driver *driver =
+ to_vs_service_driver(service->dev.driver);
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ if (driver != &vs_devio_server_driver)
+#endif
+ {
+ service->notify_recv_bits = driver->in_notify_count;
+ service->notify_send_bits = driver->out_notify_count;
+ }
+
+ /*
+ * We can't allocate transport resources here for the core service
+ * because the resource pool doesn't exist yet. It's done in alloc()
+ * instead (which is called, indirectly, by vs_service_bus_probe()).
+ */
+ if (service->id == 0)
+ return vs_service_bus_probe(dev);
+
+ if (!server)
+ return -ENODEV;
+ ret = alloc_transport_resources(server, service);
+ if (ret < 0)
+ goto fail;
+
+ ret = vs_service_bus_probe(dev);
+ if (ret < 0)
+ goto fail_free_resources;
+
+ return 0;
+
+fail_free_resources:
+ free_transport_resources(server, service);
+fail:
+ return ret;
+}
+
+static int vs_server_bus_remove(struct device *dev)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct core_server *server = vs_server_session_core_server(session);
+
+ vs_service_bus_remove(dev);
+
+ /*
+ * We skip free_transport_resources for the core service because the
+ * resource pool has already been freed at this point. It's also
+ * possible that the core service has disappeared, in which case
+ * there's no work to do here.
+ */
+ if (server != NULL && service->id != 0)
+ free_transport_resources(server, service);
+
+ return 0;
+}
+
+static ssize_t is_server_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", service->is_server);
+}
+
+static ssize_t id_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", service->id);
+}
+
+static ssize_t dev_protocol_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", service->protocol ?: "");
+}
+
+struct service_enable_work_struct {
+ struct vs_service_device *service;
+ struct work_struct work;
+};
+
+static void service_enable_work(struct work_struct *work)
+{
+ struct service_enable_work_struct *enable_work = container_of(work,
+ struct service_enable_work_struct, work);
+ struct vs_service_device *service = enable_work->service;
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct core_server *server = vs_server_session_core_server(session);
+ bool started;
+ int ret;
+
+ kfree(enable_work);
+
+ if (!server)
+ return;
+ /* Start and enable the service */
+ vs_service_state_lock(server->service);
+ started = vs_service_start(service);
+ if (!started) {
+ vs_service_state_unlock(server->service);
+ vs_put_service(service);
+ return;
+ }
+
+ if (VSERVICE_CORE_STATE_IS_CONNECTED(server->state.state.core))
+ vs_service_enable(service);
+ vs_service_state_unlock(server->service);
+
+ /* Tell the bus to search for a driver that supports the protocol */
+ ret = device_attach(&service->dev);
+ if (ret == 0)
+ dev_warn(&service->dev, "No driver found for protocol: %s\n",
+ service->protocol);
+ kobject_uevent(&service->dev.kobj, KOBJ_CHANGE);
+
+ /* The corresponding vs_get_service was done when the work was queued */
+ vs_put_service(service);
+}
+
+static ssize_t dev_protocol_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct service_enable_work_struct *enable_work;
+
+ /* The protocol can only be set once */
+ if (service->protocol)
+ return -EPERM;
+
+ /* Registering additional core servers is not allowed */
+ if (strcmp(buf, VSERVICE_CORE_PROTOCOL_NAME) == 0)
+ return -EINVAL;
+
+ if (strnlen(buf, VSERVICE_CORE_PROTOCOL_NAME_SIZE) + 1 >
+ VSERVICE_CORE_PROTOCOL_NAME_SIZE)
+ return -E2BIG;
+
+ enable_work = kmalloc(sizeof(*enable_work), GFP_KERNEL);
+ if (!enable_work)
+ return -ENOMEM;
+
+ /* Set the protocol and tell the client about it */
+ service->protocol = kstrdup(buf, GFP_KERNEL);
+ if (!service->protocol) {
+ kfree(enable_work);
+ return -ENOMEM;
+ }
+ strim(service->protocol);
+
+ /*
+ * Schedule work to enable the service. We can't do it here because
+ * we need to take the core service lock, and doing that here makes
+ * it depend circularly on this sysfs attribute, which can be deleted
+ * with that lock held.
+ *
+ * The corresponding vs_put_service is called in the enable_work
+ * function.
+ */
+ INIT_WORK(&enable_work->work, service_enable_work);
+ enable_work->service = vs_get_service(service);
+ schedule_work(&enable_work->work);
+
+ return count;
+}
+
+static ssize_t service_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", service->name);
+}
+
+static ssize_t quota_in_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct core_server *server = vs_server_session_core_server(session);
+ int ret;
+ unsigned long in_quota;
+
+ if (!server)
+ return -ENODEV;
+ /*
+ * Don't allow quota to be changed for services that have a driver
+ * bound. We take the alloc lock here because the device lock is held
+ * while creating and destroying this sysfs item. This means we can
+ * race with driver binding, but that doesn't matter: we actually just
+ * want to know that alloc_transport_resources() hasn't run yet, and
+ * that takes the alloc lock.
+ */
+ mutex_lock(&server->alloc_lock);
+ if (service->dev.driver) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ ret = kstrtoul(buf, 0, &in_quota);
+ if (ret < 0)
+ goto out;
+
+ service->in_quota_set = in_quota;
+ ret = count;
+
+out:
+ mutex_unlock(&server->alloc_lock);
+
+ return ret;
+}
+
+static ssize_t quota_in_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", service->recv_quota);
+}
+
+static ssize_t quota_out_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct core_server *server = vs_server_session_core_server(session);
+ int ret;
+ unsigned long out_quota;
+
+ if (!server)
+ return -ENODEV;
+ /* See comment in quota_in_store. */
+ mutex_lock(&server->alloc_lock);
+ if (service->dev.driver) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ ret = kstrtoul(buf, 0, &out_quota);
+ if (ret < 0)
+ goto out;
+
+ service->out_quota_set = out_quota;
+ ret = count;
+
+out:
+ mutex_unlock(&server->alloc_lock);
+
+ return ret;
+}
+
+static ssize_t quota_out_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", service->send_quota);
+}
+
+static struct device_attribute vs_server_dev_attrs[] = {
+ __ATTR_RO(id),
+ __ATTR_RO(is_server),
+ __ATTR(protocol, S_IRUGO | S_IWUSR,
+ dev_protocol_show, dev_protocol_store),
+ __ATTR_RO(service_name),
+ __ATTR(quota_in, S_IRUGO | S_IWUSR,
+ quota_in_show, quota_in_store),
+ __ATTR(quota_out, S_IRUGO | S_IWUSR,
+ quota_out_show, quota_out_store),
+ __ATTR_NULL
+};
+
+static ssize_t protocol_show(struct device_driver *drv, char *buf)
+{
+ struct vs_service_driver *vsdrv = to_vs_service_driver(drv);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", vsdrv->protocol);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
+static struct driver_attribute vs_server_drv_attrs[] = {
+ __ATTR_RO(protocol),
+ __ATTR_NULL
+};
+#else
+static DRIVER_ATTR_RO(protocol);
+
+static struct attribute *vs_server_drv_attrs[] = {
+ &driver_attr_protocol.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(vs_server_drv);
+#endif
+
+struct bus_type vs_server_bus_type = {
+ .name = "vservices-server",
+ .dev_attrs = vs_server_dev_attrs,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0)
+ .drv_attrs = vs_server_drv_attrs,
+#else
+ .drv_groups = vs_server_drv_groups,
+#endif
+ .match = vs_server_bus_match,
+ .probe = vs_server_bus_probe,
+ .remove = vs_server_bus_remove,
+ .uevent = vs_service_bus_uevent,
+};
+EXPORT_SYMBOL(vs_server_bus_type);
+
+/*
+ * Server session driver
+ */
+static int vs_server_session_probe(struct device *dev)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+ struct vs_service_device *service;
+
+ service = __vs_server_core_register_service(session, 0, NULL,
+ VSERVICE_CORE_SERVICE_NAME,
+ VSERVICE_CORE_PROTOCOL_NAME, NULL);
+ if (IS_ERR(service))
+ return PTR_ERR(service);
+
+ return 0;
+}
+
+static int
+vs_server_session_service_added(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ struct core_server *server = vs_server_session_core_server(session);
+ int err;
+
+ if (WARN_ON(!server || !service->id))
+ return -EINVAL;
+
+ err = vs_server_core_queue_service_created(server, service);
+
+ if (err)
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev,
+ "failed to send service_created: %d\n", err);
+
+ return err;
+}
+
+static int
+vs_server_session_service_start(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ struct core_server *server = vs_server_session_core_server(session);
+ int err;
+
+ if (WARN_ON(!server || !service->id))
+ return -EINVAL;
+
+ err = vs_server_core_queue_service_reset_ready(server,
+ VSERVICE_CORE_CORE_MSG_SERVER_READY, service);
+
+ if (err)
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev,
+ "failed to send server_ready: %d\n", err);
+
+ return err;
+}
+
+static int
+vs_server_session_service_local_reset(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ struct core_server *server = vs_server_session_core_server(session);
+ int err;
+
+ if (WARN_ON(!server || !service->id))
+ return -EINVAL;
+
+ err = vs_server_core_queue_service_reset_ready(server,
+ VSERVICE_CORE_CORE_MSG_SERVICE_RESET, service);
+
+ if (err)
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev,
+ "failed to send service_reset: %d\n", err);
+
+ return err;
+}
+
+static int
+vs_server_session_service_removed(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ struct core_server *server = vs_server_session_core_server(session);
+ int err;
+
+ /*
+ * It's possible for the core server to be forcibly removed before
+ * the other services, for example when the underlying transport
+ * vanishes. If that happens, we can end up here with a NULL core
+ * server pointer.
+ */
+ if (!server)
+ return 0;
+
+ if (WARN_ON(!service->id))
+ return -EINVAL;
+
+ err = vs_server_core_queue_service_removed(server, service);
+ if (err)
+ vs_dev_debug(VS_DEBUG_SERVER_CORE, session, &session->dev,
+ "failed to send service_removed: %d\n", err);
+
+ return err;
+}
+
+static struct vs_session_driver vs_server_session_driver = {
+ .driver = {
+ .name = "vservices-server-session",
+ .owner = THIS_MODULE,
+ .bus = &vs_session_bus_type,
+ .probe = vs_server_session_probe,
+ .suppress_bind_attrs = true,
+ },
+ .is_server = true,
+ .service_bus = &vs_server_bus_type,
+ .service_added = vs_server_session_service_added,
+ .service_start = vs_server_session_service_start,
+ .service_local_reset = vs_server_session_service_local_reset,
+ .service_removed = vs_server_session_service_removed,
+};
+
+static int __init vs_core_server_init(void)
+{
+ int ret;
+
+ ret = bus_register(&vs_server_bus_type);
+ if (ret)
+ goto fail_bus_register;
+
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ vs_devio_server_driver.driver.bus = &vs_server_bus_type;
+ vs_devio_server_driver.driver.owner = THIS_MODULE;
+ ret = driver_register(&vs_devio_server_driver.driver);
+ if (ret)
+ goto fail_devio_register;
+#endif
+
+ ret = driver_register(&vs_server_session_driver.driver);
+ if (ret)
+ goto fail_driver_register;
+
+ ret = vservice_core_server_register(&vs_core_server_driver,
+ "vs_core_server");
+ if (ret)
+ goto fail_core_register;
+
+ vservices_server_root = kobject_create_and_add("server-sessions",
+ vservices_root);
+ if (!vservices_server_root) {
+ ret = -ENOMEM;
+ goto fail_create_root;
+ }
+
+ return 0;
+
+fail_create_root:
+ vservice_core_server_unregister(&vs_core_server_driver);
+fail_core_register:
+ driver_unregister(&vs_server_session_driver.driver);
+fail_driver_register:
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ driver_unregister(&vs_devio_server_driver.driver);
+ vs_devio_server_driver.driver.bus = NULL;
+ vs_devio_server_driver.driver.owner = NULL;
+fail_devio_register:
+#endif
+ bus_unregister(&vs_server_bus_type);
+fail_bus_register:
+ return ret;
+}
+
+static void __exit vs_core_server_exit(void)
+{
+ kobject_put(vservices_server_root);
+ vservice_core_server_unregister(&vs_core_server_driver);
+ driver_unregister(&vs_server_session_driver.driver);
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ driver_unregister(&vs_devio_server_driver.driver);
+ vs_devio_server_driver.driver.bus = NULL;
+ vs_devio_server_driver.driver.owner = NULL;
+#endif
+ bus_unregister(&vs_server_bus_type);
+}
+
+subsys_initcall(vs_core_server_init);
+module_exit(vs_core_server_exit);
+
+MODULE_DESCRIPTION("OKL4 Virtual Services Core Server Driver");
+MODULE_AUTHOR("Open Kernel Labs, Inc");
diff --git a/drivers/vservices/debug.h b/drivers/vservices/debug.h
new file mode 100644
index 0000000..b379b04
--- /dev/null
+++ b/drivers/vservices/debug.h
@@ -0,0 +1,74 @@
+/*
+ * drivers/vservices/debug.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * Debugging macros and support functions for Virtual Services.
+ */
+#ifndef _VSERVICES_DEBUG_H
+#define _VSERVICES_DEBUG_H
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38)
+#include <linux/printk.h>
+#else
+#ifndef no_printk
+#define no_printk(format, args...) do { } while (0)
+#endif
+#endif
+
+#include <vservices/session.h>
+#include "transport.h"
+
+#define VS_DEBUG_TRANSPORT (1 << 0)
+#define VS_DEBUG_TRANSPORT_MESSAGES (1 << 1)
+#define VS_DEBUG_SESSION (1 << 2)
+#define VS_DEBUG_CLIENT (1 << 3)
+#define VS_DEBUG_CLIENT_CORE (1 << 4)
+#define VS_DEBUG_SERVER (1 << 5)
+#define VS_DEBUG_SERVER_CORE (1 << 6)
+#define VS_DEBUG_PROTOCOL (1 << 7)
+#define VS_DEBUG_ALL 0xff
+
+#ifdef CONFIG_VSERVICES_DEBUG
+
+#define vs_debug(type, session, format, args...) \
+ do { \
+ if ((session)->debug_mask & (type)) \
+ dev_dbg(&(session)->dev, format, ##args); \
+ } while (0)
+
+#define vs_dev_debug(type, session, dev, format, args...) \
+ do { \
+ if ((session)->debug_mask & (type)) \
+ dev_dbg(dev, format, ##args); \
+ } while (0)
+
+static inline void vs_debug_dump_mbuf(struct vs_session_device *session,
+ struct vs_mbuf *mbuf)
+{
+ if (session->debug_mask & VS_DEBUG_TRANSPORT_MESSAGES)
+ print_hex_dump_bytes("msg:", DUMP_PREFIX_OFFSET,
+ mbuf->data, mbuf->size);
+}
+
+#else
+
+/* Dummy versions: Use no_printk to retain type/format string checking */
+#define vs_debug(type, session, format, args...) \
+ do { (void)session; no_printk(format, ##args); } while(0)
+
+#define vs_dev_debug(type, session, dev, format, args...) \
+ do { (void)session; (void)dev; no_printk(format, ##args); } while(0)
+
+static inline void vs_debug_dump_mbuf(struct vs_session_device *session,
+ struct vs_mbuf *mbuf) {}
+
+#endif /* CONFIG_VSERVICES_DEBUG */
+
+#endif /* _VSERVICES_DEBUG_H */
diff --git a/drivers/vservices/devio.c b/drivers/vservices/devio.c
new file mode 100644
index 0000000..b3ed4ab
--- /dev/null
+++ b/drivers/vservices/devio.c
@@ -0,0 +1,1059 @@
+/*
+ * devio.c - cdev I/O for service devices
+ *
+ * Copyright (c) 2016 Cog Systems Pty Ltd
+ * Author: Philip Derrin <philip@cog.systems>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/pagemap.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/uio.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#include <linux/security.h>
+#include <linux/compat.h>
+
+#include <vservices/types.h>
+#include <vservices/buffer.h>
+#include <vservices/transport.h>
+#include <vservices/session.h>
+#include <vservices/service.h>
+#include <vservices/ioctl.h>
+#include "session.h"
+
+#define VSERVICES_DEVICE_MAX (VS_MAX_SERVICES * VS_MAX_SESSIONS)
+
+struct vs_devio_priv {
+ struct kref kref;
+ bool running, reset;
+
+ /* Receive queue */
+ wait_queue_head_t recv_wq;
+ atomic_t notify_pending;
+ struct list_head recv_queue;
+};
+
+static void
+vs_devio_priv_free(struct kref *kref)
+{
+ struct vs_devio_priv *priv = container_of(kref, struct vs_devio_priv,
+ kref);
+
+ WARN_ON(priv->running);
+ WARN_ON(!list_empty_careful(&priv->recv_queue));
+ WARN_ON(waitqueue_active(&priv->recv_wq));
+
+ kfree(priv);
+}
+
+static void vs_devio_priv_put(struct vs_devio_priv *priv)
+{
+ kref_put(&priv->kref, vs_devio_priv_free);
+}
+
+static int
+vs_devio_service_probe(struct vs_service_device *service)
+{
+ struct vs_devio_priv *priv;
+
+ priv = kmalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ kref_init(&priv->kref);
+ priv->running = false;
+ priv->reset = false;
+ init_waitqueue_head(&priv->recv_wq);
+ atomic_set(&priv->notify_pending, 0);
+ INIT_LIST_HEAD(&priv->recv_queue);
+
+ dev_set_drvdata(&service->dev, priv);
+
+ wake_up(&service->quota_wq);
+
+ return 0;
+}
+
+static int
+vs_devio_service_remove(struct vs_service_device *service)
+{
+ struct vs_devio_priv *priv = dev_get_drvdata(&service->dev);
+
+ WARN_ON(priv->running);
+ WARN_ON(!list_empty_careful(&priv->recv_queue));
+ WARN_ON(waitqueue_active(&priv->recv_wq));
+
+ vs_devio_priv_put(priv);
+
+ return 0;
+}
+
+static int
+vs_devio_service_receive(struct vs_service_device *service,
+ struct vs_mbuf *mbuf)
+{
+ struct vs_devio_priv *priv = dev_get_drvdata(&service->dev);
+
+ WARN_ON(!priv->running);
+
+ spin_lock(&priv->recv_wq.lock);
+ list_add_tail(&mbuf->queue, &priv->recv_queue);
+ wake_up_locked(&priv->recv_wq);
+ spin_unlock(&priv->recv_wq.lock);
+
+ return 0;
+}
+
+static void
+vs_devio_service_notify(struct vs_service_device *service, u32 flags)
+{
+ struct vs_devio_priv *priv = dev_get_drvdata(&service->dev);
+ int old, cur;
+
+ WARN_ON(!priv->running);
+
+ if (!flags)
+ return;
+
+ /* open-coded atomic_or() */
+ cur = atomic_read(&priv->notify_pending);
+ while ((old = atomic_cmpxchg(&priv->notify_pending,
+ cur, cur | flags)) != cur)
+ cur = old;
+
+ wake_up(&priv->recv_wq);
+}
+
+static void
+vs_devio_service_start(struct vs_service_device *service)
+{
+ struct vs_devio_priv *priv = dev_get_drvdata(&service->dev);
+
+ if (!priv->reset) {
+ WARN_ON(priv->running);
+ priv->running = true;
+ wake_up(&service->quota_wq);
+ }
+}
+
+static void
+vs_devio_service_reset(struct vs_service_device *service)
+{
+ struct vs_devio_priv *priv = dev_get_drvdata(&service->dev);
+ struct vs_mbuf *mbuf, *tmp;
+
+ WARN_ON(!priv->running && !priv->reset);
+
+ /*
+ * Mark the service as being in reset. This flag can never be cleared
+ * on an open device; the user must acknowledge the reset by closing
+ * and reopening the device.
+ */
+ priv->reset = true;
+ priv->running = false;
+
+ spin_lock_irq(&priv->recv_wq.lock);
+ list_for_each_entry_safe(mbuf, tmp, &priv->recv_queue, queue)
+ vs_service_free_mbuf(service, mbuf);
+ INIT_LIST_HEAD(&priv->recv_queue);
+ spin_unlock_irq(&priv->recv_wq.lock);
+ wake_up_all(&priv->recv_wq);
+}
+
+/*
+ * This driver will be registered by the core server module, which must also
+ * set its bus and owner function pointers.
+ */
+struct vs_service_driver vs_devio_server_driver = {
+ /* No protocol, so the normal bus match will never bind this. */
+ .protocol = NULL,
+ .is_server = true,
+ .rx_atomic = true,
+
+ .probe = vs_devio_service_probe,
+ .remove = vs_devio_service_remove,
+ .receive = vs_devio_service_receive,
+ .notify = vs_devio_service_notify,
+ .start = vs_devio_service_start,
+ .reset = vs_devio_service_reset,
+
+ /*
+ * Set reasonable default quotas. These can be overridden by passing
+ * nonzero values to IOCTL_VS_BIND_SERVER, which will set the
+ * service's *_quota_set fields.
+ */
+ .in_quota_min = 1,
+ .in_quota_best = 8,
+ .out_quota_min = 1,
+ .out_quota_best = 8,
+
+ /* Mark the notify counts as invalid; the service's will be used. */
+ .in_notify_count = (unsigned)-1,
+ .out_notify_count = (unsigned)-1,
+
+ .driver = {
+ .name = "vservices-server-devio",
+ .owner = NULL, /* set by core server */
+ .bus = NULL, /* set by core server */
+ .suppress_bind_attrs = true, /* see vs_devio_poll */
+ },
+};
+EXPORT_SYMBOL_GPL(vs_devio_server_driver);
+
+static int
+vs_devio_bind_server(struct vs_service_device *service,
+ struct vs_ioctl_bind *bind)
+{
+ int ret = -ENODEV;
+
+ /* Ensure the server module is loaded and the driver is registered. */
+ if (!try_module_get(vs_devio_server_driver.driver.owner))
+ goto fail_module_get;
+
+ device_lock(&service->dev);
+ ret = -EBUSY;
+ if (service->dev.driver != NULL)
+ goto fail_device_unbound;
+
+ /* Set up the quota and notify counts. */
+ service->in_quota_set = bind->recv_quota;
+ service->out_quota_set = bind->send_quota;
+ service->notify_send_bits = bind->send_notify_bits;
+ service->notify_recv_bits = bind->recv_notify_bits;
+
+ /* Manually probe the driver. */
+ service->dev.driver = &vs_devio_server_driver.driver;
+ ret = service->dev.bus->probe(&service->dev);
+ if (ret < 0)
+ goto fail_probe_driver;
+
+ ret = device_bind_driver(&service->dev);
+ if (ret < 0)
+ goto fail_bind_driver;
+
+ /* Pass the allocated quotas back to the user. */
+ bind->recv_quota = service->recv_quota;
+ bind->send_quota = service->send_quota;
+ bind->msg_size = vs_service_max_mbuf_size(service);
+
+ device_unlock(&service->dev);
+ module_put(vs_devio_server_driver.driver.owner);
+
+ return 0;
+
+fail_bind_driver:
+ ret = service->dev.bus->remove(&service->dev);
+fail_probe_driver:
+ service->dev.driver = NULL;
+fail_device_unbound:
+ device_unlock(&service->dev);
+ module_put(vs_devio_server_driver.driver.owner);
+fail_module_get:
+ return ret;
+}
+
+/*
+ * This driver will be registered by the core client module, which must also
+ * set its bus and owner pointers.
+ */
+struct vs_service_driver vs_devio_client_driver = {
+ /* No protocol, so the normal bus match will never bind this. */
+ .protocol = NULL,
+ .is_server = false,
+ .rx_atomic = true,
+
+ .probe = vs_devio_service_probe,
+ .remove = vs_devio_service_remove,
+ .receive = vs_devio_service_receive,
+ .notify = vs_devio_service_notify,
+ .start = vs_devio_service_start,
+ .reset = vs_devio_service_reset,
+
+ .driver = {
+ .name = "vservices-client-devio",
+ .owner = NULL, /* set by core client */
+ .bus = NULL, /* set by core client */
+ .suppress_bind_attrs = true, /* see vs_devio_poll */
+ },
+};
+EXPORT_SYMBOL_GPL(vs_devio_client_driver);
+
+static int
+vs_devio_bind_client(struct vs_service_device *service,
+ struct vs_ioctl_bind *bind)
+{
+ int ret = -ENODEV;
+
+ /* Ensure the client module is loaded and the driver is registered. */
+ if (!try_module_get(vs_devio_client_driver.driver.owner))
+ goto fail_module_get;
+
+ device_lock(&service->dev);
+ ret = -EBUSY;
+ if (service->dev.driver != NULL)
+ goto fail_device_unbound;
+
+ /* Manually probe the driver. */
+ service->dev.driver = &vs_devio_client_driver.driver;
+ ret = service->dev.bus->probe(&service->dev);
+ if (ret < 0)
+ goto fail_probe_driver;
+
+ ret = device_bind_driver(&service->dev);
+ if (ret < 0)
+ goto fail_bind_driver;
+
+ /* Pass the allocated quotas back to the user. */
+ bind->recv_quota = service->recv_quota;
+ bind->send_quota = service->send_quota;
+ bind->msg_size = vs_service_max_mbuf_size(service);
+ bind->send_notify_bits = service->notify_send_bits;
+ bind->recv_notify_bits = service->notify_recv_bits;
+
+ device_unlock(&service->dev);
+ module_put(vs_devio_client_driver.driver.owner);
+
+ return 0;
+
+fail_bind_driver:
+ ret = service->dev.bus->remove(&service->dev);
+fail_probe_driver:
+ service->dev.driver = NULL;
+fail_device_unbound:
+ device_unlock(&service->dev);
+ module_put(vs_devio_client_driver.driver.owner);
+fail_module_get:
+ return ret;
+}
+
+static struct vs_devio_priv *
+vs_devio_priv_get_from_service(struct vs_service_device *service)
+{
+ struct vs_devio_priv *priv = NULL;
+ struct device_driver *drv;
+
+ if (!service)
+ return NULL;
+
+ device_lock(&service->dev);
+ drv = service->dev.driver;
+
+ if ((drv == &vs_devio_client_driver.driver) ||
+ (drv == &vs_devio_server_driver.driver)) {
+ vs_service_state_lock(service);
+ priv = dev_get_drvdata(&service->dev);
+ if (priv)
+ kref_get(&priv->kref);
+ vs_service_state_unlock(service);
+ }
+
+ device_unlock(&service->dev);
+
+ return priv;
+}
+
+static int
+vs_devio_open(struct inode *inode, struct file *file)
+{
+ struct vs_service_device *service;
+
+ if (imajor(inode) != vservices_cdev_major)
+ return -ENODEV;
+
+ service = vs_service_lookup_by_devt(inode->i_rdev);
+ if (!service)
+ return -ENODEV;
+
+ file->private_data = service;
+
+ return 0;
+}
+
+static int
+vs_devio_release(struct inode *inode, struct file *file)
+{
+ struct vs_service_device *service = file->private_data;
+
+ if (service) {
+ struct vs_devio_priv *priv =
+ vs_devio_priv_get_from_service(service);
+
+ if (priv) {
+ device_release_driver(&service->dev);
+ vs_devio_priv_put(priv);
+ }
+
+ file->private_data = NULL;
+ vs_put_service(service);
+ }
+
+ return 0;
+}
+
+static struct iovec *
+vs_devio_check_iov(struct vs_ioctl_iovec *io, bool is_send, ssize_t *total)
+{
+ struct iovec *iov;
+ unsigned i;
+ int ret;
+
+ if (io->iovcnt > UIO_MAXIOV)
+ return ERR_PTR(-EINVAL);
+
+ iov = kmalloc(sizeof(*iov) * io->iovcnt, GFP_KERNEL);
+ if (!iov)
+ return ERR_PTR(-ENOMEM);
+
+ if (copy_from_user(iov, io->iov, sizeof(*iov) * io->iovcnt)) {
+ ret = -EFAULT;
+ goto fail;
+ }
+
+ *total = 0;
+ for (i = 0; i < io->iovcnt; i++) {
+ ssize_t iov_len = (ssize_t)iov[i].iov_len;
+
+ if (iov_len > MAX_RW_COUNT - *total) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!access_ok(is_send ? VERIFY_READ : VERIFY_WRITE,
+ iov[i].iov_base, iov_len)) {
+ ret = -EFAULT;
+ goto fail;
+ }
+
+ *total += iov_len;
+ }
+
+ return iov;
+
+fail:
+ kfree(iov);
+ return ERR_PTR(ret);
+}
+
+static ssize_t
+vs_devio_send(struct vs_service_device *service, struct iovec *iov,
+ size_t iovcnt, ssize_t to_send, bool nonblocking)
+{
+ struct vs_mbuf *mbuf = NULL;
+ struct vs_devio_priv *priv;
+ unsigned i;
+ ssize_t offset = 0;
+ ssize_t ret;
+ DEFINE_WAIT(wait);
+
+ priv = vs_devio_priv_get_from_service(service);
+ ret = -ENODEV;
+ if (!priv)
+ goto fail_priv_get;
+
+ vs_service_state_lock(service);
+
+ /*
+ * Waiting alloc. We must open-code this because there is no real
+ * state structure or base state.
+ */
+ ret = 0;
+ while (!vs_service_send_mbufs_available(service)) {
+ if (nonblocking) {
+ ret = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+
+ prepare_to_wait_exclusive(&service->quota_wq, &wait,
+ TASK_INTERRUPTIBLE);
+
+ vs_service_state_unlock(service);
+ schedule();
+ vs_service_state_lock(service);
+
+ if (priv->reset) {
+ ret = -ECONNRESET;
+ break;
+ }
+
+ if (!priv->running) {
+ ret = -ENOTCONN;
+ break;
+ }
+ }
+ finish_wait(&service->quota_wq, &wait);
+
+ if (ret)
+ goto fail_alloc;
+
+ mbuf = vs_service_alloc_mbuf(service, to_send, GFP_KERNEL);
+ if (IS_ERR(mbuf)) {
+ ret = PTR_ERR(mbuf);
+ goto fail_alloc;
+ }
+
+ /* Ready to send; copy data into the mbuf. */
+ ret = -EFAULT;
+ for (i = 0; i < iovcnt; i++) {
+ if (copy_from_user(mbuf->data + offset, iov[i].iov_base,
+ iov[i].iov_len))
+ goto fail_copy;
+ offset += iov[i].iov_len;
+ }
+ mbuf->size = to_send;
+
+ /* Send the message. */
+ ret = vs_service_send(service, mbuf);
+ if (ret < 0)
+ goto fail_send;
+
+ /* Wake the next waiter, if there's more quota available. */
+ if (waitqueue_active(&service->quota_wq) &&
+ vs_service_send_mbufs_available(service) > 0)
+ wake_up(&service->quota_wq);
+
+ vs_service_state_unlock(service);
+ vs_devio_priv_put(priv);
+
+ return to_send;
+
+fail_send:
+fail_copy:
+ vs_service_free_mbuf(service, mbuf);
+ wake_up(&service->quota_wq);
+fail_alloc:
+ vs_service_state_unlock(service);
+ vs_devio_priv_put(priv);
+fail_priv_get:
+ return ret;
+}
+
+static ssize_t
+vs_devio_recv(struct vs_service_device *service, struct iovec *iov,
+ size_t iovcnt, u32 *notify_bits, ssize_t recv_space,
+ bool nonblocking)
+{
+ struct vs_mbuf *mbuf = NULL;
+ struct vs_devio_priv *priv;
+ unsigned i;
+ ssize_t offset = 0;
+ ssize_t ret;
+ DEFINE_WAIT(wait);
+
+ priv = vs_devio_priv_get_from_service(service);
+ ret = -ENODEV;
+ if (!priv)
+ goto fail_priv_get;
+
+ /* Take the recv_wq lock, which also protects recv_queue. */
+ spin_lock_irq(&priv->recv_wq.lock);
+
+ /* Wait for a message, notification, or reset. */
+ ret = wait_event_interruptible_exclusive_locked_irq(priv->recv_wq,
+ !list_empty(&priv->recv_queue) || priv->reset ||
+ atomic_read(&priv->notify_pending) || nonblocking);
+
+ if (priv->reset)
+ ret = -ECONNRESET; /* Service reset */
+ else if (!ret && list_empty(&priv->recv_queue))
+ ret = -EAGAIN; /* Nonblocking, or notification */
+
+ if (ret < 0) {
+ spin_unlock_irq(&priv->recv_wq.lock);
+ goto no_mbuf;
+ }
+
+ /* Take the first mbuf from the list, and check its size. */
+ mbuf = list_first_entry(&priv->recv_queue, struct vs_mbuf, queue);
+ if (mbuf->size > recv_space) {
+ spin_unlock_irq(&priv->recv_wq.lock);
+ ret = -EMSGSIZE;
+ goto fail_msg_size;
+ }
+ list_del_init(&mbuf->queue);
+
+ spin_unlock_irq(&priv->recv_wq.lock);
+
+ /* Copy to user. */
+ ret = -EFAULT;
+ for (i = 0; (mbuf->size > offset) && (i < iovcnt); i++) {
+ size_t len = min(mbuf->size - offset, iov[i].iov_len);
+ if (copy_to_user(iov[i].iov_base, mbuf->data + offset, len))
+ goto fail_copy;
+ offset += len;
+ }
+ ret = offset;
+
+no_mbuf:
+ /*
+ * Read and clear the pending notification bits. If any notifications
+ * are received, don't return an error, even if we failed to receive a
+ * message.
+ */
+ *notify_bits = atomic_xchg(&priv->notify_pending, 0);
+ if ((ret < 0) && *notify_bits)
+ ret = 0;
+
+fail_copy:
+ if (mbuf)
+ vs_service_free_mbuf(service, mbuf);
+fail_msg_size:
+ vs_devio_priv_put(priv);
+fail_priv_get:
+ return ret;
+}
+
+static int
+vs_devio_check_perms(struct file *file, unsigned flags)
+{
+ if ((flags & MAY_READ) & !(file->f_mode & FMODE_READ))
+ return -EBADF;
+
+ if ((flags & MAY_WRITE) & !(file->f_mode & FMODE_WRITE))
+ return -EBADF;
+
+ return security_file_permission(file, flags);
+}
+
+static long
+vs_devio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *ptr = (void __user *)arg;
+ struct vs_service_device *service = file->private_data;
+ struct vs_ioctl_bind bind;
+ struct vs_ioctl_iovec io;
+ u32 flags;
+ long ret;
+ ssize_t iov_total;
+ struct iovec *iov;
+
+ if (!service)
+ return -ENODEV;
+
+ switch (cmd) {
+ case IOCTL_VS_RESET_SERVICE:
+ ret = vs_devio_check_perms(file, MAY_WRITE);
+ if (ret < 0)
+ break;
+ ret = vs_service_reset(service, service);
+ break;
+ case IOCTL_VS_GET_NAME:
+ ret = vs_devio_check_perms(file, MAY_READ);
+ if (ret < 0)
+ break;
+ if (service->name != NULL) {
+ size_t len = strnlen(service->name,
+ _IOC_SIZE(IOCTL_VS_GET_NAME) - 1);
+ if (copy_to_user(ptr, service->name, len + 1))
+ ret = -EFAULT;
+ } else {
+ ret = -EINVAL;
+ }
+ break;
+ case IOCTL_VS_GET_PROTOCOL:
+ ret = vs_devio_check_perms(file, MAY_READ);
+ if (ret < 0)
+ break;
+ if (service->protocol != NULL) {
+ size_t len = strnlen(service->protocol,
+ _IOC_SIZE(IOCTL_VS_GET_PROTOCOL) - 1);
+ if (copy_to_user(ptr, service->protocol, len + 1))
+ ret = -EFAULT;
+ } else {
+ ret = -EINVAL;
+ }
+ break;
+ case IOCTL_VS_BIND_CLIENT:
+ ret = vs_devio_check_perms(file, MAY_EXEC);
+ if (ret < 0)
+ break;
+ ret = vs_devio_bind_client(service, &bind);
+ if (!ret && copy_to_user(ptr, &bind, sizeof(bind)))
+ ret = -EFAULT;
+ break;
+ case IOCTL_VS_BIND_SERVER:
+ ret = vs_devio_check_perms(file, MAY_EXEC);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&bind, ptr, sizeof(bind))) {
+ ret = -EFAULT;
+ break;
+ }
+ ret = vs_devio_bind_server(service, &bind);
+ if (!ret && copy_to_user(ptr, &bind, sizeof(bind)))
+ ret = -EFAULT;
+ break;
+ case IOCTL_VS_NOTIFY:
+ ret = vs_devio_check_perms(file, MAY_WRITE);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&flags, ptr, sizeof(flags))) {
+ ret = -EFAULT;
+ break;
+ }
+ ret = vs_service_notify(service, flags);
+ break;
+ case IOCTL_VS_SEND:
+ ret = vs_devio_check_perms(file, MAY_WRITE);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&io, ptr, sizeof(io))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ iov = vs_devio_check_iov(&io, true, &iov_total);
+ if (IS_ERR(iov)) {
+ ret = PTR_ERR(iov);
+ break;
+ }
+
+ ret = vs_devio_send(service, iov, io.iovcnt, iov_total,
+ file->f_flags & O_NONBLOCK);
+ kfree(iov);
+ break;
+ case IOCTL_VS_RECV:
+ ret = vs_devio_check_perms(file, MAY_READ);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&io, ptr, sizeof(io))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ iov = vs_devio_check_iov(&io, true, &iov_total);
+ if (IS_ERR(iov)) {
+ ret = PTR_ERR(iov);
+ break;
+ }
+
+ ret = vs_devio_recv(service, iov, io.iovcnt,
+ &io.notify_bits, iov_total,
+ file->f_flags & O_NONBLOCK);
+ kfree(iov);
+
+ if (ret >= 0) {
+ u32 __user *notify_bits_ptr = ptr + offsetof(
+ struct vs_ioctl_iovec, notify_bits);
+ if (copy_to_user(notify_bits_ptr, &io.notify_bits,
+ sizeof(io.notify_bits)))
+ ret = -EFAULT;
+ }
+ break;
+ default:
+ dev_dbg(&service->dev, "Unknown ioctl %#x, arg: %lx\n", cmd,
+ arg);
+ ret = -ENOSYS;
+ break;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_COMPAT
+
+struct vs_compat_ioctl_bind {
+ __u32 send_quota;
+ __u32 recv_quota;
+ __u32 send_notify_bits;
+ __u32 recv_notify_bits;
+ compat_size_t msg_size;
+};
+
+#define compat_ioctl_bind_conv(dest, src) ({ \
+ dest.send_quota = src.send_quota; \
+ dest.recv_quota = src.recv_quota; \
+ dest.send_notify_bits = src.send_notify_bits; \
+ dest.recv_notify_bits = src.recv_notify_bits; \
+ dest.msg_size = (compat_size_t)src.msg_size; \
+})
+
+#define COMPAT_IOCTL_VS_BIND_CLIENT _IOR('4', 3, struct vs_compat_ioctl_bind)
+#define COMPAT_IOCTL_VS_BIND_SERVER _IOWR('4', 4, struct vs_compat_ioctl_bind)
+
+struct vs_compat_ioctl_iovec {
+ union {
+ __u32 iovcnt; /* input */
+ __u32 notify_bits; /* output (recv only) */
+ };
+ compat_uptr_t iov;
+};
+
+#define COMPAT_IOCTL_VS_SEND \
+ _IOW('4', 6, struct vs_compat_ioctl_iovec)
+#define COMPAT_IOCTL_VS_RECV \
+ _IOWR('4', 7, struct vs_compat_ioctl_iovec)
+
+static struct iovec *
+vs_devio_check_compat_iov(struct vs_compat_ioctl_iovec *c_io,
+ bool is_send, ssize_t *total)
+{
+ struct iovec *iov;
+ struct compat_iovec *c_iov;
+
+ unsigned i;
+ int ret;
+
+ if (c_io->iovcnt > UIO_MAXIOV)
+ return ERR_PTR(-EINVAL);
+
+ c_iov = kzalloc(sizeof(*c_iov) * c_io->iovcnt, GFP_KERNEL);
+ if (!c_iov)
+ return ERR_PTR(-ENOMEM);
+
+ iov = kzalloc(sizeof(*iov) * c_io->iovcnt, GFP_KERNEL);
+ if (!iov) {
+ kfree(c_iov);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ if (copy_from_user(c_iov, (struct compat_iovec __user *)
+ compat_ptr(c_io->iov), sizeof(*c_iov) * c_io->iovcnt)) {
+ ret = -EFAULT;
+ goto fail;
+ }
+
+ *total = 0;
+ for (i = 0; i < c_io->iovcnt; i++) {
+ ssize_t iov_len;
+ iov[i].iov_base = compat_ptr (c_iov[i].iov_base);
+ iov[i].iov_len = (compat_size_t) c_iov[i].iov_len;
+
+ iov_len = (ssize_t)iov[i].iov_len;
+
+ if (iov_len > MAX_RW_COUNT - *total) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!access_ok(is_send ? VERIFY_READ : VERIFY_WRITE,
+ iov[i].iov_base, iov_len)) {
+ ret = -EFAULT;
+ goto fail;
+ }
+
+ *total += iov_len;
+ }
+
+ kfree (c_iov);
+ return iov;
+
+fail:
+ kfree(c_iov);
+ kfree(iov);
+ return ERR_PTR(ret);
+}
+
+static long
+vs_devio_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *ptr = (void __user *)arg;
+ struct vs_service_device *service = file->private_data;
+ struct vs_ioctl_bind bind;
+ struct vs_compat_ioctl_bind compat_bind;
+ struct vs_compat_ioctl_iovec compat_io;
+ long ret;
+ ssize_t iov_total;
+ struct iovec *iov;
+
+ if (!service)
+ return -ENODEV;
+
+ switch (cmd) {
+ case IOCTL_VS_RESET_SERVICE:
+ case IOCTL_VS_GET_NAME:
+ case IOCTL_VS_GET_PROTOCOL:
+ return vs_devio_ioctl (file, cmd, arg);
+ case COMPAT_IOCTL_VS_SEND:
+ ret = vs_devio_check_perms(file, MAY_WRITE);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&compat_io, ptr, sizeof(compat_io))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ iov = vs_devio_check_compat_iov(&compat_io, true, &iov_total);
+ if (IS_ERR(iov)) {
+ ret = PTR_ERR(iov);
+ break;
+ }
+
+ ret = vs_devio_send(service, iov, compat_io.iovcnt, iov_total,
+ file->f_flags & O_NONBLOCK);
+ kfree(iov);
+
+ break;
+ case COMPAT_IOCTL_VS_RECV:
+ ret = vs_devio_check_perms(file, MAY_READ);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&compat_io, ptr, sizeof(compat_io))) {
+ ret = -EFAULT;
+ break;
+ }
+
+ iov = vs_devio_check_compat_iov(&compat_io, true, &iov_total);
+ if (IS_ERR(iov)) {
+ ret = PTR_ERR(iov);
+ break;
+ }
+
+ ret = vs_devio_recv(service, iov, compat_io.iovcnt,
+ &compat_io.notify_bits, iov_total,
+ file->f_flags & O_NONBLOCK);
+ kfree(iov);
+
+ if (ret >= 0) {
+ u32 __user *notify_bits_ptr = ptr + offsetof(
+ struct vs_compat_ioctl_iovec, notify_bits);
+ if (copy_to_user(notify_bits_ptr, &compat_io.notify_bits,
+ sizeof(compat_io.notify_bits)))
+ ret = -EFAULT;
+ }
+ break;
+ case COMPAT_IOCTL_VS_BIND_CLIENT:
+ ret = vs_devio_check_perms(file, MAY_EXEC);
+ if (ret < 0)
+ break;
+ ret = vs_devio_bind_client(service, &bind);
+ compat_ioctl_bind_conv(compat_bind, bind);
+ if (!ret && copy_to_user(ptr, &compat_bind,
+ sizeof(compat_bind)))
+ ret = -EFAULT;
+ break;
+ case COMPAT_IOCTL_VS_BIND_SERVER:
+ ret = vs_devio_check_perms(file, MAY_EXEC);
+ if (ret < 0)
+ break;
+ if (copy_from_user(&compat_bind, ptr, sizeof(compat_bind))) {
+ ret = -EFAULT;
+ break;
+ }
+ compat_ioctl_bind_conv(bind, compat_bind);
+ ret = vs_devio_bind_server(service, &bind);
+ compat_ioctl_bind_conv(compat_bind, bind);
+ if (!ret && copy_to_user(ptr, &compat_bind,
+ sizeof(compat_bind)))
+ ret = -EFAULT;
+ break;
+ default:
+ dev_dbg(&service->dev, "Unknown ioctl %#x, arg: %lx\n", cmd,
+ arg);
+ ret = -ENOSYS;
+ break;
+ }
+
+ return ret;
+}
+
+#endif /* CONFIG_COMPAT */
+
+static unsigned int
+vs_devio_poll(struct file *file, struct poll_table_struct *wait)
+{
+ struct vs_service_device *service = file->private_data;
+ struct vs_devio_priv *priv = vs_devio_priv_get_from_service(service);
+ unsigned int flags = 0;
+
+ poll_wait(file, &service->quota_wq, wait);
+
+ if (priv) {
+ /*
+ * Note: there is no way for us to ensure that all poll
+ * waiters on a given workqueue have gone away, other than to
+ * actually close the file. So, this poll_wait() is only safe
+ * if we never release our claim on the service before the
+ * file is closed.
+ *
+ * We try to guarantee this by only unbinding the devio driver
+ * on close, and setting suppress_bind_attrs in the driver so
+ * root can't unbind us with sysfs.
+ */
+ poll_wait(file, &priv->recv_wq, wait);
+
+ if (priv->reset) {
+ /* Service reset; raise poll error. */
+ flags |= POLLERR | POLLHUP;
+ } else if (priv->running) {
+ if (!list_empty_careful(&priv->recv_queue))
+ flags |= POLLRDNORM | POLLIN;
+ if (atomic_read(&priv->notify_pending))
+ flags |= POLLRDNORM | POLLIN;
+ if (vs_service_send_mbufs_available(service) > 0)
+ flags |= POLLWRNORM | POLLOUT;
+ }
+
+ vs_devio_priv_put(priv);
+ } else {
+ /* No driver attached. Return error flags. */
+ flags |= POLLERR | POLLHUP;
+ }
+
+ return flags;
+}
+
+static const struct file_operations vs_fops = {
+ .owner = THIS_MODULE,
+ .open = vs_devio_open,
+ .release = vs_devio_release,
+ .unlocked_ioctl = vs_devio_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = vs_devio_compat_ioctl,
+#endif
+ .poll = vs_devio_poll,
+};
+
+int vservices_cdev_major;
+static struct cdev vs_cdev;
+
+int __init
+vs_devio_init(void)
+{
+ dev_t dev;
+ int r;
+
+ r = alloc_chrdev_region(&dev, 0, VSERVICES_DEVICE_MAX,
+ "vs_service");
+ if (r < 0)
+ goto fail_alloc_chrdev;
+ vservices_cdev_major = MAJOR(dev);
+
+ cdev_init(&vs_cdev, &vs_fops);
+ r = cdev_add(&vs_cdev, dev, VSERVICES_DEVICE_MAX);
+ if (r < 0)
+ goto fail_cdev_add;
+
+ return 0;
+
+fail_cdev_add:
+ unregister_chrdev_region(dev, VSERVICES_DEVICE_MAX);
+fail_alloc_chrdev:
+ return r;
+}
+
+void __exit
+vs_devio_exit(void)
+{
+ cdev_del(&vs_cdev);
+ unregister_chrdev_region(MKDEV(vservices_cdev_major, 0),
+ VSERVICES_DEVICE_MAX);
+}
diff --git a/drivers/vservices/protocol/Kconfig b/drivers/vservices/protocol/Kconfig
new file mode 100644
index 0000000..a4c622b
--- /dev/null
+++ b/drivers/vservices/protocol/Kconfig
@@ -0,0 +1,11 @@
+#
+# vServices protocol drivers configuration
+#
+
+if VSERVICES_SERVER || VSERVICES_CLIENT
+
+menu "Protocol drivers"
+
+endmenu
+
+endif # VSERVICES_SERVER || VSERVICES_CLIENT
diff --git a/drivers/vservices/protocol/Makefile b/drivers/vservices/protocol/Makefile
new file mode 100644
index 0000000..a2162c8
--- /dev/null
+++ b/drivers/vservices/protocol/Makefile
@@ -0,0 +1,3 @@
+# This is a autogenerated Makefile for vservice-linux-stacks
+
+obj-$(CONFIG_VSERVICES_SUPPORT) += core/
diff --git a/drivers/vservices/protocol/core/Makefile b/drivers/vservices/protocol/core/Makefile
new file mode 100644
index 0000000..6bef7f5
--- /dev/null
+++ b/drivers/vservices/protocol/core/Makefile
@@ -0,0 +1,7 @@
+ccflags-y += -Werror
+
+obj-$(CONFIG_VSERVICES_SERVER) += vservices_protocol_core_server.o
+vservices_protocol_core_server-objs = server.o
+
+obj-$(CONFIG_VSERVICES_CLIENT) += vservices_protocol_core_client.o
+vservices_protocol_core_client-objs = client.o
diff --git a/drivers/vservices/protocol/core/client.c b/drivers/vservices/protocol/core/client.c
new file mode 100644
index 0000000..2dd2136
--- /dev/null
+++ b/drivers/vservices/protocol/core/client.c
@@ -0,0 +1,1069 @@
+
+/*
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+ /*
+ * This is the generated code for the core client protocol handling.
+ */
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)
+#include <linux/export.h>
+#endif
+
+#include <vservices/types.h>
+#include <vservices/buffer.h>
+#include <vservices/protocol/core/types.h>
+#include <vservices/protocol/core/common.h>
+#include <vservices/protocol/core/client.h>
+#include <vservices/service.h>
+
+#include "../../transport.h"
+
+#define VS_MBUF_SIZE(mbuf) mbuf->size
+#define VS_MBUF_DATA(mbuf) mbuf->data
+#define VS_STATE_SERVICE_PTR(state) state->service
+
+/*** Linux driver model integration ***/
+struct vs_core_client_driver {
+ struct vs_client_core *client;
+ struct list_head list;
+ struct vs_service_driver vsdrv;
+};
+
+#define to_client_driver(d) \
+ container_of(d, struct vs_core_client_driver, vsdrv)
+
+static void core_handle_start(struct vs_service_device *service)
+{
+
+ struct vs_client_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client __maybe_unused =
+ to_client_driver(vsdrv)->client;
+
+ vs_service_state_lock(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (client->start)
+ client->start(state);
+ vs_service_state_unlock(service);
+}
+
+static void core_handle_reset(struct vs_service_device *service)
+{
+
+ struct vs_client_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client __maybe_unused =
+ to_client_driver(vsdrv)->client;
+
+ vs_service_state_lock(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (client->reset)
+ client->reset(state);
+ vs_service_state_unlock(service);
+}
+
+static void core_handle_start_bh(struct vs_service_device *service)
+{
+
+ struct vs_client_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client __maybe_unused =
+ to_client_driver(vsdrv)->client;
+
+ vs_service_state_lock_bh(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (client->start)
+ client->start(state);
+ vs_service_state_unlock_bh(service);
+}
+
+static void core_handle_reset_bh(struct vs_service_device *service)
+{
+
+ struct vs_client_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client __maybe_unused =
+ to_client_driver(vsdrv)->client;
+
+ vs_service_state_lock_bh(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (client->reset)
+ client->reset(state);
+ vs_service_state_unlock_bh(service);
+}
+
+static int core_client_probe(struct vs_service_device *service);
+static int core_client_remove(struct vs_service_device *service);
+static int core_handle_message(struct vs_service_device *service,
+ struct vs_mbuf *_mbuf);
+static void core_handle_notify(struct vs_service_device *service,
+ uint32_t flags);
+static void core_handle_start(struct vs_service_device *service);
+static void core_handle_start_bh(struct vs_service_device *service);
+static void core_handle_reset(struct vs_service_device *service);
+static void core_handle_reset_bh(struct vs_service_device *service);
+static int core_handle_tx_ready(struct vs_service_device *service);
+
+int __vservice_core_client_register(struct vs_client_core *client,
+ const char *name, struct module *owner)
+{
+ int ret;
+ struct vs_core_client_driver *driver;
+
+ if (client->tx_atomic && !client->rx_atomic)
+ return -EINVAL;
+
+ driver = kzalloc(sizeof(*driver), GFP_KERNEL);
+ if (!driver) {
+ ret = -ENOMEM;
+ goto fail_alloc_driver;
+ }
+
+ client->driver = &driver->vsdrv;
+ driver->client = client;
+
+ driver->vsdrv.protocol = VSERVICE_CORE_PROTOCOL_NAME;
+
+ driver->vsdrv.is_server = false;
+ driver->vsdrv.rx_atomic = client->rx_atomic;
+ driver->vsdrv.tx_atomic = client->tx_atomic;
+
+ driver->vsdrv.probe = core_client_probe;
+ driver->vsdrv.remove = core_client_remove;
+ driver->vsdrv.receive = core_handle_message;
+ driver->vsdrv.notify = core_handle_notify;
+ driver->vsdrv.start = client->tx_atomic ?
+ core_handle_start_bh : core_handle_start;
+ driver->vsdrv.reset = client->tx_atomic ?
+ core_handle_reset_bh : core_handle_reset;
+ driver->vsdrv.tx_ready = core_handle_tx_ready;
+ driver->vsdrv.out_notify_count = 0;
+ driver->vsdrv.in_notify_count = 0;
+ driver->vsdrv.driver.name = name;
+ driver->vsdrv.driver.owner = owner;
+ driver->vsdrv.driver.bus = &vs_client_bus_type;
+
+ ret = driver_register(&driver->vsdrv.driver);
+
+ if (ret) {
+ goto fail_driver_register;
+ }
+
+ return 0;
+
+ fail_driver_register:
+ client->driver = NULL;
+ kfree(driver);
+ fail_alloc_driver:
+ return ret;
+}
+
+EXPORT_SYMBOL(__vservice_core_client_register);
+
+int vservice_core_client_unregister(struct vs_client_core *client)
+{
+ struct vs_core_client_driver *driver;
+
+ if (!client->driver)
+ return 0;
+
+ driver = to_client_driver(client->driver);
+ driver_unregister(&driver->vsdrv.driver);
+
+ client->driver = NULL;
+ kfree(driver);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vservice_core_client_unregister);
+
+static int core_client_probe(struct vs_service_device *service)
+{
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client = to_client_driver(vsdrv)->client;
+ struct vs_client_core_state *state;
+
+ state = client->alloc(service);
+ if (!state)
+ return -ENOMEM;
+ else if (IS_ERR(state))
+ return PTR_ERR(state);
+
+ state->service = vs_get_service(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+
+ dev_set_drvdata(&service->dev, state);
+
+ return 0;
+}
+
+static int core_client_remove(struct vs_service_device *service)
+{
+ struct vs_client_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client = to_client_driver(vsdrv)->client;
+
+ state->released = true;
+ dev_set_drvdata(&service->dev, NULL);
+ client->release(state);
+
+ vs_put_service(service);
+
+ return 0;
+}
+
+static int core_handle_tx_ready(struct vs_service_device *service)
+{
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_client_core *client = to_client_driver(vsdrv)->client;
+ struct vs_client_core_state *state = dev_get_drvdata(&service->dev);
+
+ if (client->tx_ready)
+ client->tx_ready(state);
+
+ return 0;
+}
+
+int vs_client_core_core_getbufs_service_created(struct vs_client_core_state
+ *_state,
+ struct vs_string *service_name,
+ struct vs_string *protocol_name,
+ struct vs_mbuf *_mbuf)
+{
+ const vs_message_id_t _msg_id = VSERVICE_CORE_CORE_MSG_SERVICE_CREATED;
+ const size_t _max_size =
+ sizeof(vs_message_id_t) + VSERVICE_CORE_SERVICE_NAME_SIZE +
+ VSERVICE_CORE_PROTOCOL_NAME_SIZE + 4UL;
+ const size_t _min_size = _max_size - VSERVICE_CORE_PROTOCOL_NAME_SIZE;
+ size_t _exact_size;
+
+ if (*(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) != _msg_id)
+ return -EINVAL;
+ if ((VS_MBUF_SIZE(_mbuf) > _max_size)
+ || (VS_MBUF_SIZE(_mbuf) < _min_size))
+ return -EBADMSG;
+
+ service_name->ptr =
+ (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL);
+ service_name->max_size = VSERVICE_CORE_SERVICE_NAME_SIZE;
+
+ protocol_name->ptr =
+ (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL);
+ protocol_name->max_size =
+ VS_MBUF_SIZE(_mbuf) - (sizeof(vs_message_id_t) +
+ VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL);
+
+ /* Now check the size received is the exact size expected */
+ _exact_size =
+ _max_size - (VSERVICE_CORE_PROTOCOL_NAME_SIZE -
+ protocol_name->max_size);
+ if (VS_MBUF_SIZE(_mbuf) != _exact_size)
+ return -EBADMSG;
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_getbufs_service_created);
+int vs_client_core_core_free_service_created(struct vs_client_core_state
+ *_state,
+ struct vs_string *service_name,
+ struct vs_string *protocol_name,
+ struct vs_mbuf *_mbuf)
+{
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_free_service_created);
+int
+vs_client_core_core_req_connect(struct vs_client_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_client_core *_client =
+ to_client_driver(vsdrv)->client;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_REQ_CONNECT;
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED__CONNECT;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT);
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_req_connect);
+int
+vs_client_core_core_req_disconnect(struct vs_client_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_client_core *_client =
+ to_client_driver(vsdrv)->client;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_REQ_DISCONNECT;
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED__DISCONNECT;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT);
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_req_disconnect);
+static int
+core_core_handle_ack_connect(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 0UL;
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT,
+ VSERVICE_CORE_STATE_CONNECTED);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.ack_connect)
+ return _client->core.ack_connect(_state);
+ return 0;
+}
+
+static int
+core_core_handle_nack_connect(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT,
+ VSERVICE_CORE_STATE_DISCONNECTED);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.nack_connect)
+ return _client->core.nack_connect(_state);
+ return 0;
+}
+
+EXPORT_SYMBOL(core_core_handle_ack_connect);
+static int
+core_core_handle_ack_disconnect(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 0UL;
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT,
+ VSERVICE_CORE_STATE_DISCONNECTED);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.ack_disconnect)
+ return _client->core.ack_disconnect(_state);
+ return 0;
+}
+
+static int
+core_core_handle_nack_disconnect(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT,
+ VSERVICE_CORE_STATE_CONNECTED);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.nack_disconnect)
+ return _client->core.nack_disconnect(_state);
+ return 0;
+}
+
+EXPORT_SYMBOL(core_core_handle_ack_disconnect);
+static int
+vs_client_core_core_handle_startup(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 8UL;
+ uint32_t core_in_quota;
+ uint32_t core_out_quota;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_OFFLINE:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state, VSERVICE_CORE_STATE_OFFLINE,
+ VSERVICE_CORE_STATE_DISCONNECTED);
+ core_in_quota =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL);
+ core_out_quota =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.msg_startup)
+ return _client->core.msg_startup(_state, core_in_quota,
+ core_out_quota);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_handle_startup);
+static int
+vs_client_core_core_handle_shutdown(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 0UL;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED:
+ case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT:
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED:
+ _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED,
+ VSERVICE_CORE_STATE_OFFLINE);
+ break;
+ case VSERVICE_CORE_STATE_CONNECTED:
+ _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE;
+
+ if (_client->core.state_change)
+ _client->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED,
+ VSERVICE_CORE_STATE_OFFLINE);
+ break;
+
+ default:
+ break;
+ }
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.msg_shutdown)
+ return _client->core.msg_shutdown(_state);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_handle_shutdown);
+static int
+vs_client_core_core_handle_service_created(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _max_size =
+ sizeof(vs_message_id_t) + VSERVICE_CORE_SERVICE_NAME_SIZE +
+ VSERVICE_CORE_PROTOCOL_NAME_SIZE + 4UL;
+ uint32_t service_id;
+ struct vs_string service_name;
+ struct vs_string protocol_name;
+ const size_t _min_size = _max_size - VSERVICE_CORE_PROTOCOL_NAME_SIZE;
+ size_t _exact_size;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ /* The first check is to ensure the message isn't complete garbage */
+ if ((VS_MBUF_SIZE(_mbuf) > _max_size)
+ || (VS_MBUF_SIZE(_mbuf) < _min_size))
+ return -EBADMSG;
+ service_id =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL);
+ service_name.ptr =
+ (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL);
+ service_name.max_size = VSERVICE_CORE_SERVICE_NAME_SIZE;
+
+ protocol_name.ptr =
+ (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL);
+ protocol_name.max_size =
+ VS_MBUF_SIZE(_mbuf) - (sizeof(vs_message_id_t) +
+ VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL);
+
+ /* Now check the size received is the exact size expected */
+ _exact_size =
+ _max_size - (VSERVICE_CORE_PROTOCOL_NAME_SIZE -
+ protocol_name.max_size);
+ if (VS_MBUF_SIZE(_mbuf) != _exact_size)
+ return -EBADMSG;
+ if (_client->core.msg_service_created)
+ return _client->core.msg_service_created(_state, service_id,
+ service_name,
+ protocol_name, _mbuf);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_handle_service_created);
+static int
+vs_client_core_core_handle_service_removed(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 4UL;
+ uint32_t service_id;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ service_id =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.msg_service_removed)
+ return _client->core.msg_service_removed(_state, service_id);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_handle_service_removed);
+static int
+vs_client_core_core_handle_server_ready(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 28UL;
+ uint32_t service_id;
+ uint32_t in_quota;
+ uint32_t out_quota;
+ uint32_t in_bit_offset;
+ uint32_t in_num_bits;
+ uint32_t out_bit_offset;
+ uint32_t out_num_bits;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ service_id =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL);
+ in_quota =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL);
+ out_quota =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 8UL);
+ in_bit_offset =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ 12UL);
+ in_num_bits =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ 16UL);
+ out_bit_offset =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ 20UL);
+ out_num_bits =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ 24UL);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.msg_server_ready)
+ return _client->core.msg_server_ready(_state, service_id,
+ in_quota, out_quota,
+ in_bit_offset,
+ in_num_bits,
+ out_bit_offset,
+ out_num_bits);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_handle_server_ready);
+static int
+vs_client_core_core_handle_service_reset(const struct vs_client_core *_client,
+ struct vs_client_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 4UL;
+ uint32_t service_id;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ service_id =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_client->core.msg_service_reset)
+ return _client->core.msg_service_reset(_state, service_id);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_handle_service_reset);
+int
+vs_client_core_core_send_service_reset(struct vs_client_core_state *_state,
+ uint32_t service_id, gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 4UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_client_core *_client =
+ to_client_driver(vsdrv)->client;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_MSG_SERVICE_RESET;
+
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) =
+ service_id;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_client_core_core_send_service_reset);
+static int
+core_handle_message(struct vs_service_device *service, struct vs_mbuf *_mbuf)
+{
+ vs_message_id_t message_id;
+ __maybe_unused struct vs_client_core_state *state =
+ dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ __maybe_unused struct vs_client_core *client =
+ to_client_driver(vsdrv)->client;
+
+ int ret;
+
+ /* Extract the message ID */
+ if (VS_MBUF_SIZE(_mbuf) < sizeof(message_id)) {
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Invalid message size %zd\n",
+ __func__, __LINE__, VS_MBUF_SIZE(_mbuf));
+
+ return -EBADMSG;
+ }
+
+ message_id = *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf));
+
+ switch (message_id) {
+
+/** interface core **/
+/* command in sync connect */
+ case VSERVICE_CORE_CORE_ACK_CONNECT:
+ ret = core_core_handle_ack_connect(client, state, _mbuf);
+ break;
+ case VSERVICE_CORE_CORE_NACK_CONNECT:
+ ret = core_core_handle_nack_connect(client, state, _mbuf);
+ break;
+
+/* command in sync disconnect */
+ case VSERVICE_CORE_CORE_ACK_DISCONNECT:
+ ret = core_core_handle_ack_disconnect(client, state, _mbuf);
+ break;
+ case VSERVICE_CORE_CORE_NACK_DISCONNECT:
+ ret = core_core_handle_nack_disconnect(client, state, _mbuf);
+ break;
+
+/* message startup */
+ case VSERVICE_CORE_CORE_MSG_STARTUP:
+ ret = vs_client_core_core_handle_startup(client, state, _mbuf);
+ break;
+
+/* message shutdown */
+ case VSERVICE_CORE_CORE_MSG_SHUTDOWN:
+ ret = vs_client_core_core_handle_shutdown(client, state, _mbuf);
+ break;
+
+/* message service_created */
+ case VSERVICE_CORE_CORE_MSG_SERVICE_CREATED:
+ ret =
+ vs_client_core_core_handle_service_created(client, state,
+ _mbuf);
+ break;
+
+/* message service_removed */
+ case VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED:
+ ret =
+ vs_client_core_core_handle_service_removed(client, state,
+ _mbuf);
+ break;
+
+/* message server_ready */
+ case VSERVICE_CORE_CORE_MSG_SERVER_READY:
+ ret =
+ vs_client_core_core_handle_server_ready(client, state,
+ _mbuf);
+ break;
+
+/* message service_reset */
+ case VSERVICE_CORE_CORE_MSG_SERVICE_RESET:
+ ret =
+ vs_client_core_core_handle_service_reset(client, state,
+ _mbuf);
+ break;
+
+ default:
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Unknown message type %d\n",
+ __func__, __LINE__, (int)message_id);
+
+ ret = -EPROTO;
+ break;
+ }
+
+ if (ret) {
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Handler for message type %d returned %d\n",
+ __func__, __LINE__, (int)message_id, ret);
+
+ }
+
+ return ret;
+}
+
+static void core_handle_notify(struct vs_service_device *service,
+ uint32_t notify_bits)
+{
+ __maybe_unused struct vs_client_core_state *state =
+ dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ __maybe_unused struct vs_client_core *client =
+ to_client_driver(vsdrv)->client;
+
+ uint32_t bits = notify_bits;
+ int ret;
+
+ while (bits) {
+ uint32_t not = __ffs(bits);
+ switch (not) {
+
+ /** interface core **/
+
+ default:
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Unknown notification %d\n",
+ __func__, __LINE__, (int)not);
+
+ ret = -EPROTO;
+ break;
+
+ }
+ bits &= ~(1 << not);
+ if (ret) {
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Handler for notification %d returned %d\n",
+ __func__, __LINE__, (int)not, ret);
+
+ }
+ }
+}
+
+MODULE_DESCRIPTION("OKL4 Virtual Services coreClient Protocol Driver");
+MODULE_AUTHOR("Open Kernel Labs, Inc");
diff --git a/drivers/vservices/protocol/core/server.c b/drivers/vservices/protocol/core/server.c
new file mode 100644
index 0000000..c3f3686
--- /dev/null
+++ b/drivers/vservices/protocol/core/server.c
@@ -0,0 +1,1226 @@
+
+/*
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+ /*
+ * This is the generated code for the core server protocol handling.
+ */
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)
+#include <linux/export.h>
+#endif
+
+#include <vservices/types.h>
+#include <vservices/buffer.h>
+#include <vservices/protocol/core/types.h>
+#include <vservices/protocol/core/common.h>
+#include <vservices/protocol/core/server.h>
+#include <vservices/service.h>
+
+#include "../../transport.h"
+
+#define VS_MBUF_SIZE(mbuf) mbuf->size
+#define VS_MBUF_DATA(mbuf) mbuf->data
+#define VS_STATE_SERVICE_PTR(state) state->service
+
+/*** Linux driver model integration ***/
+struct vs_core_server_driver {
+ struct vs_server_core *server;
+ struct list_head list;
+ struct vs_service_driver vsdrv;
+};
+
+#define to_server_driver(d) \
+ container_of(d, struct vs_core_server_driver, vsdrv)
+
+static void core_handle_start(struct vs_service_device *service)
+{
+
+ struct vs_server_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server __maybe_unused =
+ to_server_driver(vsdrv)->server;
+
+ vs_service_state_lock(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (server->start)
+ server->start(state);
+ vs_service_state_unlock(service);
+}
+
+static void core_handle_reset(struct vs_service_device *service)
+{
+
+ struct vs_server_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server __maybe_unused =
+ to_server_driver(vsdrv)->server;
+
+ vs_service_state_lock(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (server->reset)
+ server->reset(state);
+ vs_service_state_unlock(service);
+}
+
+static void core_handle_start_bh(struct vs_service_device *service)
+{
+
+ struct vs_server_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server __maybe_unused =
+ to_server_driver(vsdrv)->server;
+
+ vs_service_state_lock_bh(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (server->start)
+ server->start(state);
+ vs_service_state_unlock_bh(service);
+}
+
+static void core_handle_reset_bh(struct vs_service_device *service)
+{
+
+ struct vs_server_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server __maybe_unused =
+ to_server_driver(vsdrv)->server;
+
+ vs_service_state_lock_bh(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+ if (server->reset)
+ server->reset(state);
+ vs_service_state_unlock_bh(service);
+}
+
+static int core_server_probe(struct vs_service_device *service);
+static int core_server_remove(struct vs_service_device *service);
+static int core_handle_message(struct vs_service_device *service,
+ struct vs_mbuf *_mbuf);
+static void core_handle_notify(struct vs_service_device *service,
+ uint32_t flags);
+static void core_handle_start(struct vs_service_device *service);
+static void core_handle_start_bh(struct vs_service_device *service);
+static void core_handle_reset(struct vs_service_device *service);
+static void core_handle_reset_bh(struct vs_service_device *service);
+static int core_handle_tx_ready(struct vs_service_device *service);
+
+int __vservice_core_server_register(struct vs_server_core *server,
+ const char *name, struct module *owner)
+{
+ int ret;
+ struct vs_core_server_driver *driver;
+
+ if (server->tx_atomic && !server->rx_atomic)
+ return -EINVAL;
+
+ driver = kzalloc(sizeof(*driver), GFP_KERNEL);
+ if (!driver) {
+ ret = -ENOMEM;
+ goto fail_alloc_driver;
+ }
+
+ server->driver = &driver->vsdrv;
+ driver->server = server;
+
+ driver->vsdrv.protocol = VSERVICE_CORE_PROTOCOL_NAME;
+
+ driver->vsdrv.is_server = true;
+ driver->vsdrv.rx_atomic = server->rx_atomic;
+ driver->vsdrv.tx_atomic = server->tx_atomic;
+ /* FIXME Jira ticket SDK-2835 - philipd. */
+ driver->vsdrv.in_quota_min = 1;
+ driver->vsdrv.in_quota_best = server->in_quota_best ?
+ server->in_quota_best : driver->vsdrv.in_quota_min;
+ /* FIXME Jira ticket SDK-2835 - philipd. */
+ driver->vsdrv.out_quota_min = 1;
+ driver->vsdrv.out_quota_best = server->out_quota_best ?
+ server->out_quota_best : driver->vsdrv.out_quota_min;
+ driver->vsdrv.in_notify_count = VSERVICE_CORE_NBIT_IN__COUNT;
+ driver->vsdrv.out_notify_count = VSERVICE_CORE_NBIT_OUT__COUNT;
+
+ driver->vsdrv.probe = core_server_probe;
+ driver->vsdrv.remove = core_server_remove;
+ driver->vsdrv.receive = core_handle_message;
+ driver->vsdrv.notify = core_handle_notify;
+ driver->vsdrv.start = server->tx_atomic ?
+ core_handle_start_bh : core_handle_start;
+ driver->vsdrv.reset = server->tx_atomic ?
+ core_handle_reset_bh : core_handle_reset;
+ driver->vsdrv.tx_ready = core_handle_tx_ready;
+ driver->vsdrv.out_notify_count = 0;
+ driver->vsdrv.in_notify_count = 0;
+ driver->vsdrv.driver.name = name;
+ driver->vsdrv.driver.owner = owner;
+ driver->vsdrv.driver.bus = &vs_server_bus_type;
+
+ ret = driver_register(&driver->vsdrv.driver);
+
+ if (ret) {
+ goto fail_driver_register;
+ }
+
+ return 0;
+
+ fail_driver_register:
+ server->driver = NULL;
+ kfree(driver);
+ fail_alloc_driver:
+ return ret;
+}
+
+EXPORT_SYMBOL(__vservice_core_server_register);
+
+int vservice_core_server_unregister(struct vs_server_core *server)
+{
+ struct vs_core_server_driver *driver;
+
+ if (!server->driver)
+ return 0;
+
+ driver = to_server_driver(server->driver);
+ driver_unregister(&driver->vsdrv.driver);
+
+ server->driver = NULL;
+ kfree(driver);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vservice_core_server_unregister);
+
+static int core_server_probe(struct vs_service_device *service)
+{
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server = to_server_driver(vsdrv)->server;
+ struct vs_server_core_state *state;
+
+ state = server->alloc(service);
+ if (!state)
+ return -ENOMEM;
+ else if (IS_ERR(state))
+ return PTR_ERR(state);
+
+ state->service = vs_get_service(service);
+ state->state = VSERVICE_CORE_PROTOCOL_RESET_STATE;
+
+ dev_set_drvdata(&service->dev, state);
+
+ return 0;
+}
+
+static int core_server_remove(struct vs_service_device *service)
+{
+ struct vs_server_core_state *state = dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server = to_server_driver(vsdrv)->server;
+
+ state->released = true;
+ dev_set_drvdata(&service->dev, NULL);
+ server->release(state);
+
+ vs_put_service(service);
+
+ return 0;
+}
+
+static int core_handle_tx_ready(struct vs_service_device *service)
+{
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_server_core *server = to_server_driver(vsdrv)->server;
+ struct vs_server_core_state *state = dev_get_drvdata(&service->dev);
+
+ if (server->tx_ready)
+ server->tx_ready(state);
+
+ return 0;
+}
+
+struct vs_mbuf *vs_server_core_core_alloc_service_created(struct
+ vs_server_core_state
+ *_state,
+ struct vs_string
+ *service_name,
+ struct vs_string
+ *protocol_name,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+ const vs_message_id_t _msg_id = VSERVICE_CORE_CORE_MSG_SERVICE_CREATED;
+ const uint32_t _msg_size =
+ sizeof(vs_message_id_t) + VSERVICE_CORE_SERVICE_NAME_SIZE +
+ VSERVICE_CORE_PROTOCOL_NAME_SIZE + 4UL;
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return _mbuf;
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+ return ERR_PTR(-ENOMEM);
+ }
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) = _msg_id;
+
+ if (!service_name)
+ goto fail;
+ service_name->ptr =
+ (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL);
+ service_name->max_size = VSERVICE_CORE_SERVICE_NAME_SIZE;
+ if (!protocol_name)
+ goto fail;
+ protocol_name->ptr =
+ (char *)(VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) +
+ VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL);
+ protocol_name->max_size = VSERVICE_CORE_PROTOCOL_NAME_SIZE;
+
+ return _mbuf;
+
+ fail:
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ return NULL;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_alloc_service_created);
+int vs_server_core_core_free_service_created(struct vs_server_core_state
+ *_state,
+ struct vs_string *service_name,
+ struct vs_string *protocol_name,
+ struct vs_mbuf *_mbuf)
+{
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_free_service_created);
+int
+vs_server_core_core_send_ack_connect(struct vs_server_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_ACK_CONNECT;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT,
+ VSERVICE_CORE_STATE_CONNECTED);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_ack_connect);
+int
+vs_server_core_core_send_nack_connect(struct vs_server_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_NACK_CONNECT;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT,
+ VSERVICE_CORE_STATE_DISCONNECTED);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_nack_connect);
+int
+vs_server_core_core_send_ack_disconnect(struct vs_server_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_ACK_DISCONNECT;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT,
+ VSERVICE_CORE_STATE_DISCONNECTED);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_ack_disconnect);
+int
+vs_server_core_core_send_nack_disconnect(struct vs_server_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_NACK_DISCONNECT;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT,
+ VSERVICE_CORE_STATE_CONNECTED);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_nack_disconnect);
+static int
+vs_server_core_core_handle_req_connect(const struct vs_server_core *_server,
+ struct vs_server_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 0UL;
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED__CONNECT;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_server->core.req_connect)
+ return _server->core.req_connect(_state);
+ else
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: No handler registered for _server->core.req_connect, command will never be acknowledged\n",
+ __func__, __LINE__);
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_handle_req_connect);
+static int
+vs_server_core_core_handle_req_disconnect(const struct vs_server_core *_server,
+ struct vs_server_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 0UL;
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ _state->state.core.statenum = VSERVICE_CORE_STATE_CONNECTED__DISCONNECT;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_server->core.req_disconnect)
+ return _server->core.req_disconnect(_state);
+ else
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: No handler registered for _server->core.req_disconnect, command will never be acknowledged\n",
+ __func__, __LINE__);
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_handle_req_disconnect);
+int
+vs_server_core_core_send_startup(struct vs_server_core_state *_state,
+ uint32_t core_in_quota,
+ uint32_t core_out_quota, gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 8UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_OFFLINE:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_MSG_STARTUP;
+
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) =
+ core_in_quota;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL) =
+ core_out_quota;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ _state->state.core.statenum = VSERVICE_CORE_STATE_DISCONNECTED;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state, VSERVICE_CORE_STATE_OFFLINE,
+ VSERVICE_CORE_STATE_DISCONNECTED);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_startup);
+int
+vs_server_core_core_send_shutdown(struct vs_server_core_state *_state,
+ gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 0UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED:
+ case VSERVICE_CORE_STATE_DISCONNECTED__CONNECT:
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_MSG_SHUTDOWN;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_DISCONNECTED:
+ _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_DISCONNECTED,
+ VSERVICE_CORE_STATE_OFFLINE);
+ break;
+ case VSERVICE_CORE_STATE_CONNECTED:
+ _state->state.core.statenum = VSERVICE_CORE_STATE_OFFLINE;
+
+ if (_server->core.state_change)
+ _server->core.state_change(_state,
+ VSERVICE_CORE_STATE_CONNECTED,
+ VSERVICE_CORE_STATE_OFFLINE);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_shutdown);
+int
+vs_server_core_core_send_service_created(struct vs_server_core_state *_state,
+ uint32_t service_id,
+ struct vs_string service_name,
+ struct vs_string protocol_name,
+ struct vs_mbuf *_mbuf)
+{
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+ if (*(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) !=
+ VSERVICE_CORE_CORE_MSG_SERVICE_CREATED)
+
+ return -EINVAL;
+
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) =
+ service_id;
+ {
+ size_t _size = strnlen(service_name.ptr, service_name.max_size);
+ if ((_size + sizeof(vs_message_id_t) + 4UL) >
+ VS_MBUF_SIZE(_mbuf))
+ return -EINVAL;
+
+ memset(service_name.ptr + _size, 0,
+ service_name.max_size - _size);
+ }
+ {
+ size_t _size =
+ strnlen(protocol_name.ptr, protocol_name.max_size);
+ if ((_size + sizeof(vs_message_id_t) +
+ VSERVICE_CORE_SERVICE_NAME_SIZE + 4UL) >
+ VS_MBUF_SIZE(_mbuf))
+ return -EINVAL;
+
+ if (_size < protocol_name.max_size)
+ VS_MBUF_SIZE(_mbuf) -= (protocol_name.max_size - _size);
+
+ }
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_service_created);
+int
+vs_server_core_core_send_service_removed(struct vs_server_core_state *_state,
+ uint32_t service_id, gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 4UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED;
+
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) =
+ service_id;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_service_removed);
+int
+vs_server_core_core_send_server_ready(struct vs_server_core_state *_state,
+ uint32_t service_id, uint32_t in_quota,
+ uint32_t out_quota,
+ uint32_t in_bit_offset,
+ uint32_t in_num_bits,
+ uint32_t out_bit_offset,
+ uint32_t out_num_bits, gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 28UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+ case VSERVICE_CORE_STATE_CONNECTED__DISCONNECT:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_MSG_SERVER_READY;
+
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) =
+ service_id;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 4UL) =
+ in_quota;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 8UL) =
+ out_quota;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 12UL) =
+ in_bit_offset;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 16UL) =
+ in_num_bits;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 20UL) =
+ out_bit_offset;
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 24UL) =
+ out_num_bits;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_server_ready);
+int
+vs_server_core_core_send_service_reset(struct vs_server_core_state *_state,
+ uint32_t service_id, gfp_t flags)
+{
+ struct vs_mbuf *_mbuf;
+
+ const size_t _msg_size = sizeof(vs_message_id_t) + 4UL;
+
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(VS_STATE_SERVICE_PTR(_state)->dev.driver);
+ __maybe_unused struct vs_server_core *_server =
+ to_server_driver(vsdrv)->server;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ _mbuf =
+ vs_service_alloc_mbuf(VS_STATE_SERVICE_PTR(_state), _msg_size,
+ flags);
+ if (IS_ERR(_mbuf))
+ return PTR_ERR(_mbuf);
+ if (!_mbuf) {
+
+ WARN_ON_ONCE(1);
+
+ return -ENOMEM;
+ }
+
+ *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf)) =
+ VSERVICE_CORE_CORE_MSG_SERVICE_RESET;
+
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL) =
+ service_id;
+
+ {
+ int err = vs_service_send(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (err) {
+ dev_warn(&_state->service->dev,
+ "[%s:%d] Protocol warning: Error %d sending message on transport.\n",
+ __func__, __LINE__, err);
+
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_send_service_reset);
+static int
+vs_server_core_core_handle_service_reset(const struct vs_server_core *_server,
+ struct vs_server_core_state *_state,
+ struct vs_mbuf *_mbuf)
+{
+ const size_t _expected_size = sizeof(vs_message_id_t) + 4UL;
+ uint32_t service_id;
+
+ switch (_state->state.core.statenum) {
+ case VSERVICE_CORE_STATE_CONNECTED:
+
+ break;
+
+ default:
+ dev_err(&_state->service->dev,
+ "[%s:%d] Protocol error: In wrong protocol state %d - %s\n",
+ __func__, __LINE__, _state->state.core.statenum,
+ vservice_core_get_state_string(_state->state.core));
+
+ return -EPROTO;
+
+ }
+
+ if (VS_MBUF_SIZE(_mbuf) < _expected_size)
+ return -EBADMSG;
+
+ service_id =
+ *(uint32_t *) (VS_MBUF_DATA(_mbuf) + sizeof(vs_message_id_t) + 0UL);
+ vs_service_free_mbuf(VS_STATE_SERVICE_PTR(_state), _mbuf);
+ if (_server->core.msg_service_reset)
+ return _server->core.msg_service_reset(_state, service_id);
+ return 0;
+ return 0;
+}
+
+EXPORT_SYMBOL(vs_server_core_core_handle_service_reset);
+static int
+core_handle_message(struct vs_service_device *service, struct vs_mbuf *_mbuf)
+{
+ vs_message_id_t message_id;
+ __maybe_unused struct vs_server_core_state *state =
+ dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ __maybe_unused struct vs_server_core *server =
+ to_server_driver(vsdrv)->server;
+
+ int ret;
+
+ /* Extract the message ID */
+ if (VS_MBUF_SIZE(_mbuf) < sizeof(message_id)) {
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Invalid message size %zd\n",
+ __func__, __LINE__, VS_MBUF_SIZE(_mbuf));
+
+ return -EBADMSG;
+ }
+
+ message_id = *(vs_message_id_t *) (VS_MBUF_DATA(_mbuf));
+
+ switch (message_id) {
+
+/** interface core **/
+/* command in sync connect */
+ case VSERVICE_CORE_CORE_REQ_CONNECT:
+ ret =
+ vs_server_core_core_handle_req_connect(server, state,
+ _mbuf);
+ break;
+
+/* command in sync disconnect */
+ case VSERVICE_CORE_CORE_REQ_DISCONNECT:
+ ret =
+ vs_server_core_core_handle_req_disconnect(server, state,
+ _mbuf);
+ break;
+
+/* message service_reset */
+ case VSERVICE_CORE_CORE_MSG_SERVICE_RESET:
+ ret =
+ vs_server_core_core_handle_service_reset(server, state,
+ _mbuf);
+ break;
+
+ default:
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Unknown message type %d\n",
+ __func__, __LINE__, (int)message_id);
+
+ ret = -EPROTO;
+ break;
+ }
+
+ if (ret) {
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Handler for message type %d returned %d\n",
+ __func__, __LINE__, (int)message_id, ret);
+
+ }
+
+ return ret;
+}
+
+static void core_handle_notify(struct vs_service_device *service,
+ uint32_t notify_bits)
+{
+ __maybe_unused struct vs_server_core_state *state =
+ dev_get_drvdata(&service->dev);
+ struct vs_service_driver *vsdrv =
+ to_vs_service_driver(service->dev.driver);
+ __maybe_unused struct vs_server_core *server =
+ to_server_driver(vsdrv)->server;
+
+ uint32_t bits = notify_bits;
+ int ret;
+
+ while (bits) {
+ uint32_t not = __ffs(bits);
+ switch (not) {
+
+ /** interface core **/
+
+ default:
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Unknown notification %d\n",
+ __func__, __LINE__, (int)not);
+
+ ret = -EPROTO;
+ break;
+
+ }
+ bits &= ~(1 << not);
+ if (ret) {
+ dev_err(&state->service->dev,
+ "[%s:%d] Protocol error: Handler for notification %d returned %d\n",
+ __func__, __LINE__, (int)not, ret);
+
+ }
+ }
+}
+
+MODULE_DESCRIPTION("OKL4 Virtual Services coreServer Protocol Driver");
+MODULE_AUTHOR("Open Kernel Labs, Inc");
diff --git a/drivers/vservices/session.c b/drivers/vservices/session.c
new file mode 100644
index 0000000..d695184
--- /dev/null
+++ b/drivers/vservices/session.c
@@ -0,0 +1,2913 @@
+/*
+ * drivers/vservices/session.c
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This is the generic session-management code for the vServices framework.
+ * It creates service and session devices on request from session and
+ * transport drivers, respectively; it also queues incoming messages from the
+ * transport and distributes them to the session's services.
+ */
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/moduleparam.h>
+#include <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/notifier.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/kdev_t.h>
+#include <linux/err.h>
+
+#include <vservices/transport.h>
+#include <vservices/session.h>
+#include <vservices/service.h>
+
+#include "session.h"
+#include "transport.h"
+#include "compat.h"
+
+/* Minimum required time between resets to avoid throttling */
+#define RESET_THROTTLE_TIME msecs_to_jiffies(1000)
+
+/*
+ * Minimum/maximum reset throttling time. The reset throttle will start at
+ * the minimum and increase to the maximum exponetially.
+ */
+#define RESET_THROTTLE_MIN RESET_THROTTLE_TIME
+#define RESET_THROTTLE_MAX msecs_to_jiffies(8 * 1000)
+
+/*
+ * If the reset is being throttled and a sane reset (doesn't need throttling)
+ * is requested, then if the service's reset delay mutliplied by this value
+ * has elapsed throttling is disabled.
+ */
+#define RESET_THROTTLE_COOL_OFF_MULT 2
+
+/* IDR of session ids to sessions */
+static DEFINE_IDR(session_idr);
+DEFINE_MUTEX(vs_session_lock);
+EXPORT_SYMBOL_GPL(vs_session_lock);
+
+/* Notifier list for vService session events */
+static BLOCKING_NOTIFIER_HEAD(vs_session_notifier_list);
+
+static unsigned long default_debug_mask;
+module_param(default_debug_mask, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(default_debug_mask, "Default vServices debug mask");
+
+/* vServices root in sysfs at /sys/vservices */
+struct kobject *vservices_root;
+EXPORT_SYMBOL_GPL(vservices_root);
+
+/* vServices server root in sysfs at /sys/vservices/server-sessions */
+struct kobject *vservices_server_root;
+EXPORT_SYMBOL_GPL(vservices_server_root);
+
+/* vServices client root in sysfs at /sys/vservices/client-sessions */
+struct kobject *vservices_client_root;
+EXPORT_SYMBOL_GPL(vservices_client_root);
+
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+struct vs_service_device *vs_service_lookup_by_devt(dev_t dev)
+{
+ struct vs_session_device *session;
+ struct vs_service_device *service;
+
+ mutex_lock(&vs_session_lock);
+ session = idr_find(&session_idr, MINOR(dev) / VS_MAX_SERVICES);
+ get_device(&session->dev);
+ mutex_unlock(&vs_session_lock);
+
+ service = vs_session_get_service(session,
+ MINOR(dev) % VS_MAX_SERVICES);
+ put_device(&session->dev);
+
+ return service;
+}
+#endif
+
+struct vs_session_for_each_data {
+ int (*fn)(struct vs_session_device *session, void *data);
+ void *data;
+};
+
+int vs_session_for_each_from_idr(int id, void *session, void *_data)
+{
+ struct vs_session_for_each_data *data =
+ (struct vs_session_for_each_data *)_data;
+ return data->fn(session, data->data);
+}
+
+/**
+ * vs_session_for_each_locked - call a callback function for each session
+ * @fn: function to call
+ * @data: opaque pointer that is passed through to the function
+ */
+extern int vs_session_for_each_locked(
+ int (*fn)(struct vs_session_device *session, void *data),
+ void *data)
+{
+ struct vs_session_for_each_data priv = { .fn = fn, .data = data };
+
+ lockdep_assert_held(&vs_session_lock);
+
+ return idr_for_each(&session_idr, vs_session_for_each_from_idr,
+ &priv);
+}
+EXPORT_SYMBOL(vs_session_for_each_locked);
+
+/**
+ * vs_register_notify - register a notifier callback for vServices events
+ * @nb: pointer to the notifier block for the callback events.
+ */
+void vs_session_register_notify(struct notifier_block *nb)
+{
+ blocking_notifier_chain_register(&vs_session_notifier_list, nb);
+}
+EXPORT_SYMBOL(vs_session_register_notify);
+
+/**
+ * vs_unregister_notify - unregister a notifier callback for vServices events
+ * @nb: pointer to the notifier block for the callback events.
+ */
+void vs_session_unregister_notify(struct notifier_block *nb)
+{
+ blocking_notifier_chain_unregister(&vs_session_notifier_list, nb);
+}
+EXPORT_SYMBOL(vs_session_unregister_notify);
+
+/*
+ * Helper function for returning how long ago something happened
+ * Marked as __maybe_unused since this is only needed when
+ * CONFIG_VSERVICES_DEBUG is enabled, but cannot be removed because it
+ * will cause compile time errors.
+ */
+static __maybe_unused unsigned msecs_ago(unsigned long jiffy_value)
+{
+ return jiffies_to_msecs(jiffies - jiffy_value);
+}
+
+static void session_fatal_error_work(struct work_struct *work)
+{
+ struct vs_session_device *session = container_of(work,
+ struct vs_session_device, fatal_error_work);
+
+ session->transport->vt->reset(session->transport);
+}
+
+static void session_fatal_error(struct vs_session_device *session, gfp_t gfp)
+{
+ schedule_work(&session->fatal_error_work);
+}
+
+/*
+ * Service readiness state machine
+ *
+ * The states are:
+ *
+ * INIT: Initial state. Service may not be completely configured yet
+ * (typically because the protocol hasn't been set); call vs_service_start
+ * once configuration is complete. The disable count must be nonzero, and
+ * must never reach zero in this state.
+ * DISABLED: Service is not permitted to communicate. Non-core services are
+ * in this state whenever the core protocol and/or transport state does not
+ * allow them to be active; core services are only in this state transiently.
+ * The disable count must be nonzero; when it reaches zero, the service
+ * transitions to RESET state.
+ * RESET: Service drivers are inactive at both ends, but the core service
+ * state allows the service to become active. The session will schedule a
+ * future transition to READY state when entering this state, but the
+ * transition may be delayed to throttle the rate at which resets occur.
+ * READY: All core-service and session-layer policy allows the service to
+ * communicate; it will become active as soon as it has a protocol driver.
+ * ACTIVE: The driver is present and communicating.
+ * LOCAL_RESET: We have initiated a reset at this end, but the remote end has
+ * not yet acknowledged it. We will enter the RESET state on receiving
+ * acknowledgement, unless the disable count is nonzero in which case we
+ * will enter DISABLED state.
+ * LOCAL_DELETE: As for LOCAL_RESET, but we will enter the DELETED state
+ * instead of RESET or DISABLED.
+ * DELETED: The service is no longer present on the session; the service
+ * device structure may still exist because something is holding a reference
+ * to it.
+ *
+ * The permitted transitions are:
+ *
+ * From To Trigger
+ * INIT DISABLED vs_service_start
+ * DISABLED RESET vs_service_enable (disable_count -> 0)
+ * RESET READY End of throttle delay (may be 0)
+ * READY ACTIVE Latter of probe() and entering READY
+ * {READY, ACTIVE}
+ * LOCAL_RESET vs_service_reset
+ * {READY, ACTIVE, LOCAL_RESET}
+ * RESET vs_service_handle_reset (server)
+ * RESET DISABLED vs_service_disable (server)
+ * {READY, ACTIVE, LOCAL_RESET}
+ * DISABLED vs_service_handle_reset (client)
+ * {INIT, RESET, READY, ACTIVE, LOCAL_RESET}
+ * DISABLED vs_service_disable_noncore
+ * {ACTIVE, LOCAL_RESET}
+ * LOCAL_DELETE vs_service_delete
+ * {INIT, DISABLED, RESET, READY}
+ * DELETED vs_service_delete
+ * LOCAL_DELETE DELETED vs_service_handle_reset
+ * vs_service_disable_noncore
+ *
+ * See the documentation for the triggers for details.
+ */
+
+enum vs_service_readiness {
+ VS_SERVICE_INIT,
+ VS_SERVICE_DISABLED,
+ VS_SERVICE_RESET,
+ VS_SERVICE_READY,
+ VS_SERVICE_ACTIVE,
+ VS_SERVICE_LOCAL_RESET,
+ VS_SERVICE_LOCAL_DELETE,
+ VS_SERVICE_DELETED,
+};
+
+/* Session activation states. */
+enum {
+ VS_SESSION_RESET,
+ VS_SESSION_ACTIVATE,
+ VS_SESSION_ACTIVE,
+};
+
+/**
+ * vs_service_start - Start a service by moving it from the init state to the
+ * disabled state.
+ *
+ * @service: The service to start.
+ *
+ * Returns true if the service was started, or false if it was not.
+ */
+bool vs_service_start(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct vs_session_driver *session_drv =
+ to_vs_session_driver(session->dev.driver);
+
+ WARN_ON(!service->protocol);
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ if (service->readiness != VS_SERVICE_INIT) {
+ if (service->readiness != VS_SERVICE_DELETED)
+ dev_err(&service->dev,
+ "start called from invalid state %d\n",
+ service->readiness);
+ mutex_unlock(&service->ready_lock);
+ return false;
+ }
+
+ if (service->id != 0 && session_drv->service_added) {
+ int err = session_drv->service_added(session, service);
+ if (err < 0) {
+ dev_err(&session->dev, "Failed to add service %d: %d\n",
+ service->id, err);
+ mutex_unlock(&service->ready_lock);
+ return false;
+ }
+ }
+
+ service->readiness = VS_SERVICE_DISABLED;
+ service->disable_count = 1;
+ service->last_reset_request = jiffies;
+
+ mutex_unlock(&service->ready_lock);
+
+ /* Tell userspace about the service. */
+ dev_set_uevent_suppress(&service->dev, false);
+ kobject_uevent(&service->dev.kobj, KOBJ_ADD);
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(vs_service_start);
+
+static void cancel_pending_rx(struct vs_service_device *service);
+static void queue_ready_work(struct vs_service_device *service);
+
+static void __try_start_service(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct vs_session_driver *session_drv =
+ to_vs_session_driver(session->dev.driver);
+ struct vs_transport *transport;
+ int err;
+ struct vs_service_driver *driver;
+
+ lockdep_assert_held(&service->ready_lock);
+
+ /* We can't start if the service is not ready yet. */
+ if (service->readiness != VS_SERVICE_READY)
+ return;
+
+ /*
+ * There should never be anything in the RX queue at this point.
+ * If there is, it can seriously confuse the service drivers for
+ * no obvious reason, so we check.
+ */
+ if (WARN_ON(!list_empty(&service->rx_queue)))
+ cancel_pending_rx(service);
+
+ if (!service->driver_probed) {
+ vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev,
+ "ready with no driver\n");
+ return;
+ }
+
+ /* Prepare the transport to support the service. */
+ transport = session->transport;
+ err = transport->vt->service_start(transport, service);
+
+ if (err < 0) {
+ /* fatal error attempting to start; reset and try again */
+ service->readiness = VS_SERVICE_RESET;
+ service->last_reset_request = jiffies;
+ service->last_reset = jiffies;
+ queue_ready_work(service);
+
+ return;
+ }
+
+ service->readiness = VS_SERVICE_ACTIVE;
+
+ driver = to_vs_service_driver(service->dev.driver);
+ if (driver->start)
+ driver->start(service);
+
+ if (service->id && session_drv->service_start) {
+ err = session_drv->service_start(session, service);
+ if (err < 0) {
+ dev_err(&session->dev, "Failed to start service %s (%d): %d\n",
+ dev_name(&service->dev),
+ service->id, err);
+ session_fatal_error(session, GFP_KERNEL);
+ }
+ }
+}
+
+static void try_start_service(struct vs_service_device *service)
+{
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ __try_start_service(service);
+
+ mutex_unlock(&service->ready_lock);
+}
+
+static void service_ready_work(struct work_struct *work)
+{
+ struct vs_service_device *service = container_of(work,
+ struct vs_service_device, ready_work.work);
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev,
+ "ready work - last reset request was %u ms ago\n",
+ msecs_ago(service->last_reset_request));
+
+ /*
+ * Make sure there's no reset work pending from an earlier driver
+ * failure. We should already be inactive at this point, so it's safe
+ * to just cancel it.
+ */
+ cancel_work_sync(&service->reset_work);
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ if (service->readiness != VS_SERVICE_RESET) {
+ vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev,
+ "ready work found readiness of %d, doing nothing\n",
+ service->readiness);
+ mutex_unlock(&service->ready_lock);
+ return;
+ }
+
+ service->readiness = VS_SERVICE_READY;
+ /* Record the time at which this happened, for throttling. */
+ service->last_ready = jiffies;
+
+ /* Tell userspace that the service is ready. */
+ kobject_uevent(&service->dev.kobj, KOBJ_ONLINE);
+
+ /* Start the service, if it has a driver attached. */
+ __try_start_service(service);
+
+ mutex_unlock(&service->ready_lock);
+}
+
+static int __enable_service(struct vs_service_device *service);
+
+/**
+ * __reset_service - make a service inactive, and tell its driver, the
+ * transport, and possibly the remote partner
+ * @service: The service to reset
+ * @notify_remote: If true, the partner is notified of the reset
+ *
+ * This routine is called to make an active service inactive. If the given
+ * service is currently active, it drops any queued messages for the service,
+ * and then informs the service driver and the transport layer that the
+ * service has reset. It sets the service readiness to VS_SERVICE_LOCAL_RESET
+ * to indicate that the driver is no longer active.
+ *
+ * This routine has no effect on services that are not active.
+ *
+ * The caller must hold the target service's ready lock.
+ */
+static void __reset_service(struct vs_service_device *service,
+ bool notify_remote)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct vs_session_driver *session_drv =
+ to_vs_session_driver(session->dev.driver);
+ struct vs_service_driver *driver = NULL;
+ struct vs_transport *transport;
+ int err;
+
+ lockdep_assert_held(&service->ready_lock);
+
+ /* If we're already inactive, there's nothing to do. */
+ if (service->readiness != VS_SERVICE_ACTIVE)
+ return;
+
+ service->last_reset = jiffies;
+ service->readiness = VS_SERVICE_LOCAL_RESET;
+
+ cancel_pending_rx(service);
+
+ if (!WARN_ON(!service->driver_probed))
+ driver = to_vs_service_driver(service->dev.driver);
+
+ if (driver && driver->reset)
+ driver->reset(service);
+
+ wake_up_all(&service->quota_wq);
+
+ transport = vs_service_get_session(service)->transport;
+
+ /*
+ * Ask the transport to reset the service. If this returns a positive
+ * value, we need to leave the service disabled, and the transport
+ * will re-enable it. To avoid allowing the disable count to go
+ * negative if that re-enable races with this callback returning, we
+ * disable the service beforehand and re-enable it if the callback
+ * returns zero.
+ */
+ service->disable_count++;
+ err = transport->vt->service_reset(transport, service);
+ if (err < 0) {
+ dev_err(&session->dev, "Failed to reset service %d: %d (transport)\n",
+ service->id, err);
+ session_fatal_error(session, GFP_KERNEL);
+ } else if (!err) {
+ err = __enable_service(service);
+ }
+
+ if (notify_remote) {
+ if (service->id) {
+ err = session_drv->service_local_reset(session,
+ service);
+ if (err == VS_SERVICE_ALREADY_RESET) {
+ service->readiness = VS_SERVICE_RESET;
+ service->last_reset = jiffies;
+ queue_ready_work(service);
+
+ } else if (err < 0) {
+ dev_err(&session->dev, "Failed to reset service %d: %d (session)\n",
+ service->id, err);
+ session_fatal_error(session, GFP_KERNEL);
+ }
+ } else {
+ session->transport->vt->reset(session->transport);
+ }
+ }
+
+ /* Tell userspace that the service is no longer active. */
+ kobject_uevent(&service->dev.kobj, KOBJ_OFFLINE);
+}
+
+/**
+ * reset_service - reset a service and inform the remote partner
+ * @service: The service to reset
+ *
+ * This routine is called when a reset is locally initiated (other than
+ * implicitly by a session / core service reset). It bumps the reset request
+ * timestamp, acquires the necessary locks, and calls __reset_service.
+ *
+ * This routine returns with the service ready lock held, to allow the caller
+ * to make any other state changes that must be atomic with the service
+ * reset.
+ */
+static void reset_service(struct vs_service_device *service)
+ __acquires(service->ready_lock)
+{
+ service->last_reset_request = jiffies;
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ __reset_service(service, true);
+}
+
+/**
+ * vs_service_reset - initiate a service reset
+ * @service: the service that is to be reset
+ * @caller: the service that is initiating the reset
+ *
+ * This routine informs the partner that the given service is being reset,
+ * then disables and flushes the service's receive queues and resets its
+ * driver. The service will be automatically re-enabled once the partner has
+ * acknowledged the reset (see vs_session_handle_service_reset, above).
+ *
+ * If the given service is the core service, this will perform a transport
+ * reset, which implicitly resets (on the server side) or destroys (on
+ * the client side) every other service on the session.
+ *
+ * If the given service is already being reset, this has no effect, other
+ * than to delay completion of the reset if it is being throttled.
+ *
+ * For lock safety reasons, a service can only be directly reset by itself,
+ * the core service, or the service that created it (which is typically also
+ * the core service).
+ *
+ * A service that wishes to reset itself must not do so while holding its state
+ * lock or while running on its own workqueue. In these circumstances, call
+ * vs_service_reset_nosync() instead. Note that returning an error code
+ * (any negative number) from a driver callback forces a call to
+ * vs_service_reset_nosync() and prints an error message.
+ */
+int vs_service_reset(struct vs_service_device *service,
+ struct vs_service_device *caller)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ if (caller != service && caller != service->owner) {
+ struct vs_service_device *core_service = session->core_service;
+
+ WARN_ON(!core_service);
+ if (caller != core_service)
+ return -EPERM;
+ }
+
+ reset_service(service);
+ /* reset_service returns with ready_lock held, but we don't need it */
+ mutex_unlock(&service->ready_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_service_reset);
+
+/**
+ * vs_service_reset_nosync - asynchronously reset a service.
+ * @service: the service that is to be reset
+ *
+ * This routine triggers a reset for the nominated service. It may be called
+ * from any context, including interrupt context. It does not wait for the
+ * reset to occur, and provides no synchronisation guarantees when called from
+ * outside the target service.
+ *
+ * This is intended only for service drivers that need to reset themselves
+ * from a context that would not normally allow it. In other cases, use
+ * vs_service_reset.
+ */
+void vs_service_reset_nosync(struct vs_service_device *service)
+{
+ service->pending_reset = true;
+ schedule_work(&service->reset_work);
+}
+EXPORT_SYMBOL_GPL(vs_service_reset_nosync);
+
+static void
+vs_service_remove_sysfs_entries(struct vs_session_device *session,
+ struct vs_service_device *service)
+{
+ sysfs_remove_link(session->sysfs_entry, service->sysfs_name);
+ sysfs_remove_link(&service->dev.kobj, VS_SESSION_SYMLINK_NAME);
+}
+
+static void vs_session_release_service_id(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ mutex_lock(&session->service_idr_lock);
+ idr_remove(&session->service_idr, service->id);
+ mutex_unlock(&session->service_idr_lock);
+ vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev,
+ "service id deallocated\n");
+}
+
+static void destroy_service(struct vs_service_device *service,
+ bool notify_remote)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct vs_session_driver *session_drv =
+ to_vs_session_driver(session->dev.driver);
+ struct vs_service_device *core_service __maybe_unused =
+ session->core_service;
+ int err;
+
+ lockdep_assert_held(&service->ready_lock);
+ WARN_ON(service->readiness != VS_SERVICE_DELETED);
+
+ /* Notify the core service and transport that the service is gone */
+ session->transport->vt->service_remove(session->transport, service);
+ if (notify_remote && service->id && session_drv->service_removed) {
+ err = session_drv->service_removed(session, service);
+ if (err < 0) {
+ dev_err(&session->dev,
+ "Failed to remove service %d: %d\n",
+ service->id, err);
+ session_fatal_error(session, GFP_KERNEL);
+ }
+ }
+
+ /*
+ * At this point the service is guaranteed to be gone on the client
+ * side, so we can safely release the service ID.
+ */
+ if (session->is_server)
+ vs_session_release_service_id(service);
+
+ /*
+ * This guarantees that any concurrent vs_session_get_service() that
+ * found the service before we removed it from the IDR will take a
+ * reference before we release ours.
+ *
+ * This similarly protects for_each_[usable_]service().
+ */
+ synchronize_rcu();
+
+ /* Matches device_initialize() in vs_service_register() */
+ put_device(&service->dev);
+}
+
+/**
+ * disable_service - prevent a service becoming ready
+ * @service: the service that is to be disabled
+ * @force: true if the service is known to be in reset
+ *
+ * This routine may be called for any inactive service. Once disabled, the
+ * service cannot be made ready by the session, and thus cannot become active,
+ * until vs_service_enable() is called for it. If multiple calls are made to
+ * this function, they must be balanced by vs_service_enable() calls.
+ *
+ * If the force option is true, then any pending unacknowledged reset will be
+ * presumed to have been acknowledged. This is used when the core service is
+ * entering reset.
+ *
+ * This is used by the core service client to prevent the service restarting
+ * until the server is ready (i.e., a server_ready message is received); by
+ * the session layer to stop all communication while the core service itself
+ * is in reset; and by the transport layer when the transport was unable to
+ * complete reset of a service in its reset callback (typically because
+ * a service had passed message buffers to another Linux subsystem and could
+ * not free them immediately).
+ *
+ * In any case, there is no need for the operation to be signalled in any
+ * way, because the service is already in reset. It simply delays future
+ * signalling of service readiness.
+ */
+static void disable_service(struct vs_service_device *service, bool force)
+{
+ lockdep_assert_held(&service->ready_lock);
+
+ switch(service->readiness) {
+ case VS_SERVICE_INIT:
+ case VS_SERVICE_DELETED:
+ case VS_SERVICE_LOCAL_DELETE:
+ dev_err(&service->dev, "disabled while uninitialised\n");
+ break;
+ case VS_SERVICE_ACTIVE:
+ dev_err(&service->dev, "disabled while active\n");
+ break;
+ case VS_SERVICE_LOCAL_RESET:
+ /*
+ * Will go to DISABLED state when reset completes, unless
+ * it's being forced (i.e. we're moving to a core protocol
+ * state that implies everything else is reset).
+ */
+ if (force)
+ service->readiness = VS_SERVICE_DISABLED;
+ service->disable_count++;
+ break;
+ default:
+ service->readiness = VS_SERVICE_DISABLED;
+ service->disable_count++;
+ break;
+ }
+
+ cancel_delayed_work(&service->ready_work);
+}
+
+static int service_handle_reset(struct vs_session_device *session,
+ struct vs_service_device *target, bool disable)
+{
+ struct vs_session_driver *session_drv =
+ to_vs_session_driver(session->dev.driver);
+ int err = 0;
+
+ mutex_lock_nested(&target->ready_lock, target->lock_subclass);
+
+ switch (target->readiness) {
+ case VS_SERVICE_LOCAL_DELETE:
+ target->readiness = VS_SERVICE_DELETED;
+ destroy_service(target, true);
+ break;
+ case VS_SERVICE_ACTIVE:
+ /*
+ * Reset the service and send a reset notification.
+ *
+ * We only send notifications for non-core services. This is
+ * because core notifies by sending a transport reset, which
+ * is what brought us here in the first place. Note that we
+ * must already hold the core service state lock iff the
+ * target is non-core.
+ */
+ target->last_reset_request = jiffies;
+ __reset_service(target, target->id != 0);
+ /* fall through */
+ case VS_SERVICE_LOCAL_RESET:
+ target->readiness = target->disable_count ?
+ VS_SERVICE_DISABLED : VS_SERVICE_RESET;
+ if (disable)
+ disable_service(target, false);
+ if (target->readiness != VS_SERVICE_DISABLED)
+ queue_ready_work(target);
+ break;
+ case VS_SERVICE_READY:
+ /* Tell userspace that the service is no longer ready. */
+ kobject_uevent(&target->dev.kobj, KOBJ_OFFLINE);
+ /* fall through */
+ case VS_SERVICE_RESET:
+ /*
+ * This can happen for a non-core service if we get a reset
+ * request from the server on the client side, after the
+ * client has enabled the service but before it is active.
+ * Note that the service is already active on the server side
+ * at this point. The client's delay may be due to either
+ * reset throttling or the absence of a driver.
+ *
+ * We bump the reset request timestamp, disable the service
+ * again, and send back an acknowledgement.
+ */
+ if (disable && target->id) {
+ target->last_reset_request = jiffies;
+
+ err = session_drv->service_local_reset(
+ session, target);
+ if (err < 0) {
+ dev_err(&session->dev,
+ "Failed to reset service %d; %d\n",
+ target->id, err);
+ session_fatal_error(session,
+ GFP_KERNEL);
+ }
+
+ disable_service(target, false);
+ break;
+ }
+ /* fall through */
+ case VS_SERVICE_DISABLED:
+ /*
+ * This can happen for the core service if we get a reset
+ * before the transport has activated, or before the core
+ * service has become ready.
+ *
+ * We bump the reset request timestamp, and disable the
+ * service again if the transport had already activated and
+ * enabled it.
+ */
+ if (disable && !target->id) {
+ target->last_reset_request = jiffies;
+
+ if (target->readiness != VS_SERVICE_DISABLED)
+ disable_service(target, false);
+
+ break;
+ }
+ /* fall through */
+ default:
+ dev_warn(&target->dev, "remote reset while inactive (%d)\n",
+ target->readiness);
+ err = -EPROTO;
+ break;
+ }
+
+ mutex_unlock(&target->ready_lock);
+ return err;
+}
+
+/**
+ * vs_service_handle_reset - handle an incoming notification of a reset
+ * @session: the session that owns the service
+ * @service_id: the ID of the service that is to be reset
+ * @disable: if true, the service will not be automatically re-enabled
+ *
+ * This routine is called by the core service when the remote end notifies us
+ * of a non-core service reset. The service must be in ACTIVE, LOCAL_RESET or
+ * LOCAL_DELETED state. It must be called with the core service's state lock
+ * held.
+ *
+ * If the service was in ACTIVE state, the core service is called back to send
+ * a notification to the other end. If it was in LOCAL_DELETED state, it is
+ * unregistered.
+ */
+int vs_service_handle_reset(struct vs_session_device *session,
+ vs_service_id_t service_id, bool disable)
+{
+ struct vs_service_device *target;
+ int ret;
+
+ if (!service_id)
+ return -EINVAL;
+
+ target = vs_session_get_service(session, service_id);
+ if (!target)
+ return -ENODEV;
+
+ ret = service_handle_reset(session, target, disable);
+ vs_put_service(target);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(vs_service_handle_reset);
+
+static int __enable_service(struct vs_service_device *service)
+{
+ if (WARN_ON(!service->disable_count))
+ return -EINVAL;
+
+ if (--service->disable_count > 0)
+ return 0;
+
+ /*
+ * If the service is still resetting, it can't become ready until the
+ * reset completes. If it has been deleted, it will never become
+ * ready. In either case, there's nothing more to do.
+ */
+ if ((service->readiness == VS_SERVICE_LOCAL_RESET) ||
+ (service->readiness == VS_SERVICE_LOCAL_DELETE) ||
+ (service->readiness == VS_SERVICE_DELETED))
+ return 0;
+
+ if (WARN_ON(service->readiness != VS_SERVICE_DISABLED))
+ return -EINVAL;
+
+ service->readiness = VS_SERVICE_RESET;
+ service->last_reset = jiffies;
+ queue_ready_work(service);
+
+ return 0;
+}
+
+/**
+ * vs_service_enable - allow a service to become ready
+ * @service: the service that is to be enabled
+ *
+ * Calling this routine for a service permits the session layer to make the
+ * service ready. It will do so as soon as any outstanding reset throttling
+ * is complete, and will then start the service once it has a driver attached.
+ *
+ * Services are disabled, requiring a call to this routine to re-enable them:
+ * - when first initialised (after vs_service_start),
+ * - when reset on the client side by vs_service_handle_reset,
+ * - when the transport has delayed completion of a reset, and
+ * - when the server-side core protocol is disconnected or reset by
+ * vs_session_disable_noncore.
+ */
+int vs_service_enable(struct vs_service_device *service)
+{
+ int ret;
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ ret = __enable_service(service);
+
+ mutex_unlock(&service->ready_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(vs_service_enable);
+
+/*
+ * Service work functions
+ */
+static void queue_rx_work(struct vs_service_device *service)
+{
+ bool rx_atomic;
+
+ rx_atomic = vs_service_has_atomic_rx(service);
+ vs_dev_debug(VS_DEBUG_SESSION, vs_service_get_session(service),
+ &service->dev, "Queuing rx %s\n",
+ rx_atomic ? "tasklet (atomic)" : "work (cansleep)");
+
+ if (rx_atomic)
+ tasklet_schedule(&service->rx_tasklet);
+ else
+ queue_work(service->work_queue, &service->rx_work);
+}
+
+static void cancel_pending_rx(struct vs_service_device *service)
+{
+ struct vs_mbuf *mbuf;
+
+ lockdep_assert_held(&service->ready_lock);
+
+ cancel_work_sync(&service->rx_work);
+ tasklet_kill(&service->rx_tasklet);
+
+ spin_lock_irq(&service->rx_lock);
+ while (!list_empty(&service->rx_queue)) {
+ mbuf = list_first_entry(&service->rx_queue,
+ struct vs_mbuf, queue);
+ list_del_init(&mbuf->queue);
+ spin_unlock_irq(&service->rx_lock);
+ vs_service_free_mbuf(service, mbuf);
+ spin_lock_irq(&service->rx_lock);
+ }
+ service->tx_ready = false;
+ spin_unlock_irq(&service->rx_lock);
+}
+
+static bool reset_throttle_cooled_off(struct vs_service_device *service);
+static unsigned long reset_cool_off(struct vs_service_device *service);
+
+static void service_cooloff_work(struct work_struct *work)
+{
+ struct vs_service_device *service = container_of(work,
+ struct vs_service_device, cooloff_work.work);
+ struct vs_session_device *session = vs_service_get_session(service);
+ unsigned long current_time = jiffies, wake_time;
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ if (reset_throttle_cooled_off(service)) {
+ vs_debug(VS_DEBUG_SESSION, session,
+ "Reset thrashing cooled off (delay = %u ms, cool off = %u ms, last reset %u ms ago, last reset request was %u ms ago)\n",
+ jiffies_to_msecs(service->reset_delay),
+ jiffies_to_msecs(reset_cool_off(service)),
+ msecs_ago(service->last_reset),
+ msecs_ago(service->last_reset_request));
+
+ service->reset_delay = 0;
+
+ /*
+ * If the service is already in reset, then queue_ready_work
+ * has already run and has deferred queuing of the ready_work
+ * until cooloff. Schedule the ready work to run immediately.
+ */
+ if (service->readiness == VS_SERVICE_RESET)
+ schedule_delayed_work(&service->ready_work, 0);
+ } else {
+ /*
+ * This can happen if last_reset_request has been bumped
+ * since the cooloff work was first queued. We need to
+ * work out how long it is until the service cools off,
+ * then reschedule ourselves.
+ */
+ wake_time = reset_cool_off(service) +
+ service->last_reset_request;
+
+ WARN_ON(time_after(current_time, wake_time));
+
+ schedule_delayed_work(&service->cooloff_work,
+ wake_time - current_time);
+ }
+
+ mutex_unlock(&service->ready_lock);
+}
+
+static void
+service_reset_work(struct work_struct *work)
+{
+ struct vs_service_device *service = container_of(work,
+ struct vs_service_device, reset_work);
+
+ service->pending_reset = false;
+
+ vs_service_reset(service, service);
+}
+
+/* Returns true if there are more messages to handle */
+static bool
+dequeue_and_handle_received_message(struct vs_service_device *service)
+{
+ struct vs_service_driver *driver =
+ to_vs_service_driver(service->dev.driver);
+ struct vs_session_device *session = vs_service_get_session(service);
+ const struct vs_transport_vtable *vt = session->transport->vt;
+ struct vs_service_stats *stats = &service->stats;
+ struct vs_mbuf *mbuf;
+ size_t size;
+ int ret;
+
+ /* Don't do rx work unless the service is active */
+ if (service->readiness != VS_SERVICE_ACTIVE)
+ return false;
+
+ /* Atomically take an item from the queue */
+ spin_lock_irq(&service->rx_lock);
+ if (!list_empty(&service->rx_queue)) {
+ mbuf = list_first_entry(&service->rx_queue, struct vs_mbuf,
+ queue);
+ list_del_init(&mbuf->queue);
+ spin_unlock_irq(&service->rx_lock);
+ size = vt->mbuf_size(mbuf);
+
+ /*
+ * Call the message handler for the service. The service's
+ * message handler is responsible for freeing the mbuf when it
+ * is done with it.
+ */
+ ret = driver->receive(service, mbuf);
+ if (ret < 0) {
+ atomic_inc(&service->stats.recv_failures);
+ dev_err(&service->dev,
+ "receive returned %d; resetting service\n",
+ ret);
+ vs_service_reset_nosync(service);
+ return false;
+ } else {
+ atomic_add(size, &service->stats.recv_bytes);
+ atomic_inc(&service->stats.recv_mbufs);
+ }
+
+ } else if (service->tx_ready) {
+ service->tx_ready = false;
+ spin_unlock_irq(&service->rx_lock);
+
+ /*
+ * Update the tx_ready stats accounting and then call the
+ * service's tx_ready handler.
+ */
+ atomic_inc(&stats->nr_tx_ready);
+ if (atomic_read(&stats->nr_over_quota) > 0) {
+ int total;
+
+ total = atomic_add_return(jiffies_to_msecs(jiffies -
+ stats->over_quota_time),
+ &stats->over_quota_time_total);
+ atomic_set(&stats->over_quota_time_avg, total /
+ atomic_read(&stats->nr_over_quota));
+ }
+ atomic_set(&service->is_over_quota, 0);
+
+ /*
+ * Note that a service's quota may reduce at any point, even
+ * during the tx_ready handler. This is important if a service
+ * has an ordered list of pending messages to send. If a
+ * message fails to send from the tx_ready handler due to
+ * over-quota then subsequent messages in the same handler may
+ * send successfully. To avoid sending messages in the
+ * incorrect order the service's tx_ready handler should
+ * return immediately if a message fails to send.
+ */
+ ret = driver->tx_ready(service);
+ if (ret < 0) {
+ dev_err(&service->dev,
+ "tx_ready returned %d; resetting service\n",
+ ret);
+ vs_service_reset_nosync(service);
+ return false;
+ }
+ } else {
+ spin_unlock_irq(&service->rx_lock);
+ }
+
+ /*
+ * There's no need to lock for this list_empty: if we race
+ * with a msg enqueue, we'll be rescheduled by the other side,
+ * and if we race with a dequeue, we'll just do nothing when
+ * we run (or will be cancelled before we run).
+ */
+ return !list_empty(&service->rx_queue) || service->tx_ready;
+}
+
+static void service_rx_tasklet(unsigned long data)
+{
+ struct vs_service_device *service = (struct vs_service_device *)data;
+ bool resched;
+
+ /*
+ * There is no need to acquire the state spinlock or mutex here,
+ * because this tasklet is disabled when the lock is held. These
+ * are annotations for sparse and lockdep, respectively.
+ *
+ * We can't annotate the implicit mutex acquire because lockdep gets
+ * upset about inconsistent softirq states.
+ */
+ __acquire(service);
+ spin_acquire(&service->state_spinlock.dep_map, 0, 0, _THIS_IP_);
+
+ resched = dequeue_and_handle_received_message(service);
+
+ if (resched)
+ tasklet_schedule(&service->rx_tasklet);
+
+ spin_release(&service->state_spinlock.dep_map, 0, _THIS_IP_);
+ __release(service);
+}
+
+static void service_rx_work(struct work_struct *work)
+{
+ struct vs_service_device *service = container_of(work,
+ struct vs_service_device, rx_work);
+ bool requeue;
+
+ /*
+ * We must acquire the state mutex here to protect services that
+ * are using vs_service_state_lock().
+ *
+ * There is no need to acquire the spinlock, which is never used in
+ * drivers with task context receive handlers.
+ */
+ vs_service_state_lock(service);
+
+ requeue = dequeue_and_handle_received_message(service);
+
+ vs_service_state_unlock(service);
+
+ if (requeue)
+ queue_work(service->work_queue, work);
+}
+
+/*
+ * Service sysfs statistics counters. These files are all atomic_t, and
+ * read only, so we use a generator macro to avoid code duplication.
+ */
+#define service_stat_attr(__name) \
+ static ssize_t service_stat_##__name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ struct vs_service_device *service = \
+ to_vs_service_device(dev); \
+ \
+ return scnprintf(buf, PAGE_SIZE, "%u\n", \
+ atomic_read(&service->stats.__name)); \
+ } \
+ static DEVICE_ATTR(__name, S_IRUGO, \
+ service_stat_##__name##_show, NULL);
+
+service_stat_attr(sent_mbufs);
+service_stat_attr(sent_bytes);
+service_stat_attr(recv_mbufs);
+service_stat_attr(recv_bytes);
+service_stat_attr(nr_over_quota);
+service_stat_attr(nr_tx_ready);
+service_stat_attr(over_quota_time_total);
+service_stat_attr(over_quota_time_avg);
+
+static struct attribute *service_stat_dev_attrs[] = {
+ &dev_attr_sent_mbufs.attr,
+ &dev_attr_sent_bytes.attr,
+ &dev_attr_recv_mbufs.attr,
+ &dev_attr_recv_bytes.attr,
+ &dev_attr_nr_over_quota.attr,
+ &dev_attr_nr_tx_ready.attr,
+ &dev_attr_over_quota_time_total.attr,
+ &dev_attr_over_quota_time_avg.attr,
+ NULL,
+};
+
+static const struct attribute_group service_stat_attributes = {
+ .name = "stats",
+ .attrs = service_stat_dev_attrs,
+};
+
+static void delete_service(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ bool notify_on_destroy = true;
+
+ /* FIXME: Jira ticket SDK-3495 - philipd. */
+ /* This should be the caller's responsibility */
+ vs_get_service(service);
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ /*
+ * If we're on the client side, the service should already have been
+ * disabled at this point.
+ */
+ WARN_ON(service->id != 0 && !session->is_server &&
+ service->readiness != VS_SERVICE_DISABLED &&
+ service->readiness != VS_SERVICE_DELETED);
+
+ /*
+ * Make sure the service is not active, and notify the remote end if
+ * it needs to be reset. Note that we already hold the core service
+ * state lock iff this is a non-core service.
+ */
+ __reset_service(service, true);
+
+ /*
+ * If the remote end is aware that the service is inactive, we can
+ * delete right away; otherwise we need to wait for a notification
+ * that the service has reset.
+ */
+ switch (service->readiness) {
+ case VS_SERVICE_LOCAL_DELETE:
+ case VS_SERVICE_DELETED:
+ /* Nothing to do here */
+ mutex_unlock(&service->ready_lock);
+ vs_put_service(service);
+ return;
+ case VS_SERVICE_ACTIVE:
+ BUG();
+ break;
+ case VS_SERVICE_LOCAL_RESET:
+ service->readiness = VS_SERVICE_LOCAL_DELETE;
+ break;
+ case VS_SERVICE_INIT:
+ notify_on_destroy = false;
+ /* Fall through */
+ default:
+ service->readiness = VS_SERVICE_DELETED;
+ destroy_service(service, notify_on_destroy);
+ break;
+ }
+
+ mutex_unlock(&service->ready_lock);
+
+ /*
+ * Remove service syslink from
+ * sys/vservices/(<server>/<client>)-sessions/ directory
+ */
+ vs_service_remove_sysfs_entries(session, service);
+
+ sysfs_remove_group(&service->dev.kobj, &service_stat_attributes);
+
+ /*
+ * On the client-side we need to release the service id as soon as
+ * the service is deleted. Otherwise the server may attempt to create
+ * a new service with this id.
+ */
+ if (!session->is_server)
+ vs_session_release_service_id(service);
+
+ device_del(&service->dev);
+ vs_put_service(service);
+}
+
+/**
+ * vs_service_delete - deactivate and start removing a service device
+ * @service: the service to delete
+ * @caller: the service initiating deletion
+ *
+ * Services may only be deleted by their owner (on the server side), or by the
+ * core service. This function must not be called for the core service.
+ */
+int vs_service_delete(struct vs_service_device *service,
+ struct vs_service_device *caller)
+{
+ struct vs_session_device *session =
+ vs_service_get_session(service);
+ struct vs_service_device *core_service = session->core_service;
+
+ if (WARN_ON(!core_service))
+ return -ENODEV;
+
+ if (!service->id)
+ return -EINVAL;
+
+ if (caller != service->owner && caller != core_service)
+ return -EPERM;
+
+ delete_service(service);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_service_delete);
+
+/**
+ * vs_service_handle_delete - deactivate and start removing a service device
+ * @service: the service to delete
+ *
+ * This is a variant of vs_service_delete which must only be called by the
+ * core service. It is used by the core service client when a service_removed
+ * message is received.
+ */
+int vs_service_handle_delete(struct vs_service_device *service)
+{
+ struct vs_session_device *session __maybe_unused =
+ vs_service_get_session(service);
+ struct vs_service_device *core_service __maybe_unused =
+ session->core_service;
+
+ lockdep_assert_held(&core_service->state_mutex);
+
+ delete_service(service);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_service_handle_delete);
+
+static void service_cleanup_work(struct work_struct *work)
+{
+ struct vs_service_device *service = container_of(work,
+ struct vs_service_device, cleanup_work);
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, "cleanup\n");
+
+ if (service->owner)
+ vs_put_service(service->owner);
+
+ /* Put our reference to the session */
+ if (service->dev.parent)
+ put_device(service->dev.parent);
+
+ tasklet_kill(&service->rx_tasklet);
+ cancel_work_sync(&service->rx_work);
+ cancel_delayed_work_sync(&service->cooloff_work);
+ cancel_delayed_work_sync(&service->ready_work);
+ cancel_work_sync(&service->reset_work);
+
+ if (service->work_queue)
+ destroy_workqueue(service->work_queue);
+
+ kfree(service->sysfs_name);
+ kfree(service->name);
+ kfree(service->protocol);
+ kfree(service);
+}
+
+static void vs_service_release(struct device *dev)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+
+ vs_dev_debug(VS_DEBUG_SESSION, vs_service_get_session(service),
+ &service->dev, "release\n");
+
+ /*
+ * We need to defer cleanup to avoid a circular dependency between the
+ * core service's state lock (which can be held at this point, on the
+ * client side) and any non-core service's reset work (which we must
+ * cancel here, and which acquires the core service state lock).
+ */
+ schedule_work(&service->cleanup_work);
+}
+
+static int service_add_idr(struct vs_session_device *session,
+ struct vs_service_device *service, vs_service_id_t service_id)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
+ int err, base_id, id;
+
+ if (service_id == VS_SERVICE_AUTO_ALLOCATE_ID)
+ base_id = 1;
+ else
+ base_id = service_id;
+
+retry:
+ if (!idr_pre_get(&session->service_idr, GFP_KERNEL))
+ return -ENOMEM;
+
+ mutex_lock(&session->service_idr_lock);
+ err = idr_get_new_above(&session->service_idr, service, base_id, &id);
+ if (err == 0) {
+ if (service_id != VS_SERVICE_AUTO_ALLOCATE_ID &&
+ id != service_id) {
+ /* Failed to allocated the requested service id */
+ idr_remove(&session->service_idr, id);
+ mutex_unlock(&session->service_idr_lock);
+ return -EBUSY;
+ }
+ if (id > VS_MAX_SERVICE_ID) {
+ /* We are out of service ids */
+ idr_remove(&session->service_idr, id);
+ mutex_unlock(&session->service_idr_lock);
+ return -ENOSPC;
+ }
+ }
+ mutex_unlock(&session->service_idr_lock);
+ if (err == -EAGAIN)
+ goto retry;
+ if (err < 0)
+ return err;
+#else
+ int start, end, id;
+
+ if (service_id == VS_SERVICE_AUTO_ALLOCATE_ID) {
+ start = 1;
+ end = VS_MAX_SERVICES;
+ } else {
+ start = service_id;
+ end = service_id + 1;
+ }
+
+ mutex_lock(&session->service_idr_lock);
+ id = idr_alloc(&session->service_idr, service, start, end,
+ GFP_KERNEL);
+ mutex_unlock(&session->service_idr_lock);
+
+ if (id == -ENOSPC)
+ return -EBUSY;
+ else if (id < 0)
+ return id;
+#endif
+
+ service->id = id;
+ return 0;
+}
+
+static int
+vs_service_create_sysfs_entries(struct vs_session_device *session,
+ struct vs_service_device *service, vs_service_id_t id)
+{
+ int ret;
+ char *sysfs_name, *c;
+
+ /* Add a symlink to session device inside service device sysfs */
+ ret = sysfs_create_link(&service->dev.kobj, &session->dev.kobj,
+ VS_SESSION_SYMLINK_NAME);
+ if (ret) {
+ dev_err(&service->dev, "Error %d creating session symlink\n",
+ ret);
+ goto fail;
+ }
+
+ /* Get the length of the string for sysfs dir */
+ sysfs_name = kasprintf(GFP_KERNEL, "%s:%d", service->name, id);
+ if (!sysfs_name) {
+ ret = -ENOMEM;
+ goto fail_session_link;
+ }
+
+ /*
+ * We dont want to create symlinks with /'s which could get interpreted
+ * as another directory so replace all /'s with !'s
+ */
+ while ((c = strchr(sysfs_name, '/')))
+ *c = '!';
+ ret = sysfs_create_link(session->sysfs_entry, &service->dev.kobj,
+ sysfs_name);
+ if (ret)
+ goto fail_free_sysfs_name;
+
+ service->sysfs_name = sysfs_name;
+
+ return 0;
+
+fail_free_sysfs_name:
+ kfree(sysfs_name);
+fail_session_link:
+ sysfs_remove_link(&service->dev.kobj, VS_SESSION_SYMLINK_NAME);
+fail:
+ return ret;
+}
+
+/**
+ * vs_service_register - create and register a new vs_service_device
+ * @session: the session device that is the parent of the service
+ * @owner: the service responsible for managing the new service
+ * @service_id: the ID of the new service
+ * @name: the name of the new service
+ * @protocol: the protocol for the new service
+ * @plat_data: value to be assigned to (struct device *)->platform_data
+ *
+ * This function should only be called by a session driver that is bound to
+ * the given session.
+ *
+ * The given service_id must not have been passed to a prior successful
+ * vs_service_register call, unless the service ID has since been freed by a
+ * call to the session driver's service_removed callback.
+ *
+ * The core service state lock must not be held while calling this function.
+ */
+struct vs_service_device *vs_service_register(struct vs_session_device *session,
+ struct vs_service_device *owner, vs_service_id_t service_id,
+ const char *protocol, const char *name, const void *plat_data)
+{
+ struct vs_service_device *service;
+ struct vs_session_driver *session_drv;
+ int ret = -EIO;
+ char *c;
+
+ if (service_id && !owner) {
+ dev_err(&session->dev, "Non-core service must have an owner\n");
+ ret = -EINVAL;
+ goto fail;
+ } else if (!service_id && owner) {
+ dev_err(&session->dev, "Core service must not have an owner\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!session->dev.driver)
+ goto fail;
+
+ session_drv = to_vs_session_driver(session->dev.driver);
+
+ service = kzalloc(sizeof(*service), GFP_KERNEL);
+ if (!service) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ INIT_LIST_HEAD(&service->rx_queue);
+ INIT_WORK(&service->rx_work, service_rx_work);
+ INIT_WORK(&service->reset_work, service_reset_work);
+ INIT_DELAYED_WORK(&service->ready_work, service_ready_work);
+ INIT_DELAYED_WORK(&service->cooloff_work, service_cooloff_work);
+ INIT_WORK(&service->cleanup_work, service_cleanup_work);
+ spin_lock_init(&service->rx_lock);
+ init_waitqueue_head(&service->quota_wq);
+
+ service->owner = vs_get_service(owner);
+
+ service->readiness = VS_SERVICE_INIT;
+ mutex_init(&service->ready_lock);
+ service->driver_probed = false;
+
+ /*
+ * Service state locks - A service is only allowed to use one of these
+ */
+ spin_lock_init(&service->state_spinlock);
+ mutex_init(&service->state_mutex);
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ service->state_spinlock_used = false;
+ service->state_mutex_used = false;
+#endif
+
+ /* Lock ordering
+ *
+ * The dependency order for the various service locks is as follows:
+ *
+ * cooloff_work
+ * reset_work
+ * ready_work
+ * ready_lock/0
+ * rx_work/0
+ * state_mutex/0
+ * ready_lock/1
+ * ...
+ * state_mutex/n
+ * state_spinlock
+ *
+ * The subclass is the service's rank in the hierarchy of
+ * service ownership. This results in core having subclass 0 on
+ * server-side and 1 on client-side. Services directly created
+ * by the core will have a lock subclass value of 2 for
+ * servers, 3 for clients. Services created by non-core
+ * services will have a lock subclass value of x + 1, where x
+ * is the lock subclass of the creator service. (e.g servers
+ * will have even numbered lock subclasses, clients will have
+ * odd numbered lock subclasses).
+ *
+ * If a service driver has any additional locks for protecting
+ * internal state, they will generally fit between state_mutex/n and
+ * ready_lock/n+1 on this list. For the core service, this applies to
+ * the session lock.
+ */
+
+ if (owner)
+ service->lock_subclass = owner->lock_subclass + 2;
+ else
+ service->lock_subclass = session->is_server ? 0 : 1;
+
+#ifdef CONFIG_LOCKDEP
+ if (service->lock_subclass >= MAX_LOCKDEP_SUBCLASSES) {
+ dev_warn(&session->dev, "Owner hierarchy is too deep, lockdep will fail\n");
+ } else {
+ /*
+ * We need to set the default subclass for the rx work,
+ * because the workqueue API doesn't (and can't) provide
+ * anything like lock_nested() for it.
+ */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0)
+ /*
+ * Lockdep allows a specific lock's subclass to be set with
+ * the subclass argument to lockdep_init_map(). However, prior
+ * to Linux 3.3, that only works the first time it is called
+ * for a given class and subclass. So we have to fake it,
+ * putting every subclass in a different class, so the only
+ * thing that breaks is printing the subclass in lockdep
+ * warnings.
+ */
+ static struct lock_class_key
+ rx_work_keys[MAX_LOCKDEP_SUBCLASSES];
+ struct lock_class_key *key =
+ &rx_work_keys[service->lock_subclass];
+#else
+ struct lock_class_key *key = service->rx_work.lockdep_map.key;
+#endif
+
+ /*
+ * We can't use the lockdep_set_class() macro because the
+ * work's lockdep map is called .lockdep_map instead of
+ * .dep_map.
+ */
+ lockdep_init_map(&service->rx_work.lockdep_map,
+ "&service->rx_work", key,
+ service->lock_subclass);
+ }
+#endif
+
+ /*
+ * Copy the protocol and name. Remove any leading or trailing
+ * whitespace characters (including newlines) since the strings
+ * may have been passed via sysfs files.
+ */
+ if (protocol) {
+ service->protocol = kstrdup(protocol, GFP_KERNEL);
+ if (!service->protocol) {
+ ret = -ENOMEM;
+ goto fail_copy_protocol;
+ }
+ c = strim(service->protocol);
+ if (c != service->protocol)
+ memmove(service->protocol, c,
+ strlen(service->protocol) + 1);
+ }
+
+ service->name = kstrdup(name, GFP_KERNEL);
+ if (!service->name) {
+ ret = -ENOMEM;
+ goto fail_copy_name;
+ }
+ c = strim(service->name);
+ if (c != service->name)
+ memmove(service->name, c, strlen(service->name) + 1);
+
+ service->is_server = session_drv->is_server;
+
+ /* Grab a reference to the session we are on */
+ service->dev.parent = get_device(&session->dev);
+ service->dev.bus = session_drv->service_bus;
+ service->dev.release = vs_service_release;
+
+ service->last_reset = 0;
+ service->last_reset_request = 0;
+ service->last_ready = 0;
+ service->reset_delay = 0;
+
+ device_initialize(&service->dev);
+ service->dev.platform_data = (void *)plat_data;
+
+ ret = service_add_idr(session, service, service_id);
+ if (ret)
+ goto fail_add_idr;
+
+#ifdef CONFIG_VSERVICES_NAMED_DEVICE
+ /* Integrate session and service names in vservice devnodes */
+ dev_set_name(&service->dev, "vservice-%s:%s:%s:%d:%d",
+ session->is_server ? "server" : "client",
+ session->name, service->name,
+ session->session_num, service->id);
+#else
+ dev_set_name(&service->dev, "%s:%d", dev_name(&session->dev),
+ service->id);
+#endif
+
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+ if (service->id > 0)
+ service->dev.devt = MKDEV(vservices_cdev_major,
+ (session->session_num * VS_MAX_SERVICES) +
+ service->id);
+#endif
+
+ service->work_queue = vs_create_workqueue(dev_name(&service->dev));
+ if (!service->work_queue) {
+ ret = -ENOMEM;
+ goto fail_create_workqueue;
+ }
+
+ tasklet_init(&service->rx_tasklet, service_rx_tasklet,
+ (unsigned long)service);
+
+ /*
+ * If this is the core service, set the core service pointer in the
+ * session.
+ */
+ if (service->id == 0) {
+ mutex_lock(&session->service_idr_lock);
+ if (session->core_service) {
+ ret = -EEXIST;
+ mutex_unlock(&session->service_idr_lock);
+ goto fail_become_core;
+ }
+
+ /* Put in vs_session_bus_remove() */
+ session->core_service = vs_get_service(service);
+ mutex_unlock(&session->service_idr_lock);
+ }
+
+ /* Notify the transport */
+ ret = session->transport->vt->service_add(session->transport, service);
+ if (ret) {
+ dev_err(&session->dev,
+ "Failed to add service %d (%s:%s) to transport: %d\n",
+ service->id, service->name,
+ service->protocol, ret);
+ goto fail_transport_add;
+ }
+
+ /* Delay uevent until vs_service_start(). */
+ dev_set_uevent_suppress(&service->dev, true);
+
+ ret = device_add(&service->dev);
+ if (ret)
+ goto fail_device_add;
+
+ /* Create the service statistics sysfs group */
+ ret = sysfs_create_group(&service->dev.kobj, &service_stat_attributes);
+ if (ret)
+ goto fail_sysfs_create_group;
+
+ /* Create additional sysfs files */
+ ret = vs_service_create_sysfs_entries(session, service, service->id);
+ if (ret)
+ goto fail_sysfs_add_entries;
+
+ return service;
+
+fail_sysfs_add_entries:
+ sysfs_remove_group(&service->dev.kobj, &service_stat_attributes);
+fail_sysfs_create_group:
+ device_del(&service->dev);
+fail_device_add:
+ session->transport->vt->service_remove(session->transport, service);
+fail_transport_add:
+ if (service->id == 0) {
+ session->core_service = NULL;
+ vs_put_service(service);
+ }
+fail_become_core:
+fail_create_workqueue:
+ vs_session_release_service_id(service);
+fail_add_idr:
+ /*
+ * device_initialize() has been called, so we must call put_device()
+ * and let vs_service_release() handle the rest of the cleanup.
+ */
+ put_device(&service->dev);
+ return ERR_PTR(ret);
+
+fail_copy_name:
+ if (service->protocol)
+ kfree(service->protocol);
+fail_copy_protocol:
+ kfree(service);
+fail:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(vs_service_register);
+
+/**
+ * vs_session_get_service - Look up a service by ID on a session and get
+ * a reference to it. The caller must call vs_put_service when it is finished
+ * with the service.
+ *
+ * @session: The session to search for the service on
+ * @service_id: ID of the service to find
+ */
+struct vs_service_device *
+vs_session_get_service(struct vs_session_device *session,
+ vs_service_id_t service_id)
+{
+ struct vs_service_device *service;
+
+ if (!session)
+ return NULL;
+
+ rcu_read_lock();
+ service = idr_find(&session->service_idr, service_id);
+ if (!service) {
+ rcu_read_unlock();
+ return NULL;
+ }
+ vs_get_service(service);
+ rcu_read_unlock();
+
+ return service;
+}
+EXPORT_SYMBOL_GPL(vs_session_get_service);
+
+/**
+ * __for_each_service - Iterate over all non-core services on a session.
+ *
+ * @session: Session to iterate services on
+ * @func: Callback function for each iterated service
+ *
+ * Iterate over all services on a session, excluding the core service, and
+ * call a callback function on each.
+ */
+static void __for_each_service(struct vs_session_device *session,
+ void (*func)(struct vs_service_device *))
+{
+ struct vs_service_device *service;
+ int id;
+
+ for (id = 1; ; id++) {
+ rcu_read_lock();
+ service = idr_get_next(&session->service_idr, &id);
+ if (!service) {
+ rcu_read_unlock();
+ break;
+ }
+ vs_get_service(service);
+ rcu_read_unlock();
+
+ func(service);
+ vs_put_service(service);
+ }
+}
+
+/**
+ * vs_session_delete_noncore - immediately delete all non-core services
+ * @session: the session whose services are to be deleted
+ *
+ * This function disables and deletes all non-core services without notifying
+ * the core service. It must only be called by the core service, with its state
+ * lock held. It is used when the core service client disconnects or
+ * resets, and when the core service server has its driver removed.
+ */
+void vs_session_delete_noncore(struct vs_session_device *session)
+{
+ struct vs_service_device *core_service __maybe_unused =
+ session->core_service;
+
+ lockdep_assert_held(&core_service->state_mutex);
+
+ vs_session_disable_noncore(session);
+
+ __for_each_service(session, delete_service);
+}
+EXPORT_SYMBOL_GPL(vs_session_delete_noncore);
+
+/**
+ * vs_session_for_each_service - Iterate over all initialised and non-deleted
+ * non-core services on a session.
+ *
+ * @session: Session to iterate services on
+ * @func: Callback function for each iterated service
+ * @data: Extra data to pass to the callback
+ *
+ * Iterate over all services on a session, excluding the core service and any
+ * service that has been deleted or has not yet had vs_service_start() called,
+ * and call a callback function on each. The callback function is called with
+ * the service's ready lock held.
+ */
+void vs_session_for_each_service(struct vs_session_device *session,
+ void (*func)(struct vs_service_device *, void *), void *data)
+{
+ struct vs_service_device *service;
+ int id;
+
+ for (id = 1; ; id++) {
+ rcu_read_lock();
+ service = idr_get_next(&session->service_idr, &id);
+ if (!service) {
+ rcu_read_unlock();
+ break;
+ }
+ vs_get_service(service);
+ rcu_read_unlock();
+
+ mutex_lock_nested(&service->ready_lock, service->lock_subclass);
+
+ if (service->readiness != VS_SERVICE_LOCAL_DELETE &&
+ service->readiness != VS_SERVICE_DELETED &&
+ service->readiness != VS_SERVICE_INIT)
+ func(service, data);
+
+ mutex_unlock(&service->ready_lock);
+ vs_put_service(service);
+ }
+}
+
+static void force_disable_service(struct vs_service_device *service,
+ void *unused)
+{
+ lockdep_assert_held(&service->ready_lock);
+
+ if (service->readiness == VS_SERVICE_ACTIVE)
+ __reset_service(service, false);
+
+ disable_service(service, true);
+}
+
+/**
+ * vs_session_disable_noncore - immediately disable all non-core services
+ * @session: the session whose services are to be disabled
+ *
+ * This function must be called by the core service driver to disable all
+ * services, whenever it resets or is otherwise disconnected. It is called
+ * directly by the server-side core service, and by the client-side core
+ * service via vs_session_delete_noncore().
+ */
+void vs_session_disable_noncore(struct vs_session_device *session)
+{
+ vs_session_for_each_service(session, force_disable_service, NULL);
+}
+EXPORT_SYMBOL_GPL(vs_session_disable_noncore);
+
+static void try_enable_service(struct vs_service_device *service, void *unused)
+{
+ lockdep_assert_held(&service->ready_lock);
+
+ __enable_service(service);
+}
+
+/**
+ * vs_session_enable_noncore - enable all disabled non-core services
+ * @session: the session whose services are to be enabled
+ *
+ * This function is called by the core server driver to enable all services
+ * when the core client connects.
+ */
+void vs_session_enable_noncore(struct vs_session_device *session)
+{
+ vs_session_for_each_service(session, try_enable_service, NULL);
+}
+EXPORT_SYMBOL_GPL(vs_session_enable_noncore);
+
+/**
+ * vs_session_handle_message - process an incoming message from a transport
+ * @session: the session that is receiving the message
+ * @mbuf: a buffer containing the message payload
+ * @service_id: the id of the service that the message was addressed to
+ *
+ * This routine will return 0 if the buffer was accepted, or a negative value
+ * otherwise. In the latter case the caller should free the buffer. If the
+ * error is fatal, this routine will reset the service.
+ *
+ * This routine may be called from interrupt context.
+ *
+ * The caller must always serialise calls to this function relative to
+ * vs_session_handle_reset and vs_session_handle_activate. We don't do this
+ * internally, to avoid having to disable interrupts when called from task
+ * context.
+ */
+int vs_session_handle_message(struct vs_session_device *session,
+ struct vs_mbuf *mbuf, vs_service_id_t service_id)
+{
+ struct vs_service_device *service;
+ struct vs_transport *transport;
+ unsigned long flags;
+
+ transport = session->transport;
+
+ service = vs_session_get_service(session, service_id);
+ if (!service) {
+ dev_err(&session->dev, "message for unknown service %d\n",
+ service_id);
+ session_fatal_error(session, GFP_ATOMIC);
+ return -ENOTCONN;
+ }
+
+ /*
+ * Take the rx lock before checking service readiness. This guarantees
+ * that if __reset_service() has just made the service inactive, we
+ * either see it and don't enqueue the message, or else enqueue the
+ * message before cancel_pending_rx() runs (and removes it).
+ */
+ spin_lock_irqsave(&service->rx_lock, flags);
+
+ /* If the service is not active, drop the message. */
+ if (service->readiness != VS_SERVICE_ACTIVE) {
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+ vs_put_service(service);
+ return -ECONNRESET;
+ }
+
+ list_add_tail(&mbuf->queue, &service->rx_queue);
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+
+ /* Schedule processing of the message by the service's drivers. */
+ queue_rx_work(service);
+ vs_put_service(service);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_session_handle_message);
+
+/**
+ * vs_session_quota_available - notify a service that it can transmit
+ * @session: the session owning the service that is ready
+ * @service_id: the id of the service that is ready
+ * @count: the number of buffers that just became ready
+ * @call_tx_ready: true if quota has just become nonzero due to a buffer being
+ * freed by the remote communication partner
+ *
+ * This routine is called by the transport driver when a send-direction
+ * message buffer becomes free. It wakes up any task that is waiting for
+ * send quota to become available.
+ *
+ * This routine may be called from interrupt context from the transport
+ * driver, and as such, it may not sleep.
+ *
+ * The caller must always serialise calls to this function relative to
+ * vs_session_handle_reset and vs_session_handle_activate. We don't do this
+ * internally, to avoid having to disable interrupts when called from task
+ * context.
+ *
+ * If the call_tx_ready argument is true, this function also schedules a
+ * call to the driver's tx_ready callback. Note that this never has priority
+ * over handling incoming messages; it will only be handled once the receive
+ * queue is empty. This is to increase batching of outgoing messages, and also
+ * to reduce the chance that an outgoing message will be dropped by the partner
+ * because an incoming message has already changed the state.
+ *
+ * In general, task context drivers should use the waitqueue, and softirq
+ * context drivers (with tx_atomic set) should use tx_ready.
+ */
+void vs_session_quota_available(struct vs_session_device *session,
+ vs_service_id_t service_id, unsigned count,
+ bool send_tx_ready)
+{
+ struct vs_service_device *service;
+ unsigned long flags;
+
+ service = vs_session_get_service(session, service_id);
+ if (!service) {
+ dev_err(&session->dev, "tx ready for unknown service %d\n",
+ service_id);
+ session_fatal_error(session, GFP_ATOMIC);
+ return;
+ }
+
+ wake_up_nr(&service->quota_wq, count);
+
+ if (send_tx_ready) {
+ /*
+ * Take the rx lock before checking service readiness. This
+ * guarantees that if __reset_service() has just made the
+ * service inactive, we either see it and don't set the tx_ready
+ * flag, or else set the flag before cancel_pending_rx() runs
+ * (and clears it).
+ */
+ spin_lock_irqsave(&service->rx_lock, flags);
+
+ /* If the service is not active, drop the tx_ready event */
+ if (service->readiness != VS_SERVICE_ACTIVE) {
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+ vs_put_service(service);
+ return;
+ }
+
+ service->tx_ready = true;
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+
+ /* Schedule RX processing by the service driver. */
+ queue_rx_work(service);
+ }
+
+ vs_put_service(service);
+}
+EXPORT_SYMBOL_GPL(vs_session_quota_available);
+
+/**
+ * vs_session_handle_notify - process an incoming notification from a transport
+ * @session: the session that is receiving the notification
+ * @flags: notification flags
+ * @service_id: the id of the service that the notification was addressed to
+ *
+ * This function may be called from interrupt context from the transport driver,
+ * and as such, it may not sleep.
+ */
+void vs_session_handle_notify(struct vs_session_device *session,
+ unsigned long bits, vs_service_id_t service_id)
+{
+ struct vs_service_device *service;
+ struct vs_service_driver *driver;
+ unsigned long flags;
+
+ service = vs_session_get_service(session, service_id);
+ if (!service) {
+ /* Ignore the notification since the service id doesn't exist */
+ dev_err(&session->dev, "notification for unknown service %d\n",
+ service_id);
+ return;
+ }
+
+ /*
+ * Take the rx lock before checking service readiness. This guarantees
+ * that if __reset_service() has just made the service inactive, we
+ * either see it and don't send the notification, or else send it
+ * before cancel_pending_rx() runs (and thus before the driver is
+ * deactivated).
+ */
+ spin_lock_irqsave(&service->rx_lock, flags);
+
+ /* If the service is not active, drop the notification. */
+ if (service->readiness != VS_SERVICE_ACTIVE) {
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+ vs_put_service(service);
+ return;
+ }
+
+ /* There should be a driver bound on the service */
+ if (WARN_ON(!service->dev.driver)) {
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+ vs_put_service(service);
+ return;
+ }
+
+ driver = to_vs_service_driver(service->dev.driver);
+ /* Call the driver's notify function */
+ driver->notify(service, bits);
+
+ spin_unlock_irqrestore(&service->rx_lock, flags);
+ vs_put_service(service);
+}
+EXPORT_SYMBOL_GPL(vs_session_handle_notify);
+
+static unsigned long reset_cool_off(struct vs_service_device *service)
+{
+ return service->reset_delay * RESET_THROTTLE_COOL_OFF_MULT;
+}
+
+static bool ready_needs_delay(struct vs_service_device *service)
+{
+ /*
+ * We throttle resets if too little time elapsed between the service
+ * last becoming ready, and the service last starting a reset.
+ *
+ * We do not use the current time here because it includes the time
+ * taken by the local service driver to actually process the reset.
+ */
+ return service->last_reset && service->last_ready && time_before(
+ service->last_reset,
+ service->last_ready + RESET_THROTTLE_TIME);
+}
+
+static bool reset_throttle_cooled_off(struct vs_service_device *service)
+{
+ /*
+ * Reset throttling cools off if enough time has elapsed since the
+ * last reset request.
+ *
+ * We check against the last requested reset, not the last serviced
+ * reset or ready. If we are throttling, a reset may not have been
+ * serviced for some time even though we are still receiving requests.
+ */
+ return service->reset_delay && service->last_reset_request &&
+ time_after(jiffies, service->last_reset_request +
+ reset_cool_off(service));
+}
+
+/*
+ * Queue up the ready work for a service. If a service is resetting too fast
+ * then it will be throttled using an exponentially increasing delay before
+ * marking it ready. If the reset speed backs off then the ready throttling
+ * will be cleared. If a service reaches the maximum throttling delay then all
+ * resets will be ignored until the cool off period has elapsed.
+ *
+ * The basic logic of the reset throttling is:
+ *
+ * - If a reset request is processed and the last ready was less than
+ * RESET_THROTTLE_TIME ago, then the ready needs to be delayed to
+ * throttle resets.
+ *
+ * - The ready delay increases exponentially on each throttled reset
+ * between RESET_THROTTLE_MIN and RESET_THROTTLE_MAX.
+ *
+ * - If RESET_THROTTLE_MAX is reached then no ready will be sent until the
+ * reset requests have cooled off.
+ *
+ * - Reset requests have cooled off when no reset requests have been
+ * received for RESET_THROTTLE_COOL_OFF_MULT * the service's current
+ * ready delay. The service's reset throttling is disabled.
+ *
+ * Note: Be careful when adding print statements, including debugging, to
+ * this function. The ready throttling is intended to prevent DOSing of the
+ * vServices due to repeated resets (e.g. because of a persistent failure).
+ * Adding a printk on each reset for example would reset in syslog spamming
+ * which is a DOS attack in itself.
+ *
+ * The ready lock must be held by the caller.
+ */
+static void queue_ready_work(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ unsigned long delay;
+ bool wait_for_cooloff = false;
+
+ lockdep_assert_held(&service->ready_lock);
+
+ /* This should only be called when the service enters reset. */
+ WARN_ON(service->readiness != VS_SERVICE_RESET);
+
+ if (ready_needs_delay(service)) {
+ /* Reset delay increments exponentially */
+ if (!service->reset_delay) {
+ service->reset_delay = RESET_THROTTLE_MIN;
+ } else if (service->reset_delay < RESET_THROTTLE_MAX) {
+ service->reset_delay *= 2;
+ } else {
+ wait_for_cooloff = true;
+ }
+
+ delay = service->reset_delay;
+ } else {
+ /* The reset request appears to have been be sane. */
+ delay = 0;
+
+ }
+
+ if (service->reset_delay > 0) {
+ /*
+ * Schedule cooloff work, to set the reset_delay to 0 if
+ * the reset requests stop for long enough.
+ */
+ schedule_delayed_work(&service->cooloff_work,
+ reset_cool_off(service));
+ }
+
+ if (wait_for_cooloff) {
+ /*
+ * We need to finish cooling off before we service resets
+ * again. Schedule cooloff_work to run after the current
+ * cooloff period ends; it may reschedule itself even later
+ * if any more requests arrive.
+ */
+ dev_err(&session->dev,
+ "Service %s is resetting too fast - must cool off for %u ms\n",
+ dev_name(&service->dev),
+ jiffies_to_msecs(reset_cool_off(service)));
+ return;
+ }
+
+ if (delay)
+ dev_err(&session->dev,
+ "Service %s is resetting too fast - delaying ready by %u ms\n",
+ dev_name(&service->dev),
+ jiffies_to_msecs(delay));
+
+ vs_debug(VS_DEBUG_SESSION, session,
+ "Service %s will become ready in %u ms\n",
+ dev_name(&service->dev),
+ jiffies_to_msecs(delay));
+
+ if (service->last_ready)
+ vs_debug(VS_DEBUG_SESSION, session,
+ "Last became ready %u ms ago\n",
+ msecs_ago(service->last_ready));
+ if (service->reset_delay >= RESET_THROTTLE_MAX)
+ dev_err(&session->dev, "Service %s hit max reset throttle\n",
+ dev_name(&service->dev));
+
+ schedule_delayed_work(&service->ready_work, delay);
+}
+
+static void session_activation_work(struct work_struct *work)
+{
+ struct vs_session_device *session = container_of(work,
+ struct vs_session_device, activation_work);
+ struct vs_service_device *core_service = session->core_service;
+ struct vs_session_driver *session_drv =
+ to_vs_session_driver(session->dev.driver);
+ int activation_state;
+ int ret;
+
+ if (WARN_ON(!core_service))
+ return;
+
+ if (WARN_ON(!session_drv))
+ return;
+
+ /*
+ * We use an atomic to prevent duplicate activations if we race with
+ * an activate after a reset. This is very unlikely, but possible if
+ * this work item is preempted.
+ */
+ activation_state = atomic_cmpxchg(&session->activation_state,
+ VS_SESSION_ACTIVATE, VS_SESSION_ACTIVE);
+
+ switch (activation_state) {
+ case VS_SESSION_ACTIVATE:
+ vs_debug(VS_DEBUG_SESSION, session,
+ "core service will be activated\n");
+ vs_service_enable(core_service);
+ break;
+
+ case VS_SESSION_RESET:
+ vs_debug(VS_DEBUG_SESSION, session,
+ "core service will be deactivated\n");
+
+ /* Handle the core service reset */
+ ret = service_handle_reset(session, core_service, true);
+
+ /* Tell the transport if the reset succeeded */
+ if (ret >= 0)
+ session->transport->vt->ready(session->transport);
+ else
+ dev_err(&session->dev, "core service reset unhandled: %d\n",
+ ret);
+
+ break;
+
+ default:
+ vs_debug(VS_DEBUG_SESSION, session,
+ "core service already active\n");
+ break;
+ }
+}
+
+/**
+ * vs_session_handle_reset - Handle a reset at the session layer.
+ * @session: Session to reset
+ *
+ * This function is called by the transport when it receives a transport-level
+ * reset notification.
+ *
+ * After a session is reset by calling this function, it will reset all of its
+ * attached services, and then call the transport's ready callback. The
+ * services will remain in reset until the session is re-activated by a call
+ * to vs_session_handle_activate().
+ *
+ * Calling this function on a session that is already reset is permitted, as
+ * long as the transport accepts the consequent duplicate ready callbacks.
+ *
+ * A newly created session is initially in the reset state, and will not call
+ * the transport's ready callback. The transport may choose to either act as
+ * if the ready callback had been called, or call this function again to
+ * trigger a new ready callback.
+ */
+void vs_session_handle_reset(struct vs_session_device *session)
+{
+ atomic_set(&session->activation_state, VS_SESSION_RESET);
+
+ schedule_work(&session->activation_work);
+}
+EXPORT_SYMBOL_GPL(vs_session_handle_reset);
+
+/**
+ * vs_session_handle_activate - Allow a session to leave the reset state.
+ * @session: Session to mark active.
+ *
+ * This function is called by the transport when a transport-level reset is
+ * completed; that is, after the session layer has reset its services and
+ * called the ready callback, at *both* ends of the connection.
+ */
+void vs_session_handle_activate(struct vs_session_device *session)
+{
+ atomic_set(&session->activation_state, VS_SESSION_ACTIVATE);
+
+ schedule_work(&session->activation_work);
+}
+EXPORT_SYMBOL_GPL(vs_session_handle_activate);
+
+static ssize_t id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", session->session_num);
+}
+
+/*
+ * The vServices session device type
+ */
+static ssize_t is_server_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", session->is_server);
+}
+
+static ssize_t name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", session->name);
+}
+
+#ifdef CONFIG_VSERVICES_DEBUG
+static ssize_t debug_mask_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%.8lx\n", session->debug_mask);
+}
+
+static ssize_t debug_mask_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+ int err;
+
+ err = kstrtoul(buf, 0, &session->debug_mask);
+ if (err)
+ return err;
+
+ /* Clear any bits we don't know about */
+ session->debug_mask &= VS_DEBUG_ALL;
+
+ return count;
+}
+#endif /* CONFIG_VSERVICES_DEBUG */
+
+static struct device_attribute vservices_session_dev_attrs[] = {
+ __ATTR_RO(id),
+ __ATTR_RO(is_server),
+ __ATTR_RO(name),
+#ifdef CONFIG_VSERVICES_DEBUG
+ __ATTR(debug_mask, S_IRUGO | S_IWUSR,
+ debug_mask_show, debug_mask_store),
+#endif
+ __ATTR_NULL,
+};
+
+static int vs_session_free_idr(struct vs_session_device *session)
+{
+ mutex_lock(&vs_session_lock);
+ idr_remove(&session_idr, session->session_num);
+ mutex_unlock(&vs_session_lock);
+ return 0;
+}
+
+static void vs_session_device_release(struct device *dev)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ vs_session_free_idr(session);
+
+ kfree(session->name);
+ kfree(session);
+}
+
+/*
+ * The vServices session bus
+ */
+static int vs_session_bus_match(struct device *dev,
+ struct device_driver *driver)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+ struct vs_session_driver *session_drv = to_vs_session_driver(driver);
+
+ return (session->is_server == session_drv->is_server);
+}
+
+static int vs_session_bus_remove(struct device *dev)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+ struct vs_service_device *core_service = session->core_service;
+
+ if (!core_service)
+ return 0;
+
+ /*
+ * Abort any pending session activation. We rely on the transport to
+ * not call vs_session_handle_activate after this point.
+ */
+ cancel_work_sync(&session->activation_work);
+
+ /* Abort any pending fatal error handling, which is redundant now. */
+ cancel_work_sync(&session->fatal_error_work);
+
+ /*
+ * Delete the core service. This will implicitly delete everything
+ * else (in reset on the client side, and in release on the server
+ * side). The session holds a reference, so this won't release the
+ * service struct.
+ */
+ delete_service(core_service);
+
+ /* Now clean up the core service. */
+ session->core_service = NULL;
+
+ /* Matches the get in vs_service_register() */
+ vs_put_service(core_service);
+
+ return 0;
+}
+
+static int vservices_session_uevent(struct device *dev,
+ struct kobj_uevent_env *env)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ dev_dbg(dev, "uevent\n");
+
+ if (add_uevent_var(env, "IS_SERVER=%d", session->is_server))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "SESSION_ID=%d", session->session_num))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void vservices_session_shutdown(struct device *dev)
+{
+ struct vs_session_device *session = to_vs_session_device(dev);
+
+ dev_dbg(dev, "shutdown\n");
+
+ /* Do a transport reset */
+ session->transport->vt->reset(session->transport);
+}
+
+struct bus_type vs_session_bus_type = {
+ .name = "vservices-session",
+ .match = vs_session_bus_match,
+ .remove = vs_session_bus_remove,
+ .dev_attrs = vservices_session_dev_attrs,
+ .uevent = vservices_session_uevent,
+ .shutdown = vservices_session_shutdown,
+};
+EXPORT_SYMBOL_GPL(vs_session_bus_type);
+
+/*
+ * Common code for the vServices client and server buses
+ */
+int vs_service_bus_probe(struct device *dev)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_service_driver *vsdrv = to_vs_service_driver(dev->driver);
+ struct vs_session_device *session = vs_service_get_session(service);
+ int ret;
+
+ vs_dev_debug(VS_DEBUG_SESSION, session, &service->dev, "probe\n");
+
+ /*
+ * Increase the reference count on the service driver. We don't allow
+ * service driver modules to be removed if there are any device
+ * instances present. The devices must be explicitly removed first.
+ */
+ if (!try_module_get(vsdrv->driver.owner))
+ return -ENODEV;
+
+ ret = vsdrv->probe(service);
+ if (ret) {
+ module_put(vsdrv->driver.owner);
+ return ret;
+ }
+
+ service->driver_probed = true;
+
+ try_start_service(service);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_service_bus_probe);
+
+int vs_service_bus_remove(struct device *dev)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_service_driver *vsdrv = to_vs_service_driver(dev->driver);
+ int err = 0;
+
+ reset_service(service);
+
+ /* Prevent reactivation of the driver */
+ service->driver_probed = false;
+
+ /* The driver has now had its reset() callback called; remove it */
+ vsdrv->remove(service);
+
+ /*
+ * Take the service's state mutex and spinlock. This ensures that any
+ * thread that is calling vs_state_lock_safe[_bh] will either complete
+ * now, or see the driver removal and fail, irrespective of which type
+ * of lock it is using.
+ */
+ mutex_lock_nested(&service->state_mutex, service->lock_subclass);
+ spin_lock_bh(&service->state_spinlock);
+
+ /* Release all the locks. */
+ spin_unlock_bh(&service->state_spinlock);
+ mutex_unlock(&service->state_mutex);
+ mutex_unlock(&service->ready_lock);
+
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ service->state_spinlock_used = false;
+ service->state_mutex_used = false;
+#endif
+
+ module_put(vsdrv->driver.owner);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(vs_service_bus_remove);
+
+int vs_service_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct vs_service_device *service = to_vs_service_device(dev);
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ dev_dbg(dev, "uevent\n");
+
+ if (add_uevent_var(env, "IS_SERVER=%d", service->is_server))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "SERVICE_ID=%d", service->id))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "SESSION_ID=%d", session->session_num))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "SERVICE_NAME=%s", service->name))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "PROTOCOL=%s", service->protocol ?: ""))
+ return -ENOMEM;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_service_bus_uevent);
+
+static int vs_session_create_sysfs_entry(struct vs_transport *transport,
+ struct vs_session_device *session, bool server,
+ const char *transport_name)
+{
+ char *sysfs_name;
+ struct kobject *sysfs_parent = vservices_client_root;
+
+ if (!transport_name)
+ return -EINVAL;
+
+ sysfs_name = kasprintf(GFP_KERNEL, "%s:%s", transport->type,
+ transport_name);
+ if (!sysfs_name)
+ return -ENOMEM;
+
+ if (server)
+ sysfs_parent = vservices_server_root;
+
+ session->sysfs_entry = kobject_create_and_add(sysfs_name, sysfs_parent);
+
+ kfree(sysfs_name);
+ if (!session->sysfs_entry)
+ return -ENOMEM;
+ return 0;
+}
+
+static int vs_session_alloc_idr(struct vs_session_device *session)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 9, 0)
+ int err, id;
+
+retry:
+ if (!idr_pre_get(&session_idr, GFP_KERNEL))
+ return -ENOMEM;
+
+ mutex_lock(&vs_session_lock);
+ err = idr_get_new_above(&session_idr, session, 0, &id);
+ if (err == 0) {
+ if (id >= VS_MAX_SESSIONS) {
+ /* We are out of session ids */
+ idr_remove(&session_idr, id);
+ mutex_unlock(&vs_session_lock);
+ return -EBUSY;
+ }
+ }
+ mutex_unlock(&vs_session_lock);
+ if (err == -EAGAIN)
+ goto retry;
+ if (err < 0)
+ return err;
+#else
+ int id;
+
+ mutex_lock(&vs_session_lock);
+ id = idr_alloc(&session_idr, session, 0, VS_MAX_SESSIONS, GFP_KERNEL);
+ mutex_unlock(&vs_session_lock);
+
+ if (id == -ENOSPC)
+ return -EBUSY;
+ else if (id < 0)
+ return id;
+#endif
+
+ session->session_num = id;
+ return 0;
+}
+
+/**
+ * vs_session_register - register a vservices session on a transport
+ * @transport: vservices transport that the session will attach to
+ * @parent: device that implements the transport (for sysfs)
+ * @server: true if the session is server-side
+ * @transport_name: name of the transport
+ *
+ * This function is intended to be called from the probe() function of a
+ * transport driver. It sets up a new session device, which then either
+ * performs automatic service discovery (for clients) or creates sysfs nodes
+ * that allow the user to create services (for servers).
+ *
+ * Note that the parent is only used by the driver framework; it is not
+ * directly accessed by the session drivers. Thus, a single transport device
+ * can support multiple sessions, as long as they each have a unique struct
+ * vs_transport.
+ *
+ * Note: This function may sleep, and therefore must not be called from
+ * interrupt context.
+ *
+ * Returns a pointer to the new device, or an error pointer.
+ */
+struct vs_session_device *vs_session_register(struct vs_transport *transport,
+ struct device *parent, bool server, const char *transport_name)
+{
+ struct device *dev;
+ struct vs_session_device *session;
+ int ret = -ENOMEM;
+
+ WARN_ON(!transport);
+
+ session = kzalloc(sizeof(*session), GFP_KERNEL);
+ if (!session)
+ goto fail_session_alloc;
+
+ session->transport = transport;
+ session->is_server = server;
+ session->name = kstrdup(transport_name, GFP_KERNEL);
+ if (!session->name)
+ goto fail_free_session;
+
+ INIT_WORK(&session->activation_work, session_activation_work);
+ INIT_WORK(&session->fatal_error_work, session_fatal_error_work);
+
+#ifdef CONFIG_VSERVICES_DEBUG
+ session->debug_mask = default_debug_mask & VS_DEBUG_ALL;
+#endif
+
+ idr_init(&session->service_idr);
+ mutex_init(&session->service_idr_lock);
+
+ /*
+ * We must create session sysfs entry before device_create
+ * so, that sysfs entry is available while registering
+ * core service.
+ */
+ ret = vs_session_create_sysfs_entry(transport, session, server,
+ transport_name);
+ if (ret)
+ goto fail_free_session;
+
+ ret = vs_session_alloc_idr(session);
+ if (ret)
+ goto fail_sysfs_entry;
+
+ dev = &session->dev;
+ dev->parent = parent;
+ dev->bus = &vs_session_bus_type;
+ dev->release = vs_session_device_release;
+ dev_set_name(dev, "vservice:%d", session->session_num);
+
+ ret = device_register(dev);
+ if (ret) {
+ goto fail_session_map;
+ }
+
+ /* Add a symlink to transport device inside session device sysfs dir */
+ if (parent) {
+ ret = sysfs_create_link(&session->dev.kobj,
+ &parent->kobj, VS_TRANSPORT_SYMLINK_NAME);
+ if (ret) {
+ dev_err(&session->dev,
+ "Error %d creating transport symlink\n",
+ ret);
+ goto fail_session_device_unregister;
+ }
+ }
+
+ return session;
+
+fail_session_device_unregister:
+ device_unregister(&session->dev);
+ kobject_put(session->sysfs_entry);
+ /* Remaining cleanup will be done in vs_session_release */
+ return ERR_PTR(ret);
+fail_session_map:
+ vs_session_free_idr(session);
+fail_sysfs_entry:
+ kobject_put(session->sysfs_entry);
+fail_free_session:
+ kfree(session->name);
+ kfree(session);
+fail_session_alloc:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(vs_session_register);
+
+void vs_session_start(struct vs_session_device *session)
+{
+ struct vs_service_device *core_service = session->core_service;
+
+ if (WARN_ON(!core_service))
+ return;
+
+ blocking_notifier_call_chain(&vs_session_notifier_list,
+ VS_SESSION_NOTIFY_ADD, session);
+
+ vs_service_start(core_service);
+}
+EXPORT_SYMBOL_GPL(vs_session_start);
+
+/**
+ * vs_session_unregister - unregister a session device
+ * @session: the session device to unregister
+ */
+void vs_session_unregister(struct vs_session_device *session)
+{
+ if (session->dev.parent)
+ sysfs_remove_link(&session->dev.kobj, VS_TRANSPORT_SYMLINK_NAME);
+ blocking_notifier_call_chain(&vs_session_notifier_list,
+ VS_SESSION_NOTIFY_REMOVE, session);
+
+ device_unregister(&session->dev);
+
+ kobject_put(session->sysfs_entry);
+}
+EXPORT_SYMBOL_GPL(vs_session_unregister);
+
+struct service_unbind_work_struct {
+ struct vs_service_device *service;
+ struct work_struct work;
+};
+
+static void service_unbind_work(struct work_struct *work)
+{
+ struct service_unbind_work_struct *unbind_work = container_of(work,
+ struct service_unbind_work_struct, work);
+
+ device_release_driver(&unbind_work->service->dev);
+
+ /* Matches vs_get_service() in vs_session_unbind_driver() */
+ vs_put_service(unbind_work->service);
+ kfree(unbind_work);
+}
+
+int vs_session_unbind_driver(struct vs_service_device *service)
+{
+ struct service_unbind_work_struct *unbind_work =
+ kmalloc(sizeof(*unbind_work), GFP_KERNEL);
+
+ if (!unbind_work)
+ return -ENOMEM;
+
+ INIT_WORK(&unbind_work->work, service_unbind_work);
+
+ /* Put in service_unbind_work() */
+ unbind_work->service = vs_get_service(service);
+ schedule_work(&unbind_work->work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vs_session_unbind_driver);
+
+static int __init vservices_init(void)
+{
+ int r;
+
+ printk(KERN_INFO "vServices Framework 1.0\n");
+
+ vservices_root = kobject_create_and_add("vservices", NULL);
+ if (!vservices_root) {
+ r = -ENOMEM;
+ goto fail_create_root;
+ }
+
+ r = bus_register(&vs_session_bus_type);
+ if (r < 0)
+ goto fail_bus_register;
+
+ r = vs_devio_init();
+ if (r < 0)
+ goto fail_devio_init;
+
+ return 0;
+
+fail_devio_init:
+ bus_unregister(&vs_session_bus_type);
+fail_bus_register:
+ kobject_put(vservices_root);
+fail_create_root:
+ return r;
+}
+
+static void __exit vservices_exit(void)
+{
+ printk(KERN_INFO "vServices Framework exit\n");
+
+ vs_devio_exit();
+ bus_unregister(&vs_session_bus_type);
+ kobject_put(vservices_root);
+}
+
+subsys_initcall(vservices_init);
+module_exit(vservices_exit);
+
+MODULE_DESCRIPTION("OKL4 Virtual Services Session");
+MODULE_AUTHOR("Open Kernel Labs, Inc");
diff --git a/drivers/vservices/session.h b/drivers/vservices/session.h
new file mode 100644
index 0000000..f51d535
--- /dev/null
+++ b/drivers/vservices/session.h
@@ -0,0 +1,173 @@
+/*
+ * drivers/vservices/session.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * Definitions related to the vservices session bus and its client and server
+ * session drivers. The interfaces in this file are implementation details of
+ * the vServices framework and should not be used by transport or service
+ * drivers.
+ */
+
+#ifndef _VSERVICES_SESSION_PRIV_H_
+#define _VSERVICES_SESSION_PRIV_H_
+
+/* Maximum number of sessions allowed */
+#define VS_MAX_SESSIONS 64
+
+#include "debug.h"
+
+/* For use by the core server */
+#define VS_SERVICE_AUTO_ALLOCATE_ID 0xffff
+#define VS_SERVICE_ALREADY_RESET 1
+
+/*
+ * The upper bits of the service id are reserved for transport driver specific
+ * use. The reserve bits are always zeroed out above the transport layer.
+ */
+#define VS_SERVICE_ID_TRANSPORT_BITS 4
+#define VS_SERVICE_ID_TRANSPORT_OFFSET 12
+#define VS_SERVICE_ID_TRANSPORT_MASK ((1 << VS_SERVICE_ID_TRANSPORT_BITS) - 1)
+#define VS_SERVICE_ID_MASK \
+ (~(VS_SERVICE_ID_TRANSPORT_MASK << VS_SERVICE_ID_TRANSPORT_OFFSET))
+
+/* Number of bits needed to represent the service id range as a bitmap. */
+#define VS_SERVICE_ID_BITMAP_BITS \
+ (1 << ((sizeof(vs_service_id_t) * 8) - VS_SERVICE_ID_TRANSPORT_BITS))
+
+/* High service ids are reserved for use by the transport drivers */
+#define VS_SERVICE_ID_RESERVED(x) \
+ ((1 << VS_SERVICE_ID_TRANSPORT_OFFSET) - (x))
+
+#define VS_SERVICE_ID_RESERVED_1 VS_SERVICE_ID_RESERVED(1)
+
+/* Name of the session device symlink in service device sysfs directory */
+#define VS_SESSION_SYMLINK_NAME "session"
+
+/* Name of the transport device symlink in session device sysfs directory */
+#define VS_TRANSPORT_SYMLINK_NAME "transport"
+
+static inline unsigned int
+vs_get_service_id_reserved_bits(vs_service_id_t service_id)
+{
+ return (service_id >> VS_SERVICE_ID_TRANSPORT_OFFSET) &
+ VS_SERVICE_ID_TRANSPORT_MASK;
+}
+
+static inline vs_service_id_t vs_get_real_service_id(vs_service_id_t service_id)
+{
+ return service_id & VS_SERVICE_ID_MASK;
+}
+
+static inline void vs_set_service_id_reserved_bits(vs_service_id_t *service_id,
+ unsigned int reserved_bits)
+{
+ *service_id &= ~(VS_SERVICE_ID_TRANSPORT_MASK <<
+ VS_SERVICE_ID_TRANSPORT_OFFSET);
+ *service_id |= (reserved_bits & VS_SERVICE_ID_TRANSPORT_MASK) <<
+ VS_SERVICE_ID_TRANSPORT_OFFSET;
+}
+
+extern struct bus_type vs_session_bus_type;
+extern struct kobject *vservices_root;
+extern struct kobject *vservices_server_root;
+extern struct kobject *vservices_client_root;
+
+/**
+ * struct vs_session_driver - Session driver
+ * @driver: Linux device model driver structure
+ * @service_bus: Pointer to either the server or client bus type
+ * @is_server: True if this driver is for a server session, false if it is for
+ * a client session
+ * @service_added: Called when a non-core service is added.
+ * @service_start: Called when a non-core service is started.
+ * @service_local_reset: Called when an active non-core service driver becomes
+ * inactive.
+ * @service_removed: Called when a non-core service is removed.
+ */
+struct vs_session_driver {
+ struct device_driver driver;
+ struct bus_type *service_bus;
+ bool is_server;
+
+ /* These are all called with the core service state lock held. */
+ int (*service_added)(struct vs_session_device *session,
+ struct vs_service_device *service);
+ int (*service_start)(struct vs_session_device *session,
+ struct vs_service_device *service);
+ int (*service_local_reset)(struct vs_session_device *session,
+ struct vs_service_device *service);
+ int (*service_removed)(struct vs_session_device *session,
+ struct vs_service_device *service);
+};
+
+#define to_vs_session_driver(drv) \
+ container_of(drv, struct vs_session_driver, driver)
+
+/* Service lookup */
+extern struct vs_service_device * vs_session_get_service(
+ struct vs_session_device *session,
+ vs_service_id_t service_id);
+
+/* Service creation & destruction */
+extern struct vs_service_device *
+vs_service_register(struct vs_session_device *session,
+ struct vs_service_device *parent,
+ vs_service_id_t service_id,
+ const char *protocol,
+ const char *name,
+ const void *plat_data);
+
+extern bool vs_service_start(struct vs_service_device *service);
+
+extern int vs_service_delete(struct vs_service_device *service,
+ struct vs_service_device *caller);
+
+extern int vs_service_handle_delete(struct vs_service_device *service);
+
+/* Service reset handling */
+extern int vs_service_handle_reset(struct vs_session_device *session,
+ vs_service_id_t service_id, bool disable);
+extern int vs_service_enable(struct vs_service_device *service);
+
+extern void vs_session_enable_noncore(struct vs_session_device *session);
+extern void vs_session_disable_noncore(struct vs_session_device *session);
+extern void vs_session_delete_noncore(struct vs_session_device *session);
+
+/* Service bus driver management */
+extern int vs_service_bus_probe(struct device *dev);
+extern int vs_service_bus_remove(struct device *dev);
+extern int vs_service_bus_uevent(struct device *dev,
+ struct kobj_uevent_env *env);
+
+#ifdef CONFIG_VSERVICES_CHAR_DEV
+
+extern int vs_devio_init(void);
+extern void vs_devio_exit(void);
+
+extern struct vs_service_device *vs_service_lookup_by_devt(dev_t dev);
+
+extern struct vs_service_driver vs_devio_server_driver;
+extern struct vs_service_driver vs_devio_client_driver;
+
+extern int vservices_cdev_major;
+
+#else /* !CONFIG_VSERVICES_CHAR_DEV */
+
+static inline int vs_devio_init(void)
+{
+ return 0;
+}
+
+static inline void vs_devio_exit(void)
+{
+}
+
+#endif /* !CONFIG_VSERVICES_CHAR_DEV */
+
+#endif /* _VSERVICES_SESSION_PRIV_H_ */
diff --git a/drivers/vservices/skeleton_driver.c b/drivers/vservices/skeleton_driver.c
new file mode 100644
index 0000000..cfbc5df
--- /dev/null
+++ b/drivers/vservices/skeleton_driver.c
@@ -0,0 +1,133 @@
+/*
+ * drivers/vservices/skeleton_driver.c
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * Skeleton testing driver for templating vService client/server drivers
+ */
+
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include <vservices/session.h>
+#include <vservices/buffer.h>
+#include <vservices/service.h>
+
+struct skeleton_info {
+ unsigned dummy;
+};
+
+static void vs_skeleton_handle_start(struct vs_service_device *service)
+{
+ /* NOTE: Do not change this message - is it used for system testing */
+ dev_info(&service->dev, "skeleton handle_start\n");
+}
+
+static int vs_skeleton_handle_message(struct vs_service_device *service,
+ struct vs_mbuf *mbuf)
+{
+ dev_info(&service->dev, "skeleton handle_messasge\n");
+ return -EBADMSG;
+}
+
+static void vs_skeleton_handle_notify(struct vs_service_device *service,
+ u32 flags)
+{
+ dev_info(&service->dev, "skeleton handle_notify\n");
+}
+
+static void vs_skeleton_handle_reset(struct vs_service_device *service)
+{
+ dev_info(&service->dev, "skeleton handle_reset %s service %d\n",
+ service->is_server ? "server" : "client", service->id);
+}
+
+static int vs_skeleton_probe(struct vs_service_device *service)
+{
+ struct skeleton_info *info;
+ int err = -ENOMEM;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ goto fail;
+
+ dev_set_drvdata(&service->dev, info);
+ return 0;
+
+fail:
+ return err;
+}
+
+static int vs_skeleton_remove(struct vs_service_device *service)
+{
+ struct skeleton_info *info = dev_get_drvdata(&service->dev);
+
+ dev_info(&service->dev, "skeleton remove\n");
+ kfree(info);
+ return 0;
+}
+
+static struct vs_service_driver server_skeleton_driver = {
+ .protocol = "com.ok-labs.skeleton",
+ .is_server = true,
+ .probe = vs_skeleton_probe,
+ .remove = vs_skeleton_remove,
+ .start = vs_skeleton_handle_start,
+ .receive = vs_skeleton_handle_message,
+ .notify = vs_skeleton_handle_notify,
+ .reset = vs_skeleton_handle_reset,
+ .driver = {
+ .name = "vs-server-skeleton",
+ .owner = THIS_MODULE,
+ .bus = &vs_server_bus_type,
+ },
+};
+
+static struct vs_service_driver client_skeleton_driver = {
+ .protocol = "com.ok-labs.skeleton",
+ .is_server = false,
+ .probe = vs_skeleton_probe,
+ .remove = vs_skeleton_remove,
+ .start = vs_skeleton_handle_start,
+ .receive = vs_skeleton_handle_message,
+ .notify = vs_skeleton_handle_notify,
+ .reset = vs_skeleton_handle_reset,
+ .driver = {
+ .name = "vs-client-skeleton",
+ .owner = THIS_MODULE,
+ .bus = &vs_client_bus_type,
+ },
+};
+
+static int __init vs_skeleton_init(void)
+{
+ int ret;
+
+ ret = driver_register(&server_skeleton_driver.driver);
+ if (ret)
+ return ret;
+
+ ret = driver_register(&client_skeleton_driver.driver);
+ if (ret)
+ driver_unregister(&server_skeleton_driver.driver);
+
+ return ret;
+}
+
+static void __exit vs_skeleton_exit(void)
+{
+ driver_unregister(&server_skeleton_driver.driver);
+ driver_unregister(&client_skeleton_driver.driver);
+}
+
+module_init(vs_skeleton_init);
+module_exit(vs_skeleton_exit);
+
+MODULE_DESCRIPTION("OKL4 Virtual Services Skeleton Client/Server Driver");
+MODULE_AUTHOR("Open Kernel Labs, Inc");
diff --git a/drivers/vservices/transport.h b/drivers/vservices/transport.h
new file mode 100644
index 0000000..8e5055c
--- /dev/null
+++ b/drivers/vservices/transport.h
@@ -0,0 +1,40 @@
+/*
+ * include/vservices/transport.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This file defines the private interface that vServices transport drivers
+ * must provide to the vservices session and protocol layers. The transport,
+ * transport vtable, and message buffer structures are defined in the public
+ * <vservices/transport.h> header.
+ */
+
+#ifndef _VSERVICES_TRANSPORT_PRIV_H_
+#define _VSERVICES_TRANSPORT_PRIV_H_
+
+#include <linux/types.h>
+#include <linux/list.h>
+
+#include <vservices/transport.h>
+#include <vservices/types.h>
+#include <vservices/buffer.h>
+
+/**
+ * struct vs_notify_info - Notification information stored in the transport
+ * @service_id: Service id for this notification info
+ * @offset: Offset into the notification mapping
+ */
+struct vs_notify_info {
+ vs_service_id_t service_id;
+ unsigned offset;
+};
+
+#define VS_MAX_SERVICES 128
+#define VS_MAX_SERVICE_ID (VS_MAX_SERVICES - 1)
+
+#endif /* _VSERVICES_TRANSPORT_PRIV_H_ */
diff --git a/drivers/vservices/transport/Kconfig b/drivers/vservices/transport/Kconfig
new file mode 100644
index 0000000..cd1c97c
--- /dev/null
+++ b/drivers/vservices/transport/Kconfig
@@ -0,0 +1,7 @@
+#
+# vServices Transport driver configuration
+#
+
+menu "Transport drivers"
+
+endmenu
diff --git a/drivers/vservices/transport/Makefile b/drivers/vservices/transport/Makefile
new file mode 100644
index 0000000..ae1c943
--- /dev/null
+++ b/drivers/vservices/transport/Makefile
@@ -0,0 +1,2 @@
+ccflags-y += -Werror
+ccflags-$(CONFIG_VSERVICES_DEBUG) += -DDEBUG
diff --git a/drivers/xen/cpu_hotplug.c b/drivers/xen/cpu_hotplug.c
index 5676aef..f4e59c4 100644
--- a/drivers/xen/cpu_hotplug.c
+++ b/drivers/xen/cpu_hotplug.c
@@ -18,15 +18,16 @@
static void disable_hotplug_cpu(int cpu)
{
- if (cpu_online(cpu)) {
- lock_device_hotplug();
+ if (!cpu_is_hotpluggable(cpu))
+ return;
+ lock_device_hotplug();
+ if (cpu_online(cpu))
device_offline(get_cpu_device(cpu));
- unlock_device_hotplug();
- }
- if (cpu_present(cpu))
+ if (!cpu_online(cpu) && cpu_present(cpu)) {
xen_arch_unregister_cpu(cpu);
-
- set_cpu_present(cpu, false);
+ set_cpu_present(cpu, false);
+ }
+ unlock_device_hotplug();
}
static int vcpu_online(unsigned int cpu)
diff --git a/drivers/xen/events/events_base.c b/drivers/xen/events/events_base.c
index 1435d8c..4b0cc9d 100644
--- a/drivers/xen/events/events_base.c
+++ b/drivers/xen/events/events_base.c
@@ -139,7 +139,7 @@
clear_evtchn_to_irq_row(row);
}
- evtchn_to_irq[EVTCHN_ROW(evtchn)][EVTCHN_COL(evtchn)] = irq;
+ evtchn_to_irq[row][col] = irq;
return 0;
}
diff --git a/drivers/xen/manage.c b/drivers/xen/manage.c
index 7abaaa5..abd49bc 100644
--- a/drivers/xen/manage.c
+++ b/drivers/xen/manage.c
@@ -282,9 +282,11 @@
/*
* The Xenstore watch fires directly after registering it and
* after a suspend/resume cycle. So ENOENT is no error but
- * might happen in those cases.
+ * might happen in those cases. ERANGE is observed when we get
+ * an empty value (''), this happens when we acknowledge the
+ * request by writing '\0' below.
*/
- if (err != -ENOENT)
+ if (err != -ENOENT && err != -ERANGE)
pr_err("Error %d reading sysrq code in control/sysrq\n",
err);
xenbus_transaction_end(xbt, 1);
diff --git a/fs/cifs/cifs_unicode.c b/fs/cifs/cifs_unicode.c
index a0b3e7d..211ac47 100644
--- a/fs/cifs/cifs_unicode.c
+++ b/fs/cifs/cifs_unicode.c
@@ -101,9 +101,6 @@
case SFM_LESSTHAN:
*target = '<';
break;
- case SFM_SLASH:
- *target = '\\';
- break;
case SFM_SPACE:
*target = ' ';
break;
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index 8407b07..741b83c 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -577,10 +577,15 @@
}
count = 0;
+ /*
+ * We know that all the name entries in the protocols array
+ * are short (< 16 bytes anyway) and are NUL terminated.
+ */
for (i = 0; i < CIFS_NUM_PROT; i++) {
- strncpy(pSMB->DialectsArray+count, protocols[i].name, 16);
- count += strlen(protocols[i].name) + 1;
- /* null at end of source and target buffers anyway */
+ size_t len = strlen(protocols[i].name) + 1;
+
+ memcpy(pSMB->DialectsArray+count, protocols[i].name, len);
+ count += len;
}
inc_rfc1001_len(pSMB, count);
pSMB->ByteCount = cpu_to_le16(count);
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index 323d8e3..50559a8 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -406,9 +406,17 @@
(struct smb_com_transaction_change_notify_rsp *)buf;
struct file_notify_information *pnotify;
__u32 data_offset = 0;
+ size_t len = srv->total_read - sizeof(pSMBr->hdr.smb_buf_length);
+
if (get_bcc(buf) > sizeof(struct file_notify_information)) {
data_offset = le32_to_cpu(pSMBr->DataOffset);
+ if (data_offset >
+ len - sizeof(struct file_notify_information)) {
+ cifs_dbg(FYI, "invalid data_offset %u\n",
+ data_offset);
+ return true;
+ }
pnotify = (struct file_notify_information *)
((char *)&pSMBr->hdr.Protocol + data_offset);
cifs_dbg(FYI, "dnotify on %s Action: 0x%x\n",
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c
index 68622f1..08c1c86 100644
--- a/fs/cifs/smb2ops.c
+++ b/fs/cifs/smb2ops.c
@@ -989,7 +989,7 @@
}
srch_inf->entries_in_buffer = 0;
- srch_inf->index_of_last_entry = 0;
+ srch_inf->index_of_last_entry = 2;
rc = SMB2_query_directory(xid, tcon, fid->persistent_fid,
fid->volatile_fid, 0, srch_inf);
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
index e8b3650..e16bc4c 100644
--- a/fs/ext4/dir.c
+++ b/fs/ext4/dir.c
@@ -74,7 +74,7 @@
else if (unlikely(rlen < EXT4_DIR_REC_LEN(de->name_len)))
error_msg = "rec_len is too small for name_len";
else if (unlikely(((char *) de - buf) + rlen > size))
- error_msg = "directory entry across range";
+ error_msg = "directory entry overrun";
else if (unlikely(le32_to_cpu(de->inode) >
le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count)))
error_msg = "inode out of bounds";
@@ -83,18 +83,16 @@
if (filp)
ext4_error_file(filp, function, line, bh->b_blocknr,
- "bad entry in directory: %s - offset=%u(%u), "
- "inode=%u, rec_len=%d, name_len=%d",
- error_msg, (unsigned) (offset % size),
- offset, le32_to_cpu(de->inode),
- rlen, de->name_len);
+ "bad entry in directory: %s - offset=%u, "
+ "inode=%u, rec_len=%d, name_len=%d, size=%d",
+ error_msg, offset, le32_to_cpu(de->inode),
+ rlen, de->name_len, size);
else
ext4_error_inode(dir, function, line, bh->b_blocknr,
- "bad entry in directory: %s - offset=%u(%u), "
- "inode=%u, rec_len=%d, name_len=%d",
- error_msg, (unsigned) (offset % size),
- offset, le32_to_cpu(de->inode),
- rlen, de->name_len);
+ "bad entry in directory: %s - offset=%u, "
+ "inode=%u, rec_len=%d, name_len=%d, size=%d",
+ error_msg, offset, le32_to_cpu(de->inode),
+ rlen, de->name_len, size);
return 1;
}
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 74e831f..f901b643 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -1768,6 +1768,7 @@
{
int err, inline_size;
struct ext4_iloc iloc;
+ size_t inline_len;
void *inline_pos;
unsigned int offset;
struct ext4_dir_entry_2 *de;
@@ -1795,8 +1796,9 @@
goto out;
}
+ inline_len = ext4_get_inline_size(dir);
offset = EXT4_INLINE_DOTDOT_SIZE;
- while (offset < dir->i_size) {
+ while (offset < inline_len) {
de = ext4_get_inline_entry(dir, &iloc, offset,
&inline_pos, &inline_size);
if (ext4_check_dir_entry(dir, NULL, de,
diff --git a/fs/ext4/mmp.c b/fs/ext4/mmp.c
index d89754e..c2e830a 100644
--- a/fs/ext4/mmp.c
+++ b/fs/ext4/mmp.c
@@ -48,7 +48,6 @@
*/
sb_start_write(sb);
ext4_mmp_csum_set(sb, mmp);
- mark_buffer_dirty(bh);
lock_buffer(bh);
bh->b_end_io = end_buffer_write_sync;
get_bh(bh);
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 09020e4..9bad755 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3434,6 +3434,12 @@
int credits;
u8 old_file_type;
+ if (new.inode && new.inode->i_nlink == 0) {
+ EXT4_ERROR_INODE(new.inode,
+ "target of rename is already freed");
+ return -EFSCORRUPTED;
+ }
+
if ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT)) &&
(!projid_eq(EXT4_I(new_dir)->i_projid,
EXT4_I(old_dentry->d_inode)->i_projid)))
diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c
index eb720d9..1da301e 100644
--- a/fs/ext4/resize.c
+++ b/fs/ext4/resize.c
@@ -18,6 +18,7 @@
int ext4_resize_begin(struct super_block *sb)
{
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
int ret = 0;
if (!capable(CAP_SYS_RESOURCE))
@@ -28,7 +29,7 @@
* because the user tools have no way of handling this. Probably a
* bad time to do it anyways.
*/
- if (EXT4_SB(sb)->s_sbh->b_blocknr !=
+ if (EXT4_B2C(sbi, sbi->s_sbh->b_blocknr) !=
le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block)) {
ext4_warning(sb, "won't resize using backup superblock at %llu",
(unsigned long long)EXT4_SB(sb)->s_sbh->b_blocknr);
@@ -1954,6 +1955,26 @@
}
}
+ /*
+ * Make sure the last group has enough space so that it's
+ * guaranteed to have enough space for all metadata blocks
+ * that it might need to hold. (We might not need to store
+ * the inode table blocks in the last block group, but there
+ * will be cases where this might be needed.)
+ */
+ if ((ext4_group_first_block_no(sb, n_group) +
+ ext4_group_overhead_blocks(sb, n_group) + 2 +
+ sbi->s_itb_per_group + sbi->s_cluster_ratio) >= n_blocks_count) {
+ n_blocks_count = ext4_group_first_block_no(sb, n_group);
+ n_group--;
+ n_blocks_count_retry = 0;
+ if (resize_inode) {
+ iput(resize_inode);
+ resize_inode = NULL;
+ }
+ goto retry;
+ }
+
/* extend the last group */
if (n_group == o_group)
add = n_blocks_count - o_blocks_count;
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 1a2c223..031e43d 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -2019,6 +2019,8 @@
SEQ_OPTS_PRINT("max_dir_size_kb=%u", sbi->s_max_dir_size_kb);
if (test_opt(sb, DATA_ERR_ABORT))
SEQ_OPTS_PUTS("data_err=abort");
+ if (DUMMY_ENCRYPTION_ENABLED(sbi))
+ SEQ_OPTS_PUTS("test_dummy_encryption");
ext4_show_quota_options(seq, sb);
return 0;
@@ -4193,11 +4195,13 @@
block = ext4_count_free_clusters(sb);
ext4_free_blocks_count_set(sbi->s_es,
EXT4_C2B(sbi, block));
+ ext4_superblock_csum_set(sb);
err = percpu_counter_init(&sbi->s_freeclusters_counter, block,
GFP_KERNEL);
if (!err) {
unsigned long freei = ext4_count_free_inodes(sb);
sbi->s_es->s_free_inodes_count = cpu_to_le32(freei);
+ ext4_superblock_csum_set(sb);
err = percpu_counter_init(&sbi->s_freeinodes_counter, freei,
GFP_KERNEL);
}
diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
index fdcbe0f..c10180d 100644
--- a/fs/ext4/xattr.c
+++ b/fs/ext4/xattr.c
@@ -209,12 +209,12 @@
{
int error;
- if (buffer_verified(bh))
- return 0;
-
if (BHDR(bh)->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC) ||
BHDR(bh)->h_blocks != cpu_to_le32(1))
return -EFSCORRUPTED;
+ if (buffer_verified(bh))
+ return 0;
+
if (!ext4_xattr_block_csum_verify(inode, bh))
return -EFSBADCRC;
error = ext4_xattr_check_names(BFIRST(bh), bh->b_data + bh->b_size,
@@ -645,14 +645,20 @@
}
static int
-ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s)
+ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s,
+ struct inode *inode)
{
- struct ext4_xattr_entry *last;
+ struct ext4_xattr_entry *last, *next;
size_t free, min_offs = s->end - s->base, name_len = strlen(i->name);
/* Compute min_offs and last. */
last = s->first;
- for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
+ for (; !IS_LAST_ENTRY(last); last = next) {
+ next = EXT4_XATTR_NEXT(last);
+ if ((void *)next >= s->end) {
+ EXT4_ERROR_INODE(inode, "corrupted xattr entries");
+ return -EIO;
+ }
if (last->e_value_size) {
size_t offs = le16_to_cpu(last->e_value_offs);
if (offs < min_offs)
@@ -834,7 +840,7 @@
mb_cache_entry_delete_block(ext4_mb_cache, hash,
bs->bh->b_blocknr);
ea_bdebug(bs->bh, "modifying in-place");
- error = ext4_xattr_set_entry(i, s);
+ error = ext4_xattr_set_entry(i, s, inode);
if (!error) {
if (!IS_LAST_ENTRY(s->first))
ext4_xattr_rehash(header(s->base),
@@ -881,7 +887,7 @@
s->end = s->base + sb->s_blocksize;
}
- error = ext4_xattr_set_entry(i, s);
+ error = ext4_xattr_set_entry(i, s, inode);
if (error == -EFSCORRUPTED)
goto bad_block;
if (error)
@@ -1079,7 +1085,7 @@
if (EXT4_I(inode)->i_extra_isize == 0)
return -ENOSPC;
- error = ext4_xattr_set_entry(i, s);
+ error = ext4_xattr_set_entry(i, s, inode);
if (error) {
if (error == -ENOSPC &&
ext4_has_inline_data(inode)) {
@@ -1091,7 +1097,7 @@
error = ext4_xattr_ibody_find(inode, i, is);
if (error)
return error;
- error = ext4_xattr_set_entry(i, s);
+ error = ext4_xattr_set_entry(i, s, inode);
}
if (error)
return error;
@@ -1117,7 +1123,7 @@
if (EXT4_I(inode)->i_extra_isize == 0)
return -ENOSPC;
- error = ext4_xattr_set_entry(i, s);
+ error = ext4_xattr_set_entry(i, s, inode);
if (error)
return error;
header = IHDR(inode, ext4_raw_inode(&is->iloc));
@@ -1426,6 +1432,11 @@
last = IFIRST(header);
/* Find the entry best suited to be pushed into EA block */
for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
+ /* never move system.data out of the inode */
+ if ((last->e_name_len == 4) &&
+ (last->e_name_index == EXT4_XATTR_INDEX_SYSTEM) &&
+ !memcmp(last->e_name, "data", 4))
+ continue;
total_size =
EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
EXT4_XATTR_LEN(last->e_name_len);
diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c
index d7a53c6..6b24eb4 100644
--- a/fs/f2fs/checkpoint.c
+++ b/fs/f2fs/checkpoint.c
@@ -28,6 +28,7 @@
void f2fs_stop_checkpoint(struct f2fs_sb_info *sbi, bool end_io)
{
+ f2fs_build_fault_attr(sbi, 0, 0);
set_ckpt_flags(sbi, CP_ERROR_FLAG);
if (!end_io)
f2fs_flush_merged_writes(sbi);
@@ -70,6 +71,7 @@
.encrypted_page = NULL,
.is_meta = is_meta,
};
+ int err;
if (unlikely(!is_meta))
fio.op_flags &= ~REQ_META;
@@ -84,9 +86,10 @@
fio.page = page;
- if (f2fs_submit_page_bio(&fio)) {
+ err = f2fs_submit_page_bio(&fio);
+ if (err) {
f2fs_put_page(page, 1);
- goto repeat;
+ return ERR_PTR(err);
}
lock_page(page);
@@ -95,14 +98,9 @@
goto repeat;
}
- /*
- * if there is any IO error when accessing device, make our filesystem
- * readonly and make sure do not write checkpoint with non-uptodate
- * meta page.
- */
if (unlikely(!PageUptodate(page))) {
- memset(page_address(page), 0, PAGE_SIZE);
- f2fs_stop_checkpoint(sbi, false);
+ f2fs_put_page(page, 1);
+ return ERR_PTR(-EIO);
}
out:
return page;
@@ -113,13 +111,32 @@
return __get_meta_page(sbi, index, true);
}
+struct page *f2fs_get_meta_page_nofail(struct f2fs_sb_info *sbi, pgoff_t index)
+{
+ struct page *page;
+ int count = 0;
+
+retry:
+ page = __get_meta_page(sbi, index, true);
+ if (IS_ERR(page)) {
+ if (PTR_ERR(page) == -EIO &&
+ ++count <= DEFAULT_RETRY_IO_COUNT)
+ goto retry;
+
+ f2fs_stop_checkpoint(sbi, false);
+ f2fs_bug_on(sbi, 1);
+ }
+
+ return page;
+}
+
/* for POR only */
struct page *f2fs_get_tmp_page(struct f2fs_sb_info *sbi, pgoff_t index)
{
return __get_meta_page(sbi, index, false);
}
-bool f2fs_is_valid_meta_blkaddr(struct f2fs_sb_info *sbi,
+bool f2fs_is_valid_blkaddr(struct f2fs_sb_info *sbi,
block_t blkaddr, int type)
{
switch (type) {
@@ -140,8 +157,20 @@
return false;
break;
case META_POR:
+ case DATA_GENERIC:
if (unlikely(blkaddr >= MAX_BLKADDR(sbi) ||
- blkaddr < MAIN_BLKADDR(sbi)))
+ blkaddr < MAIN_BLKADDR(sbi))) {
+ if (type == DATA_GENERIC) {
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "access invalid blkaddr:%u", blkaddr);
+ WARN_ON(1);
+ }
+ return false;
+ }
+ break;
+ case META_GENERIC:
+ if (unlikely(blkaddr < SEG0_BLKADDR(sbi) ||
+ blkaddr >= MAIN_BLKADDR(sbi)))
return false;
break;
default:
@@ -176,7 +205,7 @@
blk_start_plug(&plug);
for (; nrpages-- > 0; blkno++) {
- if (!f2fs_is_valid_meta_blkaddr(sbi, blkno, type))
+ if (!f2fs_is_valid_blkaddr(sbi, blkno, type))
goto out;
switch (type) {
@@ -242,11 +271,8 @@
trace_f2fs_writepage(page, META);
- if (unlikely(f2fs_cp_error(sbi))) {
- dec_page_count(sbi, F2FS_DIRTY_META);
- unlock_page(page);
- return 0;
- }
+ if (unlikely(f2fs_cp_error(sbi)))
+ goto redirty_out;
if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
goto redirty_out;
if (wbc->for_reclaim && page->index < GET_SUM_BLOCK(sbi, 0))
@@ -529,13 +555,12 @@
spin_lock(&im->ino_lock);
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_ORPHAN)) {
spin_unlock(&im->ino_lock);
f2fs_show_injection_info(FAULT_ORPHAN);
return -ENOSPC;
}
-#endif
+
if (unlikely(im->ino_num >= sbi->max_orphans))
err = -ENOSPC;
else
@@ -572,12 +597,7 @@
{
struct inode *inode;
struct node_info ni;
- int err = f2fs_acquire_orphan_inode(sbi);
-
- if (err)
- goto err_out;
-
- __add_ino_entry(sbi, ino, 0, ORPHAN_INO);
+ int err;
inode = f2fs_iget_retry(sbi->sb, ino);
if (IS_ERR(inode)) {
@@ -600,14 +620,15 @@
/* truncate all the data during iput */
iput(inode);
- f2fs_get_node_info(sbi, ino, &ni);
+ err = f2fs_get_node_info(sbi, ino, &ni);
+ if (err)
+ goto err_out;
/* ENOMEM was fully retried in f2fs_evict_inode. */
if (ni.blk_addr != NULL_ADDR) {
err = -EIO;
goto err_out;
}
- __remove_ino_entry(sbi, ino, ORPHAN_INO);
return 0;
err_out:
@@ -639,7 +660,10 @@
/* Needed for iput() to work correctly and not trash data */
sbi->sb->s_flags |= MS_ACTIVE;
- /* Turn on quotas so that they are updated correctly */
+ /*
+ * Turn on quotas which were not enabled for read-only mounts if
+ * filesystem has quota feature, so that they are updated correctly.
+ */
quota_enabled = f2fs_enable_quota_files(sbi, s_flags & MS_RDONLY);
#endif
@@ -649,9 +673,15 @@
f2fs_ra_meta_pages(sbi, start_blk, orphan_blocks, META_CP, true);
for (i = 0; i < orphan_blocks; i++) {
- struct page *page = f2fs_get_meta_page(sbi, start_blk + i);
+ struct page *page;
struct f2fs_orphan_block *orphan_blk;
+ page = f2fs_get_meta_page(sbi, start_blk + i);
+ if (IS_ERR(page)) {
+ err = PTR_ERR(page);
+ goto out;
+ }
+
orphan_blk = (struct f2fs_orphan_block *)page_address(page);
for (j = 0; j < le32_to_cpu(orphan_blk->entry_count); j++) {
nid_t ino = le32_to_cpu(orphan_blk->ino[j]);
@@ -742,10 +772,14 @@
__u32 crc = 0;
*cp_page = f2fs_get_meta_page(sbi, cp_addr);
+ if (IS_ERR(*cp_page))
+ return PTR_ERR(*cp_page);
+
*cp_block = (struct f2fs_checkpoint *)page_address(*cp_page);
crc_offset = le32_to_cpu((*cp_block)->checksum_offset);
if (crc_offset > (blk_size - sizeof(__le32))) {
+ f2fs_put_page(*cp_page, 1);
f2fs_msg(sbi->sb, KERN_WARNING,
"invalid crc_offset: %zu", crc_offset);
return -EINVAL;
@@ -753,6 +787,7 @@
crc = cur_cp_crc(*cp_block);
if (!f2fs_crc_valid(sbi, crc, *cp_block, crc_offset)) {
+ f2fs_put_page(*cp_page, 1);
f2fs_msg(sbi->sb, KERN_WARNING, "invalid crc value");
return -EINVAL;
}
@@ -772,14 +807,22 @@
err = get_checkpoint_version(sbi, cp_addr, &cp_block,
&cp_page_1, version);
if (err)
- goto invalid_cp1;
+ return NULL;
+
+ if (le32_to_cpu(cp_block->cp_pack_total_block_count) >
+ sbi->blocks_per_seg) {
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "invalid cp_pack_total_block_count:%u",
+ le32_to_cpu(cp_block->cp_pack_total_block_count));
+ goto invalid_cp;
+ }
pre_version = *version;
cp_addr += le32_to_cpu(cp_block->cp_pack_total_block_count) - 1;
err = get_checkpoint_version(sbi, cp_addr, &cp_block,
&cp_page_2, version);
if (err)
- goto invalid_cp2;
+ goto invalid_cp;
cur_version = *version;
if (cur_version == pre_version) {
@@ -787,9 +830,8 @@
f2fs_put_page(cp_page_2, 1);
return cp_page_1;
}
-invalid_cp2:
f2fs_put_page(cp_page_2, 1);
-invalid_cp1:
+invalid_cp:
f2fs_put_page(cp_page_1, 1);
return NULL;
}
@@ -838,15 +880,15 @@
cp_block = (struct f2fs_checkpoint *)page_address(cur_page);
memcpy(sbi->ckpt, cp_block, blk_size);
- /* Sanity checking of checkpoint */
- if (f2fs_sanity_check_ckpt(sbi))
- goto free_fail_no_cp;
-
if (cur_page == cp1)
sbi->cur_cp_pack = 1;
else
sbi->cur_cp_pack = 2;
+ /* Sanity checking of checkpoint */
+ if (f2fs_sanity_check_ckpt(sbi))
+ goto free_fail_no_cp;
+
if (cp_blks <= 1)
goto done;
@@ -859,6 +901,8 @@
unsigned char *ckpt = (unsigned char *)sbi->ckpt;
cur_page = f2fs_get_meta_page(sbi, cp_blk_no + i);
+ if (IS_ERR(cur_page))
+ goto free_fail_no_cp;
sit_bitmap_ptr = page_address(cur_page);
memcpy(ckpt + i * blk_size, sit_bitmap_ptr, blk_size);
f2fs_put_page(cur_page, 1);
@@ -980,12 +1024,10 @@
iput(inode);
/* We need to give cpu to another writers. */
- if (ino == cur_ino) {
- congestion_wait(BLK_RW_ASYNC, HZ/50);
+ if (ino == cur_ino)
cond_resched();
- } else {
+ else
ino = cur_ino;
- }
} else {
/*
* We should submit bio, since it exists several
@@ -1119,7 +1161,7 @@
f2fs_unlock_all(sbi);
}
-static void wait_on_all_pages_writeback(struct f2fs_sb_info *sbi)
+void f2fs_wait_on_all_pages_writeback(struct f2fs_sb_info *sbi)
{
DEFINE_WAIT(wait);
@@ -1129,6 +1171,9 @@
if (!get_pages(sbi, F2FS_WB_CP_DATA))
break;
+ if (unlikely(f2fs_cp_error(sbi)))
+ break;
+
io_schedule_timeout(5*HZ);
}
finish_wait(&sbi->cp_wait, &wait);
@@ -1202,8 +1247,12 @@
/* writeout cp pack 2 page */
err = __f2fs_write_meta_page(page, &wbc, FS_CP_META_IO);
- f2fs_bug_on(sbi, err);
+ if (unlikely(err && f2fs_cp_error(sbi))) {
+ f2fs_put_page(page, 1);
+ return;
+ }
+ f2fs_bug_on(sbi, err);
f2fs_put_page(page, 0);
/* submit checkpoint (with barrier if NOBARRIER is not set) */
@@ -1229,7 +1278,7 @@
while (get_pages(sbi, F2FS_DIRTY_META)) {
f2fs_sync_meta_pages(sbi, META, LONG_MAX, FS_CP_META_IO);
if (unlikely(f2fs_cp_error(sbi)))
- return -EIO;
+ break;
}
/*
@@ -1309,7 +1358,7 @@
f2fs_sync_meta_pages(sbi, META, LONG_MAX,
FS_CP_META_IO);
if (unlikely(f2fs_cp_error(sbi)))
- return -EIO;
+ break;
}
}
@@ -1348,10 +1397,7 @@
f2fs_sync_meta_pages(sbi, META, LONG_MAX, FS_CP_META_IO);
/* wait for previous submitted meta pages writeback */
- wait_on_all_pages_writeback(sbi);
-
- if (unlikely(f2fs_cp_error(sbi)))
- return -EIO;
+ f2fs_wait_on_all_pages_writeback(sbi);
/* flush all device cache */
err = f2fs_flush_device_cache(sbi);
@@ -1360,12 +1406,19 @@
/* barrier and flush checkpoint cp pack 2 page if it can */
commit_checkpoint(sbi, ckpt, start_blk);
- wait_on_all_pages_writeback(sbi);
+ f2fs_wait_on_all_pages_writeback(sbi);
+
+ /*
+ * invalidate intermediate page cache borrowed from meta inode
+ * which are used for migration of encrypted inode's blocks.
+ */
+ if (f2fs_sb_has_encrypt(sbi->sb))
+ invalidate_mapping_pages(META_MAPPING(sbi),
+ MAIN_BLKADDR(sbi), MAX_BLKADDR(sbi) - 1);
f2fs_release_ino_entry(sbi, false);
- if (unlikely(f2fs_cp_error(sbi)))
- return -EIO;
+ f2fs_reset_fsync_node_info(sbi);
clear_sbi_flag(sbi, SBI_IS_DIRTY);
clear_sbi_flag(sbi, SBI_NEED_CP);
@@ -1381,7 +1434,7 @@
f2fs_bug_on(sbi, get_pages(sbi, F2FS_DIRTY_DENTS));
- return 0;
+ return unlikely(f2fs_cp_error(sbi)) ? -EIO : 0;
}
/*
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index f7f2c13..6570c75 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -126,12 +126,10 @@
static void f2fs_read_end_io(struct bio *bio)
{
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(F2FS_P_SB(bio->bi_io_vec->bv_page), FAULT_IO)) {
f2fs_show_injection_info(FAULT_IO);
bio->bi_error = -EIO;
}
-#endif
if (f2fs_bio_post_read_required(bio)) {
struct bio_post_read_ctx *ctx = bio->bi_private;
@@ -177,6 +175,8 @@
page->index != nid_of_node(page));
dec_page_count(sbi, type);
+ if (f2fs_in_warm_node_list(sbi, page))
+ f2fs_del_fsync_node_entry(sbi, page);
clear_cold_data(page);
end_page_writeback(page);
}
@@ -263,7 +263,7 @@
if (type != DATA && type != NODE)
goto submit_io;
- if (f2fs_sb_has_blkzoned(sbi->sb) && current->plug)
+ if (test_opt(sbi, LFS) && current->plug)
blk_finish_plug(current->plug);
start = bio->bi_iter.bi_size >> F2FS_BLKSIZE_BITS;
@@ -441,7 +441,10 @@
fio->encrypted_page : fio->page;
struct inode *inode = fio->page->mapping->host;
- verify_block_addr(fio, fio->new_blkaddr);
+ if (!f2fs_is_valid_blkaddr(fio->sbi, fio->new_blkaddr,
+ __is_meta_io(fio) ? META_GENERIC : DATA_GENERIC))
+ return -EFAULT;
+
trace_f2fs_submit_page_bio(page, fio);
f2fs_trace_ios(fio, 0);
@@ -493,7 +496,7 @@
spin_unlock(&io->io_lock);
}
- if (is_valid_blkaddr(fio->old_blkaddr))
+ if (__is_valid_data_blkaddr(fio->old_blkaddr))
verify_block_addr(fio, fio->old_blkaddr);
verify_block_addr(fio, fio->new_blkaddr);
@@ -554,19 +557,22 @@
}
static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
- unsigned nr_pages)
+ unsigned nr_pages, unsigned op_flag)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct bio *bio;
struct bio_post_read_ctx *ctx;
unsigned int post_read_steps = 0;
+ if (!f2fs_is_valid_blkaddr(sbi, blkaddr, DATA_GENERIC))
+ return ERR_PTR(-EFAULT);
+
bio = f2fs_bio_alloc(sbi, min_t(int, nr_pages, BIO_MAX_PAGES), false);
if (!bio)
return ERR_PTR(-ENOMEM);
f2fs_target_device(sbi, blkaddr, bio);
bio->bi_end_io = f2fs_read_end_io;
- bio_set_op_attrs(bio, REQ_OP_READ, 0);
+ bio_set_op_attrs(bio, REQ_OP_READ, op_flag);
if (f2fs_encrypted_file(inode) &&
!fscrypt_using_hardware_encryption(inode))
@@ -592,7 +598,7 @@
static int f2fs_submit_page_read(struct inode *inode, struct page *page,
block_t blkaddr)
{
- struct bio *bio = f2fs_grab_read_bio(inode, blkaddr, 1);
+ struct bio *bio = f2fs_grab_read_bio(inode, blkaddr, 1, 0);
if (IS_ERR(bio))
return PTR_ERR(bio);
@@ -893,6 +899,7 @@
struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
struct f2fs_summary sum;
struct node_info ni;
+ block_t old_blkaddr;
pgoff_t fofs;
blkcnt_t count = 1;
int err;
@@ -900,6 +907,10 @@
if (unlikely(is_inode_flag_set(dn->inode, FI_NO_ALLOC)))
return -EPERM;
+ err = f2fs_get_node_info(sbi, dn->nid, &ni);
+ if (err)
+ return err;
+
dn->data_blkaddr = datablock_addr(dn->inode,
dn->node_page, dn->ofs_in_node);
if (dn->data_blkaddr == NEW_ADDR)
@@ -909,11 +920,13 @@
return err;
alloc:
- f2fs_get_node_info(sbi, dn->nid, &ni);
set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
-
- f2fs_allocate_data_block(sbi, NULL, dn->data_blkaddr, &dn->data_blkaddr,
+ old_blkaddr = dn->data_blkaddr;
+ f2fs_allocate_data_block(sbi, NULL, old_blkaddr, &dn->data_blkaddr,
&sum, seg_type, NULL, false);
+ if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO)
+ invalidate_mapping_pages(META_MAPPING(sbi),
+ old_blkaddr, old_blkaddr);
f2fs_set_data_blkaddr(dn);
/* update i_size */
@@ -1069,7 +1082,13 @@
next_block:
blkaddr = datablock_addr(dn.inode, dn.node_page, dn.ofs_in_node);
- if (!is_valid_blkaddr(blkaddr)) {
+ if (__is_valid_data_blkaddr(blkaddr) &&
+ !f2fs_is_valid_blkaddr(sbi, blkaddr, DATA_GENERIC)) {
+ err = -EFAULT;
+ goto sync_out;
+ }
+
+ if (!is_valid_data_blkaddr(sbi, blkaddr)) {
if (create) {
if (unlikely(f2fs_cp_error(sbi))) {
err = -EIO;
@@ -1306,7 +1325,11 @@
if (!page)
return -ENOMEM;
- f2fs_get_node_info(sbi, inode->i_ino, &ni);
+ err = f2fs_get_node_info(sbi, inode->i_ino, &ni);
+ if (err) {
+ f2fs_put_page(page, 1);
+ return err;
+ }
phys = (__u64)blk_to_logical(inode, ni.blk_addr);
offset = offsetof(struct f2fs_inode, i_addr) +
@@ -1333,7 +1356,11 @@
if (!page)
return -ENOMEM;
- f2fs_get_node_info(sbi, xnid, &ni);
+ err = f2fs_get_node_info(sbi, xnid, &ni);
+ if (err) {
+ f2fs_put_page(page, 1);
+ return err;
+ }
phys = (__u64)blk_to_logical(inode, ni.blk_addr);
len = inode->i_sb->s_blocksize;
@@ -1445,10 +1472,15 @@
/*
* This function was originally taken from fs/mpage.c, and customized for f2fs.
* Major change was from block_size == page_size in f2fs by default.
+ *
+ * Note that the aops->readpages() function is ONLY used for read-ahead. If
+ * this function ever deviates from doing just read-ahead, it should either
+ * use ->readpage() or do the necessary surgery to decouple ->readpages()
+ * from read-ahead.
*/
static int f2fs_mpage_readpages(struct address_space *mapping,
struct list_head *pages, struct page *page,
- unsigned nr_pages)
+ unsigned nr_pages, bool is_readahead)
{
struct bio *bio = NULL;
sector_t last_block_in_bio = 0;
@@ -1521,6 +1553,10 @@
SetPageUptodate(page);
goto confused;
}
+
+ if (!f2fs_is_valid_blkaddr(F2FS_I_SB(inode), block_nr,
+ DATA_GENERIC))
+ goto set_error_page;
} else {
zero_user_segment(page, 0, PAGE_SIZE);
if (!PageUptodate(page))
@@ -1548,7 +1584,8 @@
}
if (bio == NULL) {
- bio = f2fs_grab_read_bio(inode, block_nr, nr_pages);
+ bio = f2fs_grab_read_bio(inode, block_nr, nr_pages,
+ is_readahead ? REQ_RAHEAD : 0);
if (IS_ERR(bio)) {
bio = NULL;
goto set_error_page;
@@ -1593,7 +1630,7 @@
if (f2fs_has_inline_data(inode))
ret = f2fs_read_inline_data(inode, page);
if (ret == -EAGAIN)
- ret = f2fs_mpage_readpages(page->mapping, NULL, page, 1);
+ ret = f2fs_mpage_readpages(page->mapping, NULL, page, 1, false);
return ret;
}
@@ -1610,12 +1647,13 @@
if (f2fs_has_inline_data(inode))
return 0;
- return f2fs_mpage_readpages(mapping, pages, NULL, nr_pages);
+ return f2fs_mpage_readpages(mapping, pages, NULL, nr_pages, true);
}
static int encrypt_one_page(struct f2fs_io_info *fio)
{
struct inode *inode = fio->page->mapping->host;
+ struct page *mpage;
gfp_t gfp_flags = GFP_NOFS;
if (!f2fs_encrypted_file(inode))
@@ -1630,17 +1668,25 @@
fio->encrypted_page = fscrypt_encrypt_page(inode, fio->page,
PAGE_SIZE, 0, fio->page->index, gfp_flags);
- if (!IS_ERR(fio->encrypted_page))
- return 0;
-
- /* flush pending IOs and wait for a while in the ENOMEM case */
- if (PTR_ERR(fio->encrypted_page) == -ENOMEM) {
- f2fs_flush_merged_writes(fio->sbi);
- congestion_wait(BLK_RW_ASYNC, HZ/50);
- gfp_flags |= __GFP_NOFAIL;
- goto retry_encrypt;
+ if (IS_ERR(fio->encrypted_page)) {
+ /* flush pending IOs and wait for a while in the ENOMEM case */
+ if (PTR_ERR(fio->encrypted_page) == -ENOMEM) {
+ f2fs_flush_merged_writes(fio->sbi);
+ congestion_wait(BLK_RW_ASYNC, HZ/50);
+ gfp_flags |= __GFP_NOFAIL;
+ goto retry_encrypt;
+ }
+ return PTR_ERR(fio->encrypted_page);
}
- return PTR_ERR(fio->encrypted_page);
+
+ mpage = find_lock_page(META_MAPPING(fio->sbi), fio->old_blkaddr);
+ if (mpage) {
+ if (PageUptodate(mpage))
+ memcpy(page_address(mpage),
+ page_address(fio->encrypted_page), PAGE_SIZE);
+ f2fs_put_page(mpage, 1);
+ }
+ return 0;
}
static inline bool check_inplace_update_policy(struct inode *inode,
@@ -1724,6 +1770,7 @@
struct inode *inode = page->mapping->host;
struct dnode_of_data dn;
struct extent_info ei = {0,0,0};
+ struct node_info ni;
bool ipu_force = false;
int err = 0;
@@ -1732,11 +1779,13 @@
f2fs_lookup_extent_cache(inode, page->index, &ei)) {
fio->old_blkaddr = ei.blk + page->index - ei.fofs;
- if (is_valid_blkaddr(fio->old_blkaddr)) {
- ipu_force = true;
- fio->need_lock = LOCK_DONE;
- goto got_it;
- }
+ if (!f2fs_is_valid_blkaddr(fio->sbi, fio->old_blkaddr,
+ DATA_GENERIC))
+ return -EFAULT;
+
+ ipu_force = true;
+ fio->need_lock = LOCK_DONE;
+ goto got_it;
}
/* Deadlock due to between page->lock and f2fs_lock_op */
@@ -1755,11 +1804,17 @@
goto out_writepage;
}
got_it:
+ if (__is_valid_data_blkaddr(fio->old_blkaddr) &&
+ !f2fs_is_valid_blkaddr(fio->sbi, fio->old_blkaddr,
+ DATA_GENERIC)) {
+ err = -EFAULT;
+ goto out_writepage;
+ }
/*
* If current allocation needs SSR,
* it had better in-place writes for updated data.
*/
- if (ipu_force || (is_valid_blkaddr(fio->old_blkaddr) &&
+ if (ipu_force || (is_valid_data_blkaddr(fio->sbi, fio->old_blkaddr) &&
need_inplace_update(fio))) {
err = encrypt_one_page(fio);
if (err)
@@ -1784,6 +1839,12 @@
fio->need_lock = LOCK_REQ;
}
+ err = f2fs_get_node_info(fio->sbi, dn.nid, &ni);
+ if (err)
+ goto out_writepage;
+
+ fio->version = ni.version;
+
err = encrypt_one_page(fio);
if (err)
goto out_writepage;
@@ -2207,10 +2268,14 @@
loff_t i_size = i_size_read(inode);
if (to > i_size) {
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
down_write(&F2FS_I(inode)->i_mmap_sem);
+
truncate_pagecache(inode, i_size);
f2fs_truncate_blocks(inode, i_size, true);
+
up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
}
}
@@ -2315,8 +2380,9 @@
}
trace_f2fs_write_begin(inode, pos, len, flags);
- if (f2fs_is_atomic_file(inode) &&
- !f2fs_available_free_memory(sbi, INMEM_PAGES)) {
+ if ((f2fs_is_atomic_file(inode) &&
+ !f2fs_available_free_memory(sbi, INMEM_PAGES)) ||
+ is_inode_flag_set(inode, FI_ATOMIC_REVOKE_REQUEST)) {
err = -ENOMEM;
drop_atomic = true;
goto fail;
@@ -2441,14 +2507,20 @@
static int check_direct_IO(struct inode *inode, struct iov_iter *iter,
loff_t offset)
{
- unsigned blocksize_mask = inode->i_sb->s_blocksize - 1;
+ unsigned i_blkbits = READ_ONCE(inode->i_blkbits);
+ unsigned blkbits = i_blkbits;
+ unsigned blocksize_mask = (1 << blkbits) - 1;
+ unsigned long align = offset | iov_iter_alignment(iter);
+ struct block_device *bdev = inode->i_sb->s_bdev;
- if (offset & blocksize_mask)
- return -EINVAL;
-
- if (iov_iter_alignment(iter) & blocksize_mask)
- return -EINVAL;
-
+ if (align & blocksize_mask) {
+ if (bdev)
+ blkbits = blksize_bits(bdev_logical_block_size(bdev));
+ blocksize_mask = (1 << blkbits) - 1;
+ if (align & blocksize_mask)
+ return -EINVAL;
+ return 1;
+ }
return 0;
}
@@ -2466,7 +2538,7 @@
err = check_direct_IO(inode, iter, offset);
if (err)
- return err;
+ return err < 0 ? err : 0;
if (f2fs_force_buffered_io(inode, rw))
return 0;
@@ -2588,6 +2660,10 @@
if (!PageUptodate(page))
SetPageUptodate(page);
+ /* don't remain PG_checked flag which was set during GC */
+ if (is_cold_data(page))
+ clear_cold_data(page);
+
if (f2fs_is_atomic_file(inode) && !f2fs_is_commit_atomic_write(inode)) {
if (!IS_ATOMIC_WRITTEN_PAGE(page)) {
f2fs_register_inmem_page(inode, page);
diff --git a/fs/f2fs/debug.c b/fs/f2fs/debug.c
index 2d65e77..214a968 100644
--- a/fs/f2fs/debug.c
+++ b/fs/f2fs/debug.c
@@ -215,7 +215,8 @@
si->base_mem += sizeof(struct f2fs_nm_info);
si->base_mem += __bitmap_size(sbi, NAT_BITMAP);
si->base_mem += (NM_I(sbi)->nat_bits_blocks << F2FS_BLKSIZE_BITS);
- si->base_mem += NM_I(sbi)->nat_blocks * NAT_ENTRY_BITMAP_SIZE;
+ si->base_mem += NM_I(sbi)->nat_blocks *
+ f2fs_bitmap_size(NAT_ENTRY_PER_BLOCK);
si->base_mem += NM_I(sbi)->nat_blocks / 8;
si->base_mem += NM_I(sbi)->nat_blocks * sizeof(unsigned short);
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index 63c0732..56cc274 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -517,12 +517,11 @@
}
start:
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(F2FS_I_SB(dir), FAULT_DIR_DEPTH)) {
f2fs_show_injection_info(FAULT_DIR_DEPTH);
return -ENOSPC;
}
-#endif
+
if (unlikely(current_depth == MAX_DIR_HASH_DEPTH))
return -ENOSPC;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index b696bc8..5bcbdce 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -43,7 +43,6 @@
} while (0)
#endif
-#ifdef CONFIG_F2FS_FAULT_INJECTION
enum {
FAULT_KMALLOC,
FAULT_KVMALLOC,
@@ -58,16 +57,20 @@
FAULT_TRUNCATE,
FAULT_IO,
FAULT_CHECKPOINT,
+ FAULT_DISCARD,
FAULT_MAX,
};
+#ifdef CONFIG_F2FS_FAULT_INJECTION
+#define F2FS_ALL_FAULT_TYPE ((1 << FAULT_MAX) - 1)
+
struct f2fs_fault_info {
atomic_t inject_ops;
unsigned int inject_rate;
unsigned int inject_type;
};
-extern char *fault_name[FAULT_MAX];
+extern char *f2fs_fault_name[FAULT_MAX];
#define IS_FAULT_SET(fi, type) ((fi)->inject_type & (1 << (type)))
#endif
@@ -180,7 +183,6 @@
#define MAX_DISCARD_BLOCKS(sbi) BLKS_PER_SEC(sbi)
#define DEF_MAX_DISCARD_REQUEST 8 /* issue 8 discards per round */
-#define DEF_MAX_DISCARD_LEN 512 /* Max. 2MB per discard */
#define DEF_MIN_DISCARD_ISSUE_TIME 50 /* 50 ms, if exists */
#define DEF_MID_DISCARD_ISSUE_TIME 500 /* 500 ms, if device busy */
#define DEF_MAX_DISCARD_ISSUE_TIME 60000 /* 60 s, if no candidates */
@@ -196,7 +198,7 @@
};
/*
- * For CP/NAT/SIT/SSA readahead
+ * indicate meta/data type
*/
enum {
META_CP,
@@ -204,6 +206,8 @@
META_SIT,
META_SSA,
META_POR,
+ DATA_GENERIC,
+ META_GENERIC,
};
/* for the list of ino */
@@ -228,6 +232,12 @@
struct inode *inode; /* vfs inode pointer */
};
+struct fsync_node_entry {
+ struct list_head list; /* list head */
+ struct page *page; /* warm node page pointer */
+ unsigned int seq_id; /* sequence id */
+};
+
/* for the bitmap indicate blocks to be discarded */
struct discard_entry {
struct list_head list; /* list head */
@@ -244,9 +254,10 @@
(MAX_PLIST_NUM - 1) : (blk_num - 1))
enum {
- D_PREP,
- D_SUBMIT,
- D_DONE,
+ D_PREP, /* initial */
+ D_PARTIAL, /* partially submitted */
+ D_SUBMIT, /* all submitted */
+ D_DONE, /* finished */
};
struct discard_info {
@@ -271,7 +282,10 @@
struct block_device *bdev; /* bdev */
unsigned short ref; /* reference count */
unsigned char state; /* state */
+ unsigned char issuing; /* issuing discard */
int error; /* bio error */
+ spinlock_t lock; /* for state/bio_ref updating */
+ unsigned short bio_ref; /* bio reference count */
};
enum {
@@ -291,6 +305,7 @@
unsigned int io_aware_gran; /* minimum granularity discard not be aware of I/O */
bool io_aware; /* issue discard in idle time */
bool sync; /* submit discard with REQ_SYNC flag */
+ bool ordered; /* issue discard by lba order */
unsigned int granularity; /* discard granularity */
};
@@ -307,10 +322,12 @@
unsigned int max_discards; /* max. discards to be issued */
unsigned int discard_granularity; /* discard granularity */
unsigned int undiscard_blks; /* # of undiscard blocks */
+ unsigned int next_pos; /* next discard position */
atomic_t issued_discard; /* # of issued discard */
atomic_t issing_discard; /* # of issing discard */
atomic_t discard_cmd_cnt; /* # of cached cmd count */
struct rb_root root; /* root of discard rb-tree */
+ bool rbtree_check; /* config for consistence check */
};
/* for the list of fsync inodes, used only during recovery */
@@ -507,13 +524,12 @@
*/
};
+#define DEFAULT_RETRY_IO_COUNT 8 /* maximum retry read IO count */
+
#define F2FS_LINK_MAX 0xffffffff /* maximum link count per file */
#define MAX_DIR_RA_PAGES 4 /* maximum ra pages of dir */
-/* vector size for gang look-up from extent cache that consists of radix tree */
-#define EXT_TREE_VEC_SIZE 64
-
/* for in-memory extent cache entry */
#define F2FS_MIN_EXTENT_LEN 64 /* minimum extent length */
@@ -599,6 +615,8 @@
#define FADVISE_HOT_BIT 0x20
#define FADVISE_VERITY_BIT 0x40 /* reserved */
+#define FADVISE_MODIFIABLE_BITS (FADVISE_COLD_BIT | FADVISE_HOT_BIT)
+
#define file_is_cold(inode) is_file(inode, FADVISE_COLD_BIT)
#define file_wrong_pino(inode) is_file(inode, FADVISE_LOST_PINO_BIT)
#define file_set_cold(inode) set_file(inode, FADVISE_COLD_BIT)
@@ -697,22 +715,22 @@
}
static inline bool __is_discard_mergeable(struct discard_info *back,
- struct discard_info *front)
+ struct discard_info *front, unsigned int max_len)
{
return (back->lstart + back->len == front->lstart) &&
- (back->len + front->len < DEF_MAX_DISCARD_LEN);
+ (back->len + front->len <= max_len);
}
static inline bool __is_discard_back_mergeable(struct discard_info *cur,
- struct discard_info *back)
+ struct discard_info *back, unsigned int max_len)
{
- return __is_discard_mergeable(back, cur);
+ return __is_discard_mergeable(back, cur, max_len);
}
static inline bool __is_discard_front_mergeable(struct discard_info *cur,
- struct discard_info *front)
+ struct discard_info *front, unsigned int max_len)
{
- return __is_discard_mergeable(cur, front);
+ return __is_discard_mergeable(cur, front, max_len);
}
static inline bool __is_extent_mergeable(struct extent_info *back,
@@ -767,6 +785,7 @@
struct radix_tree_root nat_set_root;/* root of the nat set cache */
struct rw_semaphore nat_tree_lock; /* protect nat_tree_lock */
struct list_head nat_entries; /* cached nat entry list (clean) */
+ spinlock_t nat_list_lock; /* protect clean nat entry list */
unsigned int nat_cnt; /* the # of cached nat entries */
unsigned int dirty_nat_cnt; /* total num of nat entries in set */
unsigned int nat_blocks; /* # of nat blocks */
@@ -1015,6 +1034,7 @@
bool retry; /* need to reallocate block address */
enum iostat_type io_type; /* io type */
struct writeback_control *io_wbc; /* writeback control */
+ unsigned char version; /* version of the node */
};
#define is_read_io(rw) ((rw) == READ)
@@ -1066,6 +1086,7 @@
SBI_POR_DOING, /* recovery is doing or not */
SBI_NEED_SB_WRITE, /* need to recover superblock */
SBI_NEED_CP, /* need to checkpoint */
+ SBI_IS_SHUTDOWN, /* shutdown by ioctl */
};
enum {
@@ -1149,6 +1170,11 @@
struct inode_management im[MAX_INO_ENTRY]; /* manage inode cache */
+ spinlock_t fsync_node_lock; /* for node entry lock */
+ struct list_head fsync_node_list; /* node list head */
+ unsigned int fsync_seg_id; /* sequence id */
+ unsigned int fsync_node_num; /* number of node entries */
+
/* for orphan inode, use 0'th array */
unsigned int max_orphans; /* max orphan inodes */
@@ -1216,6 +1242,7 @@
unsigned int gc_mode; /* current GC state */
/* for skip statistic */
unsigned long long skipped_atomic_files[2]; /* FG_GC and BG_GC */
+ unsigned long long skipped_gc_rwsem; /* FG_GC only */
/* threshold for gc trials on pinned files */
u64 gc_pin_file_threshold;
@@ -1280,7 +1307,7 @@
#ifdef CONFIG_F2FS_FAULT_INJECTION
#define f2fs_show_injection_info(type) \
printk("%sF2FS-fs : inject %s in %s of %pF\n", \
- KERN_INFO, fault_name[type], \
+ KERN_INFO, f2fs_fault_name[type], \
__func__, __builtin_return_address(0))
static inline bool time_to_inject(struct f2fs_sb_info *sbi, int type)
{
@@ -1299,6 +1326,12 @@
}
return false;
}
+#else
+#define f2fs_show_injection_info(type) do { } while (0)
+static inline bool time_to_inject(struct f2fs_sb_info *sbi, int type)
+{
+ return false;
+}
#endif
/* For write statistics. Suppose sector size is 512 bytes,
@@ -1327,7 +1360,7 @@
struct request_list *rl = &q->root_rl;
if (rl->count[BLK_RW_SYNC] || rl->count[BLK_RW_ASYNC])
- return 0;
+ return false;
return f2fs_time_over(sbi, REQ_TIME);
}
@@ -1651,13 +1684,12 @@
if (ret)
return ret;
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_BLOCK)) {
f2fs_show_injection_info(FAULT_BLOCK);
release = *count;
goto enospc;
}
-#endif
+
/*
* let's increase this in prior to actual block count change in order
* for f2fs_sync_file to avoid data races when deciding checkpoint.
@@ -1681,18 +1713,20 @@
sbi->total_valid_block_count -= diff;
if (!*count) {
spin_unlock(&sbi->stat_lock);
- percpu_counter_sub(&sbi->alloc_valid_block_count, diff);
goto enospc;
}
}
spin_unlock(&sbi->stat_lock);
- if (unlikely(release))
+ if (unlikely(release)) {
+ percpu_counter_sub(&sbi->alloc_valid_block_count, release);
dquot_release_reservation_block(inode, release);
+ }
f2fs_i_blocks_write(inode, *count, true, true);
return 0;
enospc:
+ percpu_counter_sub(&sbi->alloc_valid_block_count, release);
dquot_release_reservation_block(inode, release);
return -ENOSPC;
}
@@ -1864,12 +1898,10 @@
return ret;
}
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_BLOCK)) {
f2fs_show_injection_info(FAULT_BLOCK);
goto enospc;
}
-#endif
spin_lock(&sbi->stat_lock);
@@ -1954,17 +1986,23 @@
static inline struct page *f2fs_grab_cache_page(struct address_space *mapping,
pgoff_t index, bool for_write)
{
-#ifdef CONFIG_F2FS_FAULT_INJECTION
- struct page *page = find_lock_page(mapping, index);
+ struct page *page;
- if (page)
- return page;
+ if (IS_ENABLED(CONFIG_F2FS_FAULT_INJECTION)) {
+ if (!for_write)
+ page = find_get_page_flags(mapping, index,
+ FGP_LOCK | FGP_ACCESSED);
+ else
+ page = find_lock_page(mapping, index);
+ if (page)
+ return page;
- if (time_to_inject(F2FS_M_SB(mapping), FAULT_PAGE_ALLOC)) {
- f2fs_show_injection_info(FAULT_PAGE_ALLOC);
- return NULL;
+ if (time_to_inject(F2FS_M_SB(mapping), FAULT_PAGE_ALLOC)) {
+ f2fs_show_injection_info(FAULT_PAGE_ALLOC);
+ return NULL;
+ }
}
-#endif
+
if (!for_write)
return grab_cache_page(mapping, index);
return grab_cache_page_write_begin(mapping, index, AOP_FLAG_NOFS);
@@ -1974,12 +2012,11 @@
struct address_space *mapping, pgoff_t index,
int fgp_flags, gfp_t gfp_mask)
{
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(F2FS_M_SB(mapping), FAULT_PAGE_GET)) {
f2fs_show_injection_info(FAULT_PAGE_GET);
return NULL;
}
-#endif
+
return pagecache_get_page(mapping, index, fgp_flags, gfp_mask);
}
@@ -2044,12 +2081,11 @@
bio = bio_alloc(GFP_NOIO | __GFP_NOFAIL, npages);
return bio;
}
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_ALLOC_BIO)) {
f2fs_show_injection_info(FAULT_ALLOC_BIO);
return NULL;
}
-#endif
+
return bio_alloc(GFP_KERNEL, npages);
}
@@ -2584,12 +2620,11 @@
static inline void *f2fs_kmalloc(struct f2fs_sb_info *sbi,
size_t size, gfp_t flags)
{
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_KMALLOC)) {
f2fs_show_injection_info(FAULT_KMALLOC);
return NULL;
}
-#endif
+
return kmalloc(size, flags);
}
@@ -2632,12 +2667,11 @@
static inline void *f2fs_kvmalloc(struct f2fs_sb_info *sbi,
size_t size, gfp_t flags)
{
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_KVMALLOC)) {
f2fs_show_injection_info(FAULT_KVMALLOC);
return NULL;
}
-#endif
+
return kvmalloc(size, flags);
}
@@ -2696,13 +2730,39 @@
spin_unlock(&sbi->iostat_lock);
}
-static inline bool is_valid_blkaddr(block_t blkaddr)
+#define __is_meta_io(fio) (PAGE_TYPE_OF_BIO(fio->type) == META && \
+ (!is_read_io(fio->op) || fio->is_meta))
+
+bool f2fs_is_valid_blkaddr(struct f2fs_sb_info *sbi,
+ block_t blkaddr, int type);
+void f2fs_msg(struct super_block *sb, const char *level, const char *fmt, ...);
+static inline void verify_blkaddr(struct f2fs_sb_info *sbi,
+ block_t blkaddr, int type)
+{
+ if (!f2fs_is_valid_blkaddr(sbi, blkaddr, type)) {
+ f2fs_msg(sbi->sb, KERN_ERR,
+ "invalid blkaddr: %u, type: %d, run fsck to fix.",
+ blkaddr, type);
+ f2fs_bug_on(sbi, 1);
+ }
+}
+
+static inline bool __is_valid_data_blkaddr(block_t blkaddr)
{
if (blkaddr == NEW_ADDR || blkaddr == NULL_ADDR)
return false;
return true;
}
+static inline bool is_valid_data_blkaddr(struct f2fs_sb_info *sbi,
+ block_t blkaddr)
+{
+ if (!__is_valid_data_blkaddr(blkaddr))
+ return false;
+ verify_blkaddr(sbi, blkaddr, DATA_GENERIC);
+ return true;
+}
+
/*
* file.c
*/
@@ -2817,16 +2877,21 @@
int f2fs_check_nid_range(struct f2fs_sb_info *sbi, nid_t nid);
bool f2fs_available_free_memory(struct f2fs_sb_info *sbi, int type);
+bool f2fs_in_warm_node_list(struct f2fs_sb_info *sbi, struct page *page);
+void f2fs_init_fsync_node_info(struct f2fs_sb_info *sbi);
+void f2fs_del_fsync_node_entry(struct f2fs_sb_info *sbi, struct page *page);
+void f2fs_reset_fsync_node_info(struct f2fs_sb_info *sbi);
int f2fs_need_dentry_mark(struct f2fs_sb_info *sbi, nid_t nid);
bool f2fs_is_checkpointed_node(struct f2fs_sb_info *sbi, nid_t nid);
bool f2fs_need_inode_block_update(struct f2fs_sb_info *sbi, nid_t ino);
-void f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
+int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
struct node_info *ni);
pgoff_t f2fs_get_next_page_offset(struct dnode_of_data *dn, pgoff_t pgofs);
int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode);
int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from);
int f2fs_truncate_xattr_node(struct inode *inode);
-int f2fs_wait_on_node_pages_writeback(struct f2fs_sb_info *sbi, nid_t ino);
+int f2fs_wait_on_node_pages_writeback(struct f2fs_sb_info *sbi,
+ unsigned int seq_id);
int f2fs_remove_inode_page(struct inode *inode);
struct page *f2fs_new_inode_page(struct inode *inode);
struct page *f2fs_new_node_page(struct dnode_of_data *dn, unsigned int ofs);
@@ -2835,11 +2900,12 @@
struct page *f2fs_get_node_page_ra(struct page *parent, int start);
void f2fs_move_node_page(struct page *node_page, int gc_type);
int f2fs_fsync_node_pages(struct f2fs_sb_info *sbi, struct inode *inode,
- struct writeback_control *wbc, bool atomic);
+ struct writeback_control *wbc, bool atomic,
+ unsigned int *seq_id);
int f2fs_sync_node_pages(struct f2fs_sb_info *sbi,
struct writeback_control *wbc,
bool do_balance, enum iostat_type io_type);
-void f2fs_build_free_nids(struct f2fs_sb_info *sbi, bool sync, bool mount);
+int f2fs_build_free_nids(struct f2fs_sb_info *sbi, bool sync, bool mount);
bool f2fs_alloc_nid(struct f2fs_sb_info *sbi, nid_t *nid);
void f2fs_alloc_nid_done(struct f2fs_sb_info *sbi, nid_t nid);
void f2fs_alloc_nid_failed(struct f2fs_sb_info *sbi, nid_t nid);
@@ -2847,7 +2913,7 @@
void f2fs_recover_inline_xattr(struct inode *inode, struct page *page);
int f2fs_recover_xattr_data(struct inode *inode, struct page *page);
int f2fs_recover_inode_page(struct f2fs_sb_info *sbi, struct page *page);
-void f2fs_restore_node_summary(struct f2fs_sb_info *sbi,
+int f2fs_restore_node_summary(struct f2fs_sb_info *sbi,
unsigned int segno, struct f2fs_summary_block *sum);
void f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc);
int f2fs_build_node_manager(struct f2fs_sb_info *sbi);
@@ -2925,9 +2991,10 @@
void f2fs_stop_checkpoint(struct f2fs_sb_info *sbi, bool end_io);
struct page *f2fs_grab_meta_page(struct f2fs_sb_info *sbi, pgoff_t index);
struct page *f2fs_get_meta_page(struct f2fs_sb_info *sbi, pgoff_t index);
+struct page *f2fs_get_meta_page_nofail(struct f2fs_sb_info *sbi, pgoff_t index);
struct page *f2fs_get_tmp_page(struct f2fs_sb_info *sbi, pgoff_t index);
-bool f2fs_is_valid_meta_blkaddr(struct f2fs_sb_info *sbi,
- block_t blkaddr, int type);
+bool f2fs_is_valid_blkaddr(struct f2fs_sb_info *sbi,
+ block_t blkaddr, int type);
int f2fs_ra_meta_pages(struct f2fs_sb_info *sbi, block_t start, int nrpages,
int type, bool sync);
void f2fs_ra_meta_pages_cond(struct f2fs_sb_info *sbi, pgoff_t index);
@@ -2951,6 +3018,7 @@
void f2fs_update_dirty_page(struct inode *inode, struct page *page);
void f2fs_remove_dirty_inode(struct inode *inode);
int f2fs_sync_dirty_inodes(struct f2fs_sb_info *sbi, enum inode_type type);
+void f2fs_wait_on_all_pages_writeback(struct f2fs_sb_info *sbi);
int f2fs_write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc);
void f2fs_init_ino_entry_info(struct f2fs_sb_info *sbi);
int __init f2fs_create_checkpoint_caches(void);
@@ -3389,7 +3457,7 @@
return (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode));
#else
- return 0;
+ return false;
#endif
}
@@ -3411,4 +3479,11 @@
fscrypt_using_hardware_encryption(inode));
}
+#ifdef CONFIG_F2FS_FAULT_INJECTION
+extern void f2fs_build_fault_attr(struct f2fs_sb_info *sbi, unsigned int rate,
+ unsigned int type);
+#else
+#define f2fs_build_fault_attr(sbi, rate, type) do { } while (0)
+#endif
+
#endif
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index 18f415a..4636b01 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -215,6 +215,7 @@
.nr_to_write = LONG_MAX,
.for_reclaim = 0,
};
+ unsigned int seq_id = 0;
if (unlikely(f2fs_readonly(inode->i_sb)))
return 0;
@@ -277,7 +278,7 @@
}
sync_nodes:
atomic_inc(&sbi->wb_sync_req[NODE]);
- ret = f2fs_fsync_node_pages(sbi, inode, &wbc, atomic);
+ ret = f2fs_fsync_node_pages(sbi, inode, &wbc, atomic, &seq_id);
atomic_dec(&sbi->wb_sync_req[NODE]);
if (ret)
goto out;
@@ -303,7 +304,7 @@
* given fsync mark.
*/
if (!atomic) {
- ret = f2fs_wait_on_node_pages_writeback(sbi, ino);
+ ret = f2fs_wait_on_node_pages_writeback(sbi, seq_id);
if (ret)
goto out;
}
@@ -352,13 +353,13 @@
return pgofs;
}
-static bool __found_offset(block_t blkaddr, pgoff_t dirty, pgoff_t pgofs,
- int whence)
+static bool __found_offset(struct f2fs_sb_info *sbi, block_t blkaddr,
+ pgoff_t dirty, pgoff_t pgofs, int whence)
{
switch (whence) {
case SEEK_DATA:
if ((blkaddr == NEW_ADDR && dirty == pgofs) ||
- is_valid_blkaddr(blkaddr))
+ is_valid_data_blkaddr(sbi, blkaddr))
return true;
break;
case SEEK_HOLE:
@@ -422,7 +423,15 @@
blkaddr = datablock_addr(dn.inode,
dn.node_page, dn.ofs_in_node);
- if (__found_offset(blkaddr, dirty, pgofs, whence)) {
+ if (__is_valid_data_blkaddr(blkaddr) &&
+ !f2fs_is_valid_blkaddr(F2FS_I_SB(inode),
+ blkaddr, DATA_GENERIC)) {
+ f2fs_put_dnode(&dn);
+ goto fail;
+ }
+
+ if (__found_offset(F2FS_I_SB(inode), blkaddr, dirty,
+ pgofs, whence)) {
f2fs_put_dnode(&dn);
goto found;
}
@@ -515,6 +524,11 @@
dn->data_blkaddr = NULL_ADDR;
f2fs_set_data_blkaddr(dn);
+
+ if (__is_valid_data_blkaddr(blkaddr) &&
+ !f2fs_is_valid_blkaddr(sbi, blkaddr, DATA_GENERIC))
+ continue;
+
f2fs_invalidate_blocks(sbi, blkaddr);
if (dn->ofs_in_node == 0 && IS_INODE(dn->node_page))
clear_inode_flag(dn->inode, FI_FIRST_BLOCK_WRITTEN);
@@ -656,12 +670,11 @@
trace_f2fs_truncate(inode);
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(F2FS_I_SB(inode), FAULT_TRUNCATE)) {
f2fs_show_injection_info(FAULT_TRUNCATE);
return -EIO;
}
-#endif
+
/* we should check inline_data size */
if (!f2fs_may_inline_data(inode)) {
err = f2fs_convert_inline_inode(inode);
@@ -785,22 +798,26 @@
}
if (attr->ia_valid & ATTR_SIZE) {
- if (attr->ia_size <= i_size_read(inode)) {
- down_write(&F2FS_I(inode)->i_mmap_sem);
- truncate_setsize(inode, attr->ia_size);
- err = f2fs_truncate(inode);
- up_write(&F2FS_I(inode)->i_mmap_sem);
- if (err)
- return err;
- } else {
- /*
- * do not trim all blocks after i_size if target size is
- * larger than i_size.
- */
- down_write(&F2FS_I(inode)->i_mmap_sem);
- truncate_setsize(inode, attr->ia_size);
- up_write(&F2FS_I(inode)->i_mmap_sem);
+ bool to_smaller = (attr->ia_size <= i_size_read(inode));
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ down_write(&F2FS_I(inode)->i_mmap_sem);
+
+ truncate_setsize(inode, attr->ia_size);
+
+ if (to_smaller)
+ err = f2fs_truncate(inode);
+ /*
+ * do not trim all blocks after i_size if target size is
+ * larger than i_size.
+ */
+ up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+
+ if (err)
+ return err;
+
+ if (!to_smaller) {
/* should convert inline inode here */
if (!f2fs_may_inline_data(inode)) {
err = f2fs_convert_inline_inode(inode);
@@ -947,14 +964,19 @@
blk_start = (loff_t)pg_start << PAGE_SHIFT;
blk_end = (loff_t)pg_end << PAGE_SHIFT;
+
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
down_write(&F2FS_I(inode)->i_mmap_sem);
+
truncate_inode_pages_range(mapping, blk_start,
blk_end - 1);
f2fs_lock_op(sbi);
ret = f2fs_truncate_hole(inode, pg_start, pg_end);
f2fs_unlock_op(sbi);
+
up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
}
}
@@ -1057,7 +1079,12 @@
if (ret)
return ret;
- f2fs_get_node_info(sbi, dn.nid, &ni);
+ ret = f2fs_get_node_info(sbi, dn.nid, &ni);
+ if (ret) {
+ f2fs_put_dnode(&dn);
+ return ret;
+ }
+
ilen = min((pgoff_t)
ADDRS_PER_PAGE(dn.node_page, dst_inode) -
dn.ofs_in_node, len - i);
@@ -1164,25 +1191,33 @@
return ret;
}
-static int f2fs_do_collapse(struct inode *inode, pgoff_t start, pgoff_t end)
+static int f2fs_do_collapse(struct inode *inode, loff_t offset, loff_t len)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
pgoff_t nrpages = (i_size_read(inode) + PAGE_SIZE - 1) / PAGE_SIZE;
+ pgoff_t start = offset >> PAGE_SHIFT;
+ pgoff_t end = (offset + len) >> PAGE_SHIFT;
int ret;
f2fs_balance_fs(sbi, true);
+
+ /* avoid gc operation during block exchange */
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ down_write(&F2FS_I(inode)->i_mmap_sem);
+
f2fs_lock_op(sbi);
-
f2fs_drop_extent_tree(inode);
-
+ truncate_pagecache(inode, offset);
ret = __exchange_data_block(inode, inode, end, start, nrpages - end, true);
f2fs_unlock_op(sbi);
+
+ up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
return ret;
}
static int f2fs_collapse_range(struct inode *inode, loff_t offset, loff_t len)
{
- pgoff_t pg_start, pg_end;
loff_t new_size;
int ret;
@@ -1197,25 +1232,17 @@
if (ret)
return ret;
- pg_start = offset >> PAGE_SHIFT;
- pg_end = (offset + len) >> PAGE_SHIFT;
-
- /* avoid gc operation during block exchange */
- down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
-
- down_write(&F2FS_I(inode)->i_mmap_sem);
/* write out all dirty pages from offset */
ret = filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
if (ret)
- goto out_unlock;
+ return ret;
- truncate_pagecache(inode, offset);
-
- ret = f2fs_do_collapse(inode, pg_start, pg_end);
+ ret = f2fs_do_collapse(inode, offset, len);
if (ret)
- goto out_unlock;
+ return ret;
/* write out all moved pages, if possible */
+ down_write(&F2FS_I(inode)->i_mmap_sem);
filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
truncate_pagecache(inode, offset);
@@ -1223,11 +1250,9 @@
truncate_pagecache(inode, new_size);
ret = f2fs_truncate_blocks(inode, new_size, true);
+ up_write(&F2FS_I(inode)->i_mmap_sem);
if (!ret)
f2fs_i_size_write(inode, new_size);
-out_unlock:
- up_write(&F2FS_I(inode)->i_mmap_sem);
- up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
return ret;
}
@@ -1293,12 +1318,9 @@
if (ret)
return ret;
- down_write(&F2FS_I(inode)->i_mmap_sem);
ret = filemap_write_and_wait_range(mapping, offset, offset + len - 1);
if (ret)
- goto out_sem;
-
- truncate_pagecache_range(inode, offset, offset + len - 1);
+ return ret;
pg_start = ((unsigned long long) offset) >> PAGE_SHIFT;
pg_end = ((unsigned long long) offset + len) >> PAGE_SHIFT;
@@ -1310,7 +1332,7 @@
ret = fill_zero(inode, pg_start, off_start,
off_end - off_start);
if (ret)
- goto out_sem;
+ return ret;
new_size = max_t(loff_t, new_size, offset + len);
} else {
@@ -1318,7 +1340,7 @@
ret = fill_zero(inode, pg_start++, off_start,
PAGE_SIZE - off_start);
if (ret)
- goto out_sem;
+ return ret;
new_size = max_t(loff_t, new_size,
(loff_t)pg_start << PAGE_SHIFT);
@@ -1329,12 +1351,21 @@
unsigned int end_offset;
pgoff_t end;
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ down_write(&F2FS_I(inode)->i_mmap_sem);
+
+ truncate_pagecache_range(inode,
+ (loff_t)index << PAGE_SHIFT,
+ ((loff_t)pg_end << PAGE_SHIFT) - 1);
+
f2fs_lock_op(sbi);
set_new_dnode(&dn, inode, NULL, NULL, 0);
ret = f2fs_get_dnode_of_data(&dn, index, ALLOC_NODE);
if (ret) {
f2fs_unlock_op(sbi);
+ up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
goto out;
}
@@ -1343,7 +1374,10 @@
ret = f2fs_do_zero_range(&dn, index, end);
f2fs_put_dnode(&dn);
+
f2fs_unlock_op(sbi);
+ up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
f2fs_balance_fs(sbi, dn.node_changed);
@@ -1371,9 +1405,6 @@
else
f2fs_i_size_write(inode, new_size);
}
-out_sem:
- up_write(&F2FS_I(inode)->i_mmap_sem);
-
return ret;
}
@@ -1402,26 +1433,27 @@
f2fs_balance_fs(sbi, true);
- /* avoid gc operation during block exchange */
- down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
-
down_write(&F2FS_I(inode)->i_mmap_sem);
ret = f2fs_truncate_blocks(inode, i_size_read(inode), true);
+ up_write(&F2FS_I(inode)->i_mmap_sem);
if (ret)
- goto out;
+ return ret;
/* write out all dirty pages from offset */
ret = filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
if (ret)
- goto out;
-
- truncate_pagecache(inode, offset);
+ return ret;
pg_start = offset >> PAGE_SHIFT;
pg_end = (offset + len) >> PAGE_SHIFT;
delta = pg_end - pg_start;
idx = (i_size_read(inode) + PAGE_SIZE - 1) / PAGE_SIZE;
+ /* avoid gc operation during block exchange */
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ down_write(&F2FS_I(inode)->i_mmap_sem);
+ truncate_pagecache(inode, offset);
+
while (!ret && idx > pg_start) {
nr = idx - pg_start;
if (nr > delta)
@@ -1435,16 +1467,17 @@
idx + delta, nr, false);
f2fs_unlock_op(sbi);
}
+ up_write(&F2FS_I(inode)->i_mmap_sem);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
/* write out all moved pages, if possible */
+ down_write(&F2FS_I(inode)->i_mmap_sem);
filemap_write_and_wait_range(inode->i_mapping, offset, LLONG_MAX);
truncate_pagecache(inode, offset);
+ up_write(&F2FS_I(inode)->i_mmap_sem);
if (!ret)
f2fs_i_size_write(inode, new_size);
-out:
- up_write(&F2FS_I(inode)->i_mmap_sem);
- up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
return ret;
}
@@ -1600,7 +1633,7 @@
struct f2fs_inode_info *fi = F2FS_I(inode);
unsigned int flags = fi->i_flags;
- if (file_is_encrypt(inode))
+ if (f2fs_encrypted_inode(inode))
flags |= F2FS_ENCRYPT_FL;
if (f2fs_has_inline_data(inode) || f2fs_has_inline_dentry(inode))
flags |= F2FS_INLINE_DATA_FL;
@@ -1684,15 +1717,18 @@
inode_lock(inode);
- down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
-
- if (f2fs_is_atomic_file(inode))
+ if (f2fs_is_atomic_file(inode)) {
+ if (is_inode_flag_set(inode, FI_ATOMIC_REVOKE_REQUEST))
+ ret = -EINVAL;
goto out;
+ }
ret = f2fs_convert_inline_inode(inode);
if (ret)
goto out;
+ down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+
if (!get_dirty_pages(inode))
goto skip_flush;
@@ -1700,18 +1736,20 @@
"Unexpected flush for atomic writes: ino=%lu, npages=%u",
inode->i_ino, get_dirty_pages(inode));
ret = filemap_write_and_wait_range(inode->i_mapping, 0, LLONG_MAX);
- if (ret)
+ if (ret) {
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
goto out;
+ }
skip_flush:
set_inode_flag(inode, FI_ATOMIC_FILE);
clear_inode_flag(inode, FI_ATOMIC_REVOKE_REQUEST);
- f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ f2fs_update_time(F2FS_I_SB(inode), REQ_TIME);
F2FS_I(inode)->inmem_task = current;
stat_inc_atomic_write(inode);
stat_update_max_atomic_write(inode);
out:
- up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
inode_unlock(inode);
mnt_drop_write_file(filp);
return ret;
@@ -1729,9 +1767,9 @@
if (ret)
return ret;
- inode_lock(inode);
+ f2fs_balance_fs(F2FS_I_SB(inode), true);
- down_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ inode_lock(inode);
if (f2fs_is_volatile_file(inode)) {
ret = -EINVAL;
@@ -1757,7 +1795,6 @@
clear_inode_flag(inode, FI_ATOMIC_REVOKE_REQUEST);
ret = -EINVAL;
}
- up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
inode_unlock(inode);
mnt_drop_write_file(filp);
return ret;
@@ -1849,6 +1886,8 @@
ret = f2fs_do_sync_file(filp, 0, LLONG_MAX, 0, true);
}
+ clear_inode_flag(inode, FI_ATOMIC_REVOKE_REQUEST);
+
inode_unlock(inode);
mnt_drop_write_file(filp);
@@ -1885,6 +1924,7 @@
}
if (sb) {
f2fs_stop_checkpoint(sbi, false);
+ set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
thaw_bdev(sb->s_bdev, sb);
}
break;
@@ -1894,13 +1934,16 @@
if (ret)
goto out;
f2fs_stop_checkpoint(sbi, false);
+ set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
break;
case F2FS_GOING_DOWN_NOSYNC:
f2fs_stop_checkpoint(sbi, false);
+ set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
break;
case F2FS_GOING_DOWN_METAFLUSH:
f2fs_sync_meta_pages(sbi, META, LONG_MAX, FS_META_IO);
f2fs_stop_checkpoint(sbi, false);
+ set_sbi_flag(sbi, SBI_IS_SHUTDOWN);
break;
default:
ret = -EINVAL;
@@ -2103,7 +2146,7 @@
return ret;
}
-static int f2fs_ioc_f2fs_write_checkpoint(struct file *filp, unsigned long arg)
+static int f2fs_ioc_write_checkpoint(struct file *filp, unsigned long arg)
{
struct inode *inode = file_inode(filp);
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
@@ -2347,15 +2390,10 @@
}
inode_lock(src);
- down_write(&F2FS_I(src)->i_gc_rwsem[WRITE]);
if (src != dst) {
ret = -EBUSY;
if (!inode_trylock(dst))
goto out;
- if (!down_write_trylock(&F2FS_I(dst)->i_gc_rwsem[WRITE])) {
- inode_unlock(dst);
- goto out;
- }
}
ret = -EINVAL;
@@ -2400,6 +2438,14 @@
goto out_unlock;
f2fs_balance_fs(sbi, true);
+
+ down_write(&F2FS_I(src)->i_gc_rwsem[WRITE]);
+ if (src != dst) {
+ ret = -EBUSY;
+ if (!down_write_trylock(&F2FS_I(dst)->i_gc_rwsem[WRITE]))
+ goto out_src;
+ }
+
f2fs_lock_op(sbi);
ret = __exchange_data_block(src, dst, pos_in >> F2FS_BLKSIZE_BITS,
pos_out >> F2FS_BLKSIZE_BITS,
@@ -2412,13 +2458,15 @@
f2fs_i_size_write(dst, dst_osize);
}
f2fs_unlock_op(sbi);
-out_unlock:
- if (src != dst) {
+
+ if (src != dst)
up_write(&F2FS_I(dst)->i_gc_rwsem[WRITE]);
- inode_unlock(dst);
- }
-out:
+out_src:
up_write(&F2FS_I(src)->i_gc_rwsem[WRITE]);
+out_unlock:
+ if (src != dst)
+ inode_unlock(dst);
+out:
inode_unlock(src);
return ret;
}
@@ -2590,7 +2638,7 @@
if (!pin) {
clear_inode_flag(inode, FI_PIN_FILE);
- F2FS_I(inode)->i_gc_failures[GC_FAILURE_PIN] = 1;
+ f2fs_i_gc_failures_write(inode, 0);
goto done;
}
@@ -2696,7 +2744,7 @@
case F2FS_IOC_GARBAGE_COLLECT_RANGE:
return f2fs_ioc_gc_range(filp, arg);
case F2FS_IOC_WRITE_CHECKPOINT:
- return f2fs_ioc_f2fs_write_checkpoint(filp, arg);
+ return f2fs_ioc_write_checkpoint(filp, arg);
case F2FS_IOC_DEFRAGMENT:
return f2fs_ioc_defragment(filp, arg);
case F2FS_IOC_MOVE_RANGE:
diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index 9be5b50..c6322ef 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -53,12 +53,10 @@
continue;
}
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_CHECKPOINT)) {
f2fs_show_injection_info(FAULT_CHECKPOINT);
f2fs_stop_checkpoint(sbi, false);
}
-#endif
if (!sb_start_write_trylock(sbi->sb))
continue;
@@ -517,7 +515,11 @@
continue;
}
- f2fs_get_node_info(sbi, nid, &ni);
+ if (f2fs_get_node_info(sbi, nid, &ni)) {
+ f2fs_put_page(node_page, 1);
+ continue;
+ }
+
if (ni.blk_addr != start_addr + off) {
f2fs_put_page(node_page, 1);
continue;
@@ -576,7 +578,10 @@
if (IS_ERR(node_page))
return false;
- f2fs_get_node_info(sbi, nid, dni);
+ if (f2fs_get_node_info(sbi, nid, dni)) {
+ f2fs_put_page(node_page, 1);
+ return false;
+ }
if (sum->version != dni->version) {
f2fs_msg(sbi->sb, KERN_WARNING,
@@ -594,6 +599,72 @@
return true;
}
+static int ra_data_block(struct inode *inode, pgoff_t index)
+{
+ struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+ struct address_space *mapping = inode->i_mapping;
+ struct dnode_of_data dn;
+ struct page *page;
+ struct extent_info ei = {0, 0, 0};
+ struct f2fs_io_info fio = {
+ .sbi = sbi,
+ .ino = inode->i_ino,
+ .type = DATA,
+ .temp = COLD,
+ .op = REQ_OP_READ,
+ .op_flags = 0,
+ .encrypted_page = NULL,
+ .in_list = false,
+ .retry = false,
+ };
+ int err;
+
+ page = f2fs_grab_cache_page(mapping, index, true);
+ if (!page)
+ return -ENOMEM;
+
+ if (f2fs_lookup_extent_cache(inode, index, &ei)) {
+ dn.data_blkaddr = ei.blk + index - ei.fofs;
+ goto got_it;
+ }
+
+ set_new_dnode(&dn, inode, NULL, NULL, 0);
+ err = f2fs_get_dnode_of_data(&dn, index, LOOKUP_NODE);
+ if (err)
+ goto put_page;
+ f2fs_put_dnode(&dn);
+
+ if (unlikely(!f2fs_is_valid_blkaddr(sbi, dn.data_blkaddr,
+ DATA_GENERIC))) {
+ err = -EFAULT;
+ goto put_page;
+ }
+got_it:
+ /* read page */
+ fio.page = page;
+ fio.new_blkaddr = fio.old_blkaddr = dn.data_blkaddr;
+
+ fio.encrypted_page = f2fs_pagecache_get_page(META_MAPPING(sbi),
+ dn.data_blkaddr,
+ FGP_LOCK | FGP_CREAT, GFP_NOFS);
+ if (!fio.encrypted_page) {
+ err = -ENOMEM;
+ goto put_page;
+ }
+
+ err = f2fs_submit_page_bio(&fio);
+ if (err)
+ goto put_encrypted_page;
+ f2fs_put_page(fio.encrypted_page, 0);
+ f2fs_put_page(page, 1);
+ return 0;
+put_encrypted_page:
+ f2fs_put_page(fio.encrypted_page, 1);
+put_page:
+ f2fs_put_page(page, 1);
+ return err;
+}
+
/*
* Move data block via META_MAPPING while keeping locked data page.
* This can be used to move blocks, aka LBAs, directly on disk.
@@ -615,7 +686,7 @@
struct dnode_of_data dn;
struct f2fs_summary sum;
struct node_info ni;
- struct page *page;
+ struct page *page, *mpage;
block_t newaddr;
int err;
bool lfs_mode = test_opt(fio.sbi, LFS);
@@ -655,7 +726,10 @@
*/
f2fs_wait_on_page_writeback(page, DATA, true);
- f2fs_get_node_info(fio.sbi, dn.nid, &ni);
+ err = f2fs_get_node_info(fio.sbi, dn.nid, &ni);
+ if (err)
+ goto put_out;
+
set_summary(&sum, dn.nid, dn.ofs_in_node, ni.version);
/* read page */
@@ -675,6 +749,23 @@
goto recover_block;
}
+ mpage = f2fs_pagecache_get_page(META_MAPPING(fio.sbi),
+ fio.old_blkaddr, FGP_LOCK, GFP_NOFS);
+ if (mpage) {
+ bool updated = false;
+
+ if (PageUptodate(mpage)) {
+ memcpy(page_address(fio.encrypted_page),
+ page_address(mpage), PAGE_SIZE);
+ updated = true;
+ }
+ f2fs_put_page(mpage, 1);
+ invalidate_mapping_pages(META_MAPPING(fio.sbi),
+ fio.old_blkaddr, fio.old_blkaddr);
+ if (updated)
+ goto write_page;
+ }
+
err = f2fs_submit_page_bio(&fio);
if (err)
goto put_page_out;
@@ -691,6 +782,7 @@
goto put_page_out;
}
+write_page:
set_page_dirty(fio.encrypted_page);
f2fs_wait_on_page_writeback(fio.encrypted_page, DATA, true);
if (clear_page_dirty_for_io(fio.encrypted_page))
@@ -865,22 +957,30 @@
if (IS_ERR(inode) || is_bad_inode(inode))
continue;
- /* if inode uses special I/O path, let's go phase 3 */
+ if (!down_write_trylock(
+ &F2FS_I(inode)->i_gc_rwsem[WRITE])) {
+ iput(inode);
+ sbi->skipped_gc_rwsem++;
+ continue;
+ }
+
+ start_bidx = f2fs_start_bidx_of_node(nofs, inode) +
+ ofs_in_node;
+
if (f2fs_post_read_required(inode)) {
+ int err = ra_data_block(inode, start_bidx);
+
+ up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
+ if (err) {
+ iput(inode);
+ continue;
+ }
add_gc_inode(gc_list, inode);
continue;
}
- if (!down_write_trylock(
- &F2FS_I(inode)->i_gc_rwsem[WRITE])) {
- iput(inode);
- continue;
- }
-
- start_bidx = f2fs_start_bidx_of_node(nofs, inode);
data_page = f2fs_get_read_data_page(inode,
- start_bidx + ofs_in_node, REQ_RAHEAD,
- true);
+ start_bidx, REQ_RAHEAD, true);
up_write(&F2FS_I(inode)->i_gc_rwsem[WRITE]);
if (IS_ERR(data_page)) {
iput(inode);
@@ -903,6 +1003,7 @@
continue;
if (!down_write_trylock(
&fi->i_gc_rwsem[WRITE])) {
+ sbi->skipped_gc_rwsem++;
up_write(&fi->i_gc_rwsem[READ]);
continue;
}
@@ -1040,6 +1141,7 @@
.iroot = RADIX_TREE_INIT(GFP_NOFS),
};
unsigned long long last_skipped = sbi->skipped_atomic_files[FG_GC];
+ unsigned long long first_skipped;
unsigned int skipped_round = 0, round = 0;
trace_f2fs_gc_begin(sbi->sb, sync, background,
@@ -1052,6 +1154,8 @@
prefree_segments(sbi));
cpc.reason = __get_cp_reason(sbi);
+ sbi->skipped_gc_rwsem = 0;
+ first_skipped = last_skipped;
gc_more:
if (unlikely(!(sbi->sb->s_flags & MS_ACTIVE))) {
ret = -EINVAL;
@@ -1093,7 +1197,8 @@
total_freed += seg_freed;
if (gc_type == FG_GC) {
- if (sbi->skipped_atomic_files[FG_GC] > last_skipped)
+ if (sbi->skipped_atomic_files[FG_GC] > last_skipped ||
+ sbi->skipped_gc_rwsem)
skipped_round++;
last_skipped = sbi->skipped_atomic_files[FG_GC];
round++;
@@ -1102,15 +1207,23 @@
if (gc_type == FG_GC)
sbi->cur_victim_sec = NULL_SEGNO;
- if (!sync) {
- if (has_not_enough_free_secs(sbi, sec_freed, 0)) {
- if (skipped_round > MAX_SKIP_ATOMIC_COUNT &&
- skipped_round * 2 >= round)
- f2fs_drop_inmem_pages_all(sbi, true);
+ if (sync)
+ goto stop;
+
+ if (has_not_enough_free_secs(sbi, sec_freed, 0)) {
+ if (skipped_round <= MAX_SKIP_GC_COUNT ||
+ skipped_round * 2 < round) {
segno = NULL_SEGNO;
goto gc_more;
}
+ if (first_skipped < last_skipped &&
+ (last_skipped - first_skipped) >
+ sbi->skipped_gc_rwsem) {
+ f2fs_drop_inmem_pages_all(sbi, true);
+ segno = NULL_SEGNO;
+ goto gc_more;
+ }
if (gc_type == FG_GC)
ret = f2fs_write_checkpoint(sbi, &cpc);
}
diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c
index dd4cde8..df71d26 100644
--- a/fs/f2fs/inline.c
+++ b/fs/f2fs/inline.c
@@ -139,6 +139,7 @@
.encrypted_page = NULL,
.io_type = FS_DATA_IO,
};
+ struct node_info ni;
int dirty, err;
if (!f2fs_exist_data(dn->inode))
@@ -148,6 +149,14 @@
if (err)
return err;
+ err = f2fs_get_node_info(fio.sbi, dn->nid, &ni);
+ if (err) {
+ f2fs_put_dnode(dn);
+ return err;
+ }
+
+ fio.version = ni.version;
+
if (unlikely(dn->data_blkaddr != NEW_ADDR)) {
f2fs_put_dnode(dn);
set_sbi_flag(fio.sbi, SBI_NEED_FSCK);
@@ -516,6 +525,7 @@
return 0;
recover:
lock_page(ipage);
+ f2fs_wait_on_page_writeback(ipage, NODE, true);
memcpy(inline_dentry, backup_dentry, MAX_INLINE_DATA(dir));
f2fs_i_depth_write(dir, 0);
f2fs_i_size_write(dir, MAX_INLINE_DATA(dir));
@@ -707,7 +717,10 @@
ilen = start + len;
ilen -= start;
- f2fs_get_node_info(F2FS_I_SB(inode), inode->i_ino, &ni);
+ err = f2fs_get_node_info(F2FS_I_SB(inode), inode->i_ino, &ni);
+ if (err)
+ goto out;
+
byteaddr = (__u64)ni.blk_addr << inode->i_sb->s_blocksize_bits;
byteaddr += (char *)inline_data_addr(inode, ipage) -
(char *)F2FS_INODE(ipage);
diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
index 30a7773..959df22 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -68,13 +68,16 @@
}
}
-static bool __written_first_block(struct f2fs_inode *ri)
+static int __written_first_block(struct f2fs_sb_info *sbi,
+ struct f2fs_inode *ri)
{
block_t addr = le32_to_cpu(ri->i_addr[offset_in_addr(ri)]);
- if (is_valid_blkaddr(addr))
- return true;
- return false;
+ if (!__is_valid_data_blkaddr(addr))
+ return 1;
+ if (!f2fs_is_valid_blkaddr(sbi, addr, DATA_GENERIC))
+ return -EFAULT;
+ return 0;
}
static void __set_inode_rdev(struct inode *inode, struct f2fs_inode *ri)
@@ -121,7 +124,7 @@
if (!f2fs_sb_has_inode_chksum(sbi->sb))
return false;
- if (!RAW_IS_INODE(F2FS_NODE(page)) || !(ri->i_inline & F2FS_EXTRA_ATTR))
+ if (!IS_INODE(page) || !(ri->i_inline & F2FS_EXTRA_ATTR))
return false;
if (!F2FS_FITS_IN_INODE(ri, le16_to_cpu(ri->i_extra_isize),
@@ -159,8 +162,15 @@
struct f2fs_inode *ri;
__u32 provided, calculated;
+ if (unlikely(is_sbi_flag_set(sbi, SBI_IS_SHUTDOWN)))
+ return true;
+
+#ifdef CONFIG_F2FS_CHECK_FS
+ if (!f2fs_enable_inode_chksum(sbi, page))
+#else
if (!f2fs_enable_inode_chksum(sbi, page) ||
PageDirty(page) || PageWriteback(page))
+#endif
return true;
ri = &F2FS_NODE(page)->i;
@@ -185,9 +195,31 @@
ri->i_inode_checksum = cpu_to_le32(f2fs_inode_chksum(sbi, page));
}
-static bool sanity_check_inode(struct inode *inode)
+static bool sanity_check_inode(struct inode *inode, struct page *node_page)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
+ struct f2fs_inode_info *fi = F2FS_I(inode);
+ unsigned long long iblocks;
+
+ iblocks = le64_to_cpu(F2FS_INODE(node_page)->i_blocks);
+ if (!iblocks) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: corrupted inode i_blocks i_ino=%lx iblocks=%llu, "
+ "run fsck to fix.",
+ __func__, inode->i_ino, iblocks);
+ return false;
+ }
+
+ if (ino_of_node(node_page) != nid_of_node(node_page)) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: corrupted inode footer i_ino=%lx, ino,nid: "
+ "[%u, %u] run fsck to fix.",
+ __func__, inode->i_ino,
+ ino_of_node(node_page), nid_of_node(node_page));
+ return false;
+ }
if (f2fs_sb_has_flexible_inline_xattr(sbi->sb)
&& !f2fs_has_extra_attr(inode)) {
@@ -197,6 +229,64 @@
__func__, inode->i_ino);
return false;
}
+
+ if (f2fs_has_extra_attr(inode) &&
+ !f2fs_sb_has_extra_attr(sbi->sb)) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: inode (ino=%lx) is with extra_attr, "
+ "but extra_attr feature is off",
+ __func__, inode->i_ino);
+ return false;
+ }
+
+ if (fi->i_extra_isize > F2FS_TOTAL_EXTRA_ATTR_SIZE ||
+ fi->i_extra_isize % sizeof(__le32)) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: inode (ino=%lx) has corrupted i_extra_isize: %d, "
+ "max: %zu",
+ __func__, inode->i_ino, fi->i_extra_isize,
+ F2FS_TOTAL_EXTRA_ATTR_SIZE);
+ return false;
+ }
+
+ if (F2FS_I(inode)->extent_tree) {
+ struct extent_info *ei = &F2FS_I(inode)->extent_tree->largest;
+
+ if (ei->len &&
+ (!f2fs_is_valid_blkaddr(sbi, ei->blk, DATA_GENERIC) ||
+ !f2fs_is_valid_blkaddr(sbi, ei->blk + ei->len - 1,
+ DATA_GENERIC))) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: inode (ino=%lx) extent info [%u, %u, %u] "
+ "is incorrect, run fsck to fix",
+ __func__, inode->i_ino,
+ ei->blk, ei->fofs, ei->len);
+ return false;
+ }
+ }
+
+ if (f2fs_has_inline_data(inode) &&
+ (!S_ISREG(inode->i_mode) && !S_ISLNK(inode->i_mode))) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: inode (ino=%lx, mode=%u) should not have "
+ "inline_data, run fsck to fix",
+ __func__, inode->i_ino, inode->i_mode);
+ return false;
+ }
+
+ if (f2fs_has_inline_dentry(inode) && !S_ISDIR(inode->i_mode)) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "%s: inode (ino=%lx, mode=%u) should not have "
+ "inline_dentry, run fsck to fix",
+ __func__, inode->i_ino, inode->i_mode);
+ return false;
+ }
+
return true;
}
@@ -207,6 +297,7 @@
struct page *node_page;
struct f2fs_inode *ri;
projid_t i_projid;
+ int err;
/* Check if ino is within scope */
if (f2fs_check_nid_range(sbi, inode->i_ino))
@@ -268,6 +359,11 @@
fi->i_inline_xattr_size = 0;
}
+ if (!sanity_check_inode(inode, node_page)) {
+ f2fs_put_page(node_page, 1);
+ return -EINVAL;
+ }
+
/* check data exist */
if (f2fs_has_inline_data(inode) && !f2fs_exist_data(inode))
__recover_inline_status(inode, node_page);
@@ -275,8 +371,15 @@
/* get rdev by using inline_info */
__get_inode_rdev(inode, ri);
- if (__written_first_block(ri))
- set_inode_flag(inode, FI_FIRST_BLOCK_WRITTEN);
+ if (S_ISREG(inode->i_mode)) {
+ err = __written_first_block(sbi, ri);
+ if (err < 0) {
+ f2fs_put_page(node_page, 1);
+ return err;
+ }
+ if (!err)
+ set_inode_flag(inode, FI_FIRST_BLOCK_WRITTEN);
+ }
if (!f2fs_need_inode_block_update(sbi, inode->i_ino))
fi->last_disk_size = inode->i_size;
@@ -330,10 +433,6 @@
ret = do_read_inode(inode);
if (ret)
goto bad_inode;
- if (!sanity_check_inode(inode)) {
- ret = -EINVAL;
- goto bad_inode;
- }
make_now:
if (ino == F2FS_NODE_INO(sbi)) {
inode->i_mapping->a_ops = &f2fs_node_aops;
@@ -474,6 +573,10 @@
F2FS_I(inode)->i_disk_time[1] = inode->i_ctime;
F2FS_I(inode)->i_disk_time[2] = inode->i_mtime;
F2FS_I(inode)->i_disk_time[3] = F2FS_I(inode)->i_crtime;
+
+#ifdef CONFIG_F2FS_CHECK_FS
+ f2fs_inode_chksum_set(F2FS_I_SB(inode), node_page);
+#endif
}
void f2fs_update_inode_page(struct inode *inode)
@@ -558,12 +661,11 @@
if (F2FS_HAS_BLOCKS(inode))
err = f2fs_truncate(inode);
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_EVICT_INODE)) {
f2fs_show_injection_info(FAULT_EVICT_INODE);
err = -EIO;
}
-#endif
+
if (!err) {
f2fs_lock_op(sbi);
err = f2fs_remove_inode_page(inode);
@@ -626,6 +728,7 @@
{
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
struct node_info ni;
+ int err;
/*
* clear nlink of inode in order to release resource of inode
@@ -648,10 +751,16 @@
* so we can prevent losing this orphan when encoutering checkpoint
* and following suddenly power-off.
*/
- f2fs_get_node_info(sbi, inode->i_ino, &ni);
+ err = f2fs_get_node_info(sbi, inode->i_ino, &ni);
+ if (err) {
+ set_sbi_flag(sbi, SBI_NEED_FSCK);
+ f2fs_msg(sbi->sb, KERN_WARNING,
+ "May loss orphan inode, run fsck to fix.");
+ goto out;
+ }
if (ni.blk_addr != NULL_ADDR) {
- int err = f2fs_acquire_orphan_inode(sbi);
+ err = f2fs_acquire_orphan_inode(sbi);
if (err) {
set_sbi_flag(sbi, SBI_NEED_FSCK);
f2fs_msg(sbi->sb, KERN_WARNING,
@@ -664,6 +773,7 @@
set_inode_flag(inode, FI_FREE_NID);
}
+out:
f2fs_unlock_op(sbi);
/* iput will drop the inode object */
diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c
index c53760e..56593b3 100644
--- a/fs/f2fs/namei.c
+++ b/fs/f2fs/namei.c
@@ -246,7 +246,7 @@
return -EINVAL;
if (hot) {
- strncpy(extlist[count], name, strlen(name));
+ memcpy(extlist[count], name, strlen(name));
sbi->raw_super->hot_ext_count = hot_count + 1;
} else {
char buf[F2FS_MAX_EXTENSION][F2FS_EXTENSION_LEN];
@@ -254,7 +254,7 @@
memcpy(buf, &extlist[cold_count],
F2FS_EXTENSION_LEN * hot_count);
memset(extlist[cold_count], 0, F2FS_EXTENSION_LEN);
- strncpy(extlist[cold_count], name, strlen(name));
+ memcpy(extlist[cold_count], name, strlen(name));
memcpy(&extlist[cold_count + 1], buf,
F2FS_EXTENSION_LEN * hot_count);
sbi->raw_super->extension_count = cpu_to_le32(cold_count + 1);
diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
index aab2dd2..f213a53 100644
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -28,6 +28,7 @@
static struct kmem_cache *nat_entry_slab;
static struct kmem_cache *free_nid_slab;
static struct kmem_cache *nat_entry_set_slab;
+static struct kmem_cache *fsync_node_entry_slab;
/*
* Check whether the given nid is within node id range.
@@ -112,25 +113,22 @@
static struct page *get_current_nat_page(struct f2fs_sb_info *sbi, nid_t nid)
{
- pgoff_t index = current_nat_addr(sbi, nid);
- return f2fs_get_meta_page(sbi, index);
+ return f2fs_get_meta_page_nofail(sbi, current_nat_addr(sbi, nid));
}
static struct page *get_next_nat_page(struct f2fs_sb_info *sbi, nid_t nid)
{
struct page *src_page;
struct page *dst_page;
- pgoff_t src_off;
pgoff_t dst_off;
void *src_addr;
void *dst_addr;
struct f2fs_nm_info *nm_i = NM_I(sbi);
- src_off = current_nat_addr(sbi, nid);
- dst_off = next_nat_addr(sbi, src_off);
+ dst_off = next_nat_addr(sbi, current_nat_addr(sbi, nid));
/* get current nat block page with lock */
- src_page = f2fs_get_meta_page(sbi, src_off);
+ src_page = get_current_nat_page(sbi, nid);
dst_page = f2fs_grab_meta_page(sbi, dst_off);
f2fs_bug_on(sbi, PageDirty(src_page));
@@ -176,14 +174,30 @@
if (raw_ne)
node_info_from_raw_nat(&ne->ni, raw_ne);
+
+ spin_lock(&nm_i->nat_list_lock);
list_add_tail(&ne->list, &nm_i->nat_entries);
+ spin_unlock(&nm_i->nat_list_lock);
+
nm_i->nat_cnt++;
return ne;
}
static struct nat_entry *__lookup_nat_cache(struct f2fs_nm_info *nm_i, nid_t n)
{
- return radix_tree_lookup(&nm_i->nat_root, n);
+ struct nat_entry *ne;
+
+ ne = radix_tree_lookup(&nm_i->nat_root, n);
+
+ /* for recent accessed nat entry, move it to tail of lru list */
+ if (ne && !get_nat_flag(ne, IS_DIRTY)) {
+ spin_lock(&nm_i->nat_list_lock);
+ if (!list_empty(&ne->list))
+ list_move_tail(&ne->list, &nm_i->nat_entries);
+ spin_unlock(&nm_i->nat_list_lock);
+ }
+
+ return ne;
}
static unsigned int __gang_lookup_nat_cache(struct f2fs_nm_info *nm_i,
@@ -194,7 +208,6 @@
static void __del_from_nat_cache(struct f2fs_nm_info *nm_i, struct nat_entry *e)
{
- list_del(&e->list);
radix_tree_delete(&nm_i->nat_root, nat_get_nid(e));
nm_i->nat_cnt--;
__free_nat_entry(e);
@@ -245,16 +258,21 @@
nm_i->dirty_nat_cnt++;
set_nat_flag(ne, IS_DIRTY, true);
refresh_list:
+ spin_lock(&nm_i->nat_list_lock);
if (new_ne)
list_del_init(&ne->list);
else
list_move_tail(&ne->list, &head->entry_list);
+ spin_unlock(&nm_i->nat_list_lock);
}
static void __clear_nat_cache_dirty(struct f2fs_nm_info *nm_i,
struct nat_entry_set *set, struct nat_entry *ne)
{
+ spin_lock(&nm_i->nat_list_lock);
list_move_tail(&ne->list, &nm_i->nat_entries);
+ spin_unlock(&nm_i->nat_list_lock);
+
set_nat_flag(ne, IS_DIRTY, false);
set->entry_cnt--;
nm_i->dirty_nat_cnt--;
@@ -267,6 +285,72 @@
start, nr);
}
+bool f2fs_in_warm_node_list(struct f2fs_sb_info *sbi, struct page *page)
+{
+ return NODE_MAPPING(sbi) == page->mapping &&
+ IS_DNODE(page) && is_cold_node(page);
+}
+
+void f2fs_init_fsync_node_info(struct f2fs_sb_info *sbi)
+{
+ spin_lock_init(&sbi->fsync_node_lock);
+ INIT_LIST_HEAD(&sbi->fsync_node_list);
+ sbi->fsync_seg_id = 0;
+ sbi->fsync_node_num = 0;
+}
+
+static unsigned int f2fs_add_fsync_node_entry(struct f2fs_sb_info *sbi,
+ struct page *page)
+{
+ struct fsync_node_entry *fn;
+ unsigned long flags;
+ unsigned int seq_id;
+
+ fn = f2fs_kmem_cache_alloc(fsync_node_entry_slab, GFP_NOFS);
+
+ get_page(page);
+ fn->page = page;
+ INIT_LIST_HEAD(&fn->list);
+
+ spin_lock_irqsave(&sbi->fsync_node_lock, flags);
+ list_add_tail(&fn->list, &sbi->fsync_node_list);
+ fn->seq_id = sbi->fsync_seg_id++;
+ seq_id = fn->seq_id;
+ sbi->fsync_node_num++;
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+
+ return seq_id;
+}
+
+void f2fs_del_fsync_node_entry(struct f2fs_sb_info *sbi, struct page *page)
+{
+ struct fsync_node_entry *fn;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sbi->fsync_node_lock, flags);
+ list_for_each_entry(fn, &sbi->fsync_node_list, list) {
+ if (fn->page == page) {
+ list_del(&fn->list);
+ sbi->fsync_node_num--;
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+ kmem_cache_free(fsync_node_entry_slab, fn);
+ put_page(page);
+ return;
+ }
+ }
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+ f2fs_bug_on(sbi, 1);
+}
+
+void f2fs_reset_fsync_node_info(struct f2fs_sb_info *sbi)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&sbi->fsync_node_lock, flags);
+ sbi->fsync_seg_id = 0;
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+}
+
int f2fs_need_dentry_mark(struct f2fs_sb_info *sbi, nid_t nid)
{
struct f2fs_nm_info *nm_i = NM_I(sbi);
@@ -371,7 +455,7 @@
new_blkaddr == NULL_ADDR);
f2fs_bug_on(sbi, nat_get_blkaddr(e) == NEW_ADDR &&
new_blkaddr == NEW_ADDR);
- f2fs_bug_on(sbi, is_valid_blkaddr(nat_get_blkaddr(e)) &&
+ f2fs_bug_on(sbi, is_valid_data_blkaddr(sbi, nat_get_blkaddr(e)) &&
new_blkaddr == NEW_ADDR);
/* increment version no as node is removed */
@@ -382,7 +466,7 @@
/* change address */
nat_set_blkaddr(e, new_blkaddr);
- if (!is_valid_blkaddr(new_blkaddr))
+ if (!is_valid_data_blkaddr(sbi, new_blkaddr))
set_nat_flag(e, IS_CHECKPOINTED, false);
__set_nat_cache_dirty(nm_i, e);
@@ -405,13 +489,25 @@
if (!down_write_trylock(&nm_i->nat_tree_lock))
return 0;
- while (nr_shrink && !list_empty(&nm_i->nat_entries)) {
+ spin_lock(&nm_i->nat_list_lock);
+ while (nr_shrink) {
struct nat_entry *ne;
+
+ if (list_empty(&nm_i->nat_entries))
+ break;
+
ne = list_first_entry(&nm_i->nat_entries,
struct nat_entry, list);
+ list_del(&ne->list);
+ spin_unlock(&nm_i->nat_list_lock);
+
__del_from_nat_cache(nm_i, ne);
nr_shrink--;
+
+ spin_lock(&nm_i->nat_list_lock);
}
+ spin_unlock(&nm_i->nat_list_lock);
+
up_write(&nm_i->nat_tree_lock);
return nr - nr_shrink;
}
@@ -419,7 +515,7 @@
/*
* This function always returns success
*/
-void f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
+int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
struct node_info *ni)
{
struct f2fs_nm_info *nm_i = NM_I(sbi);
@@ -443,7 +539,7 @@
ni->blk_addr = nat_get_blkaddr(e);
ni->version = nat_get_version(e);
up_read(&nm_i->nat_tree_lock);
- return;
+ return 0;
}
memset(&ne, 0, sizeof(struct f2fs_nat_entry));
@@ -466,6 +562,9 @@
up_read(&nm_i->nat_tree_lock);
page = f2fs_get_meta_page(sbi, index);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
nat_blk = (struct f2fs_nat_block *)page_address(page);
ne = nat_blk->entries[nid - start_nid];
node_info_from_raw_nat(ni, &ne);
@@ -473,6 +572,7 @@
cache:
/* cache nat entry */
cache_nat_entry(sbi, nid, &ne);
+ return 0;
}
/*
@@ -722,12 +822,15 @@
return err;
}
-static void truncate_node(struct dnode_of_data *dn)
+static int truncate_node(struct dnode_of_data *dn)
{
struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
struct node_info ni;
+ int err;
- f2fs_get_node_info(sbi, dn->nid, &ni);
+ err = f2fs_get_node_info(sbi, dn->nid, &ni);
+ if (err)
+ return err;
/* Deallocate node address */
f2fs_invalidate_blocks(sbi, ni.blk_addr);
@@ -750,11 +853,14 @@
dn->node_page = NULL;
trace_f2fs_truncate_node(dn->inode, dn->nid, ni.blk_addr);
+
+ return 0;
}
static int truncate_dnode(struct dnode_of_data *dn)
{
struct page *page;
+ int err;
if (dn->nid == 0)
return 1;
@@ -770,7 +876,10 @@
dn->node_page = page;
dn->ofs_in_node = 0;
f2fs_truncate_data_blocks(dn);
- truncate_node(dn);
+ err = truncate_node(dn);
+ if (err)
+ return err;
+
return 1;
}
@@ -835,7 +944,9 @@
if (!ofs) {
/* remove current indirect node */
dn->node_page = page;
- truncate_node(dn);
+ ret = truncate_node(dn);
+ if (ret)
+ goto out_err;
freed++;
} else {
f2fs_put_page(page, 1);
@@ -893,7 +1004,9 @@
if (offset[idx + 1] == 0) {
dn->node_page = pages[idx];
dn->nid = nid[idx];
- truncate_node(dn);
+ err = truncate_node(dn);
+ if (err)
+ goto fail;
} else {
f2fs_put_page(pages[idx], 1);
}
@@ -1014,6 +1127,7 @@
nid_t nid = F2FS_I(inode)->i_xattr_nid;
struct dnode_of_data dn;
struct page *npage;
+ int err;
if (!nid)
return 0;
@@ -1022,10 +1136,15 @@
if (IS_ERR(npage))
return PTR_ERR(npage);
+ set_new_dnode(&dn, inode, NULL, npage, nid);
+ err = truncate_node(&dn);
+ if (err) {
+ f2fs_put_page(npage, 1);
+ return err;
+ }
+
f2fs_i_xnid_write(inode, 0);
- set_new_dnode(&dn, inode, NULL, npage, nid);
- truncate_node(&dn);
return 0;
}
@@ -1055,11 +1174,19 @@
f2fs_truncate_data_blocks_range(&dn, 1);
/* 0 is possible, after f2fs_new_inode() has failed */
+ if (unlikely(f2fs_cp_error(F2FS_I_SB(inode)))) {
+ f2fs_put_dnode(&dn);
+ return -EIO;
+ }
f2fs_bug_on(F2FS_I_SB(inode),
inode->i_blocks != 0 && inode->i_blocks != 8);
/* will put inode & node pages */
- truncate_node(&dn);
+ err = truncate_node(&dn);
+ if (err) {
+ f2fs_put_dnode(&dn);
+ return err;
+ }
return 0;
}
@@ -1092,7 +1219,11 @@
goto fail;
#ifdef CONFIG_F2FS_CHECK_FS
- f2fs_get_node_info(sbi, dn->nid, &new_ni);
+ err = f2fs_get_node_info(sbi, dn->nid, &new_ni);
+ if (err) {
+ dec_valid_node_count(sbi, dn->inode, !ofs);
+ goto fail;
+ }
f2fs_bug_on(sbi, new_ni.blk_addr != NULL_ADDR);
#endif
new_ni.nid = dn->nid;
@@ -1140,13 +1271,21 @@
.page = page,
.encrypted_page = NULL,
};
+ int err;
- if (PageUptodate(page))
+ if (PageUptodate(page)) {
+#ifdef CONFIG_F2FS_CHECK_FS
+ f2fs_bug_on(sbi, !f2fs_inode_chksum_verify(sbi, page));
+#endif
return LOCKED_PAGE;
+ }
- f2fs_get_node_info(sbi, page->index, &ni);
+ err = f2fs_get_node_info(sbi, page->index, &ni);
+ if (err)
+ return err;
- if (unlikely(ni.blk_addr == NULL_ADDR)) {
+ if (unlikely(ni.blk_addr == NULL_ADDR) ||
+ is_sbi_flag_set(sbi, SBI_IS_SHUTDOWN)) {
ClearPageUptodate(page);
return -ENOENT;
}
@@ -1348,7 +1487,7 @@
static int __write_node_page(struct page *page, bool atomic, bool *submitted,
struct writeback_control *wbc, bool do_balance,
- enum iostat_type io_type)
+ enum iostat_type io_type, unsigned int *seq_id)
{
struct f2fs_sb_info *sbi = F2FS_P_SB(page);
nid_t nid;
@@ -1365,6 +1504,7 @@
.io_type = io_type,
.io_wbc = wbc,
};
+ unsigned int seq;
trace_f2fs_writepage(page, NODE);
@@ -1374,10 +1514,17 @@
if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
goto redirty_out;
+ if (wbc->sync_mode == WB_SYNC_NONE &&
+ IS_DNODE(page) && is_cold_node(page))
+ goto redirty_out;
+
/* get old block addr of this node page */
nid = nid_of_node(page);
f2fs_bug_on(sbi, page->index != nid);
+ if (f2fs_get_node_info(sbi, nid, &ni))
+ goto redirty_out;
+
if (wbc->for_reclaim) {
if (!down_read_trylock(&sbi->node_write))
goto redirty_out;
@@ -1385,8 +1532,6 @@
down_read(&sbi->node_write);
}
- f2fs_get_node_info(sbi, nid, &ni);
-
/* This page is already truncated */
if (unlikely(ni.blk_addr == NULL_ADDR)) {
ClearPageUptodate(page);
@@ -1396,11 +1541,22 @@
return 0;
}
+ if (__is_valid_data_blkaddr(ni.blk_addr) &&
+ !f2fs_is_valid_blkaddr(sbi, ni.blk_addr, DATA_GENERIC))
+ goto redirty_out;
+
if (atomic && !test_opt(sbi, NOBARRIER))
fio.op_flags |= REQ_PREFLUSH | REQ_FUA;
set_page_writeback(page);
ClearPageError(page);
+
+ if (f2fs_in_warm_node_list(sbi, page)) {
+ seq = f2fs_add_fsync_node_entry(sbi, page);
+ if (seq_id)
+ *seq_id = seq;
+ }
+
fio.old_blkaddr = ni.blk_addr;
f2fs_do_write_node_page(nid, &fio);
set_node_addr(sbi, &ni, fio.new_blkaddr, is_fsync_dnode(page));
@@ -1448,7 +1604,7 @@
goto out_page;
if (__write_node_page(node_page, false, NULL,
- &wbc, false, FS_GC_NODE_IO))
+ &wbc, false, FS_GC_NODE_IO, NULL))
unlock_page(node_page);
goto release_page;
} else {
@@ -1465,11 +1621,13 @@
static int f2fs_write_node_page(struct page *page,
struct writeback_control *wbc)
{
- return __write_node_page(page, false, NULL, wbc, false, FS_NODE_IO);
+ return __write_node_page(page, false, NULL, wbc, false,
+ FS_NODE_IO, NULL);
}
int f2fs_fsync_node_pages(struct f2fs_sb_info *sbi, struct inode *inode,
- struct writeback_control *wbc, bool atomic)
+ struct writeback_control *wbc, bool atomic,
+ unsigned int *seq_id)
{
pgoff_t index;
pgoff_t last_idx = ULONG_MAX;
@@ -1550,7 +1708,7 @@
ret = __write_node_page(page, atomic &&
page == last_page,
&submitted, wbc, true,
- FS_NODE_IO);
+ FS_NODE_IO, seq_id);
if (ret) {
unlock_page(page);
f2fs_put_page(last_page, 0);
@@ -1667,7 +1825,7 @@
set_dentry_mark(page, 0);
ret = __write_node_page(page, false, &submitted,
- wbc, do_balance, io_type);
+ wbc, do_balance, io_type, NULL);
if (ret)
unlock_page(page);
else if (submitted)
@@ -1686,10 +1844,12 @@
}
if (step < 2) {
+ if (wbc->sync_mode == WB_SYNC_NONE && step == 1)
+ goto out;
step++;
goto next_step;
}
-
+out:
if (nwritten)
f2fs_submit_merged_write(sbi, NODE);
@@ -1698,35 +1858,46 @@
return ret;
}
-int f2fs_wait_on_node_pages_writeback(struct f2fs_sb_info *sbi, nid_t ino)
+int f2fs_wait_on_node_pages_writeback(struct f2fs_sb_info *sbi,
+ unsigned int seq_id)
{
- pgoff_t index = 0;
- struct pagevec pvec;
+ struct fsync_node_entry *fn;
+ struct page *page;
+ struct list_head *head = &sbi->fsync_node_list;
+ unsigned long flags;
+ unsigned int cur_seq_id = 0;
int ret2, ret = 0;
- int nr_pages;
- pagevec_init(&pvec, 0);
-
- while ((nr_pages = pagevec_lookup_tag(&pvec, NODE_MAPPING(sbi), &index,
- PAGECACHE_TAG_WRITEBACK))) {
- int i;
-
- for (i = 0; i < nr_pages; i++) {
- struct page *page = pvec.pages[i];
-
- if (ino && ino_of_node(page) == ino) {
- f2fs_wait_on_page_writeback(page, NODE, true);
- if (TestClearPageError(page))
- ret = -EIO;
- }
+ while (seq_id && cur_seq_id < seq_id) {
+ spin_lock_irqsave(&sbi->fsync_node_lock, flags);
+ if (list_empty(head)) {
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+ break;
}
- pagevec_release(&pvec);
- cond_resched();
+ fn = list_first_entry(head, struct fsync_node_entry, list);
+ if (fn->seq_id > seq_id) {
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+ break;
+ }
+ cur_seq_id = fn->seq_id;
+ page = fn->page;
+ get_page(page);
+ spin_unlock_irqrestore(&sbi->fsync_node_lock, flags);
+
+ f2fs_wait_on_page_writeback(page, NODE, true);
+ if (TestClearPageError(page))
+ ret = -EIO;
+
+ put_page(page);
+
+ if (ret)
+ break;
}
ret2 = filemap_check_errors(NODE_MAPPING(sbi));
if (!ret)
ret = ret2;
+
return ret;
}
@@ -1776,6 +1947,10 @@
if (!PageUptodate(page))
SetPageUptodate(page);
+#ifdef CONFIG_F2FS_CHECK_FS
+ if (IS_INODE(page))
+ f2fs_inode_chksum_set(F2FS_P_SB(page), page);
+#endif
if (!PageDirty(page)) {
__set_page_dirty_nobuffers(page);
inc_page_count(F2FS_P_SB(page), F2FS_DIRTY_NODES);
@@ -1970,7 +2145,7 @@
kmem_cache_free(free_nid_slab, i);
}
-static void scan_nat_page(struct f2fs_sb_info *sbi,
+static int scan_nat_page(struct f2fs_sb_info *sbi,
struct page *nat_page, nid_t start_nid)
{
struct f2fs_nm_info *nm_i = NM_I(sbi);
@@ -1988,7 +2163,10 @@
break;
blk_addr = le32_to_cpu(nat_blk->entries[i].block_addr);
- f2fs_bug_on(sbi, blk_addr == NEW_ADDR);
+
+ if (blk_addr == NEW_ADDR)
+ return -EINVAL;
+
if (blk_addr == NULL_ADDR) {
add_free_nid(sbi, start_nid, true, true);
} else {
@@ -1997,6 +2175,8 @@
spin_unlock(&NM_I(sbi)->nid_list_lock);
}
}
+
+ return 0;
}
static void scan_curseg_cache(struct f2fs_sb_info *sbi)
@@ -2052,11 +2232,11 @@
up_read(&nm_i->nat_tree_lock);
}
-static void __f2fs_build_free_nids(struct f2fs_sb_info *sbi,
+static int __f2fs_build_free_nids(struct f2fs_sb_info *sbi,
bool sync, bool mount)
{
struct f2fs_nm_info *nm_i = NM_I(sbi);
- int i = 0;
+ int i = 0, ret;
nid_t nid = nm_i->next_scan_nid;
if (unlikely(nid >= nm_i->max_nid))
@@ -2064,17 +2244,17 @@
/* Enough entries */
if (nm_i->nid_cnt[FREE_NID] >= NAT_ENTRY_PER_BLOCK)
- return;
+ return 0;
if (!sync && !f2fs_available_free_memory(sbi, FREE_NIDS))
- return;
+ return 0;
if (!mount) {
/* try to find free nids in free_nid_bitmap */
scan_free_nid_bits(sbi);
if (nm_i->nid_cnt[FREE_NID] >= NAT_ENTRY_PER_BLOCK)
- return;
+ return 0;
}
/* readahead nat pages to be scanned */
@@ -2088,8 +2268,16 @@
nm_i->nat_block_bitmap)) {
struct page *page = get_current_nat_page(sbi, nid);
- scan_nat_page(sbi, page, nid);
+ ret = scan_nat_page(sbi, page, nid);
f2fs_put_page(page, 1);
+
+ if (ret) {
+ up_read(&nm_i->nat_tree_lock);
+ f2fs_bug_on(sbi, !mount);
+ f2fs_msg(sbi->sb, KERN_ERR,
+ "NAT is corrupt, run fsck to fix it");
+ return -EINVAL;
+ }
}
nid += (NAT_ENTRY_PER_BLOCK - (nid % NAT_ENTRY_PER_BLOCK));
@@ -2110,13 +2298,19 @@
f2fs_ra_meta_pages(sbi, NAT_BLOCK_OFFSET(nm_i->next_scan_nid),
nm_i->ra_nid_pages, META_NAT, false);
+
+ return 0;
}
-void f2fs_build_free_nids(struct f2fs_sb_info *sbi, bool sync, bool mount)
+int f2fs_build_free_nids(struct f2fs_sb_info *sbi, bool sync, bool mount)
{
+ int ret;
+
mutex_lock(&NM_I(sbi)->build_lock);
- __f2fs_build_free_nids(sbi, sync, mount);
+ ret = __f2fs_build_free_nids(sbi, sync, mount);
mutex_unlock(&NM_I(sbi)->build_lock);
+
+ return ret;
}
/*
@@ -2129,12 +2323,11 @@
struct f2fs_nm_info *nm_i = NM_I(sbi);
struct free_nid *i = NULL;
retry:
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_ALLOC_NID)) {
f2fs_show_injection_info(FAULT_ALLOC_NID);
return false;
}
-#endif
+
spin_lock(&nm_i->nid_list_lock);
if (unlikely(nm_i->available_nids == 0)) {
@@ -2279,12 +2472,16 @@
struct dnode_of_data dn;
struct node_info ni;
struct page *xpage;
+ int err;
if (!prev_xnid)
goto recover_xnid;
/* 1: invalidate the previous xattr nid */
- f2fs_get_node_info(sbi, prev_xnid, &ni);
+ err = f2fs_get_node_info(sbi, prev_xnid, &ni);
+ if (err)
+ return err;
+
f2fs_invalidate_blocks(sbi, ni.blk_addr);
dec_valid_node_count(sbi, inode, false);
set_node_addr(sbi, &ni, NULL_ADDR, false);
@@ -2319,8 +2516,11 @@
nid_t ino = ino_of_node(page);
struct node_info old_ni, new_ni;
struct page *ipage;
+ int err;
- f2fs_get_node_info(sbi, ino, &old_ni);
+ err = f2fs_get_node_info(sbi, ino, &old_ni);
+ if (err)
+ return err;
if (unlikely(old_ni.blk_addr != NULL_ADDR))
return -EINVAL;
@@ -2374,7 +2574,7 @@
return 0;
}
-void f2fs_restore_node_summary(struct f2fs_sb_info *sbi,
+int f2fs_restore_node_summary(struct f2fs_sb_info *sbi,
unsigned int segno, struct f2fs_summary_block *sum)
{
struct f2fs_node *rn;
@@ -2396,6 +2596,9 @@
for (idx = addr; idx < addr + nrpages; idx++) {
struct page *page = f2fs_get_tmp_page(sbi, idx);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
rn = F2FS_NODE(page);
sum_entry->nid = rn->footer.nid;
sum_entry->version = 0;
@@ -2407,6 +2610,7 @@
invalidate_mapping_pages(META_MAPPING(sbi), addr,
addr + nrpages);
}
+ return 0;
}
static void remove_nats_in_journal(struct f2fs_sb_info *sbi)
@@ -2584,6 +2788,13 @@
nid_t set_idx = 0;
LIST_HEAD(sets);
+ /* during unmount, let's flush nat_bits before checking dirty_nat_cnt */
+ if (enabled_nat_bits(sbi, cpc)) {
+ down_write(&nm_i->nat_tree_lock);
+ remove_nats_in_journal(sbi);
+ up_write(&nm_i->nat_tree_lock);
+ }
+
if (!nm_i->dirty_nat_cnt)
return;
@@ -2636,7 +2847,13 @@
nat_bits_addr = __start_cp_addr(sbi) + sbi->blocks_per_seg -
nm_i->nat_bits_blocks;
for (i = 0; i < nm_i->nat_bits_blocks; i++) {
- struct page *page = f2fs_get_meta_page(sbi, nat_bits_addr++);
+ struct page *page;
+
+ page = f2fs_get_meta_page(sbi, nat_bits_addr++);
+ if (IS_ERR(page)) {
+ disable_nat_bits(sbi, true);
+ return PTR_ERR(page);
+ }
memcpy(nm_i->nat_bits + (i << F2FS_BLKSIZE_BITS),
page_address(page), F2FS_BLKSIZE);
@@ -2720,6 +2937,7 @@
INIT_RADIX_TREE(&nm_i->nat_root, GFP_NOIO);
INIT_RADIX_TREE(&nm_i->nat_set_root, GFP_NOIO);
INIT_LIST_HEAD(&nm_i->nat_entries);
+ spin_lock_init(&nm_i->nat_list_lock);
mutex_init(&nm_i->build_lock);
spin_lock_init(&nm_i->nid_list_lock);
@@ -2764,8 +2982,8 @@
for (i = 0; i < nm_i->nat_blocks; i++) {
nm_i->free_nid_bitmap[i] = f2fs_kvzalloc(sbi,
- NAT_ENTRY_BITMAP_SIZE_ALIGNED, GFP_KERNEL);
- if (!nm_i->free_nid_bitmap)
+ f2fs_bitmap_size(NAT_ENTRY_PER_BLOCK), GFP_KERNEL);
+ if (!nm_i->free_nid_bitmap[i])
return -ENOMEM;
}
@@ -2803,8 +3021,7 @@
/* load free nid status from nat_bits table */
load_free_nid_bitmap(sbi);
- f2fs_build_free_nids(sbi, true, true);
- return 0;
+ return f2fs_build_free_nids(sbi, true, true);
}
void f2fs_destroy_node_manager(struct f2fs_sb_info *sbi)
@@ -2839,8 +3056,13 @@
unsigned idx;
nid = nat_get_nid(natvec[found - 1]) + 1;
- for (idx = 0; idx < found; idx++)
+ for (idx = 0; idx < found; idx++) {
+ spin_lock(&nm_i->nat_list_lock);
+ list_del(&natvec[idx]->list);
+ spin_unlock(&nm_i->nat_list_lock);
+
__del_from_nat_cache(nm_i, natvec[idx]);
+ }
}
f2fs_bug_on(sbi, nm_i->nat_cnt);
@@ -2895,8 +3117,15 @@
sizeof(struct nat_entry_set));
if (!nat_entry_set_slab)
goto destroy_free_nid;
+
+ fsync_node_entry_slab = f2fs_kmem_cache_create("fsync_node_entry",
+ sizeof(struct fsync_node_entry));
+ if (!fsync_node_entry_slab)
+ goto destroy_nat_entry_set;
return 0;
+destroy_nat_entry_set:
+ kmem_cache_destroy(nat_entry_set_slab);
destroy_free_nid:
kmem_cache_destroy(free_nid_slab);
destroy_nat_entry:
@@ -2907,6 +3136,7 @@
void f2fs_destroy_node_manager_caches(void)
{
+ kmem_cache_destroy(fsync_node_entry_slab);
kmem_cache_destroy(nat_entry_set_slab);
kmem_cache_destroy(free_nid_slab);
kmem_cache_destroy(nat_entry_slab);
diff --git a/fs/f2fs/node.h b/fs/f2fs/node.h
index b95e49e..0f4db7a 100644
--- a/fs/f2fs/node.h
+++ b/fs/f2fs/node.h
@@ -135,6 +135,11 @@
return NM_I(sbi)->nat_cnt >= DEF_NAT_CACHE_THRESHOLD;
}
+static inline bool excess_dirty_nodes(struct f2fs_sb_info *sbi)
+{
+ return get_pages(sbi, F2FS_DIRTY_NODES) >= sbi->blocks_per_seg * 8;
+}
+
enum mem_type {
FREE_NIDS, /* indicates the free nid list */
NAT_ENTRIES, /* indicates the cached nat entry */
@@ -444,6 +449,10 @@
else
flag &= ~(0x1 << type);
rn->footer.flag = cpu_to_le32(flag);
+
+#ifdef CONFIG_F2FS_CHECK_FS
+ f2fs_inode_chksum_set(F2FS_P_SB(page), page);
+#endif
}
#define set_dentry_mark(page, mark) set_mark(page, mark, DENT_BIT_SHIFT)
#define set_fsync_mark(page, mark) set_mark(page, mark, FSYNC_BIT_SHIFT)
diff --git a/fs/f2fs/recovery.c b/fs/f2fs/recovery.c
index daf81d4..501bb0f 100644
--- a/fs/f2fs/recovery.c
+++ b/fs/f2fs/recovery.c
@@ -241,8 +241,8 @@
struct page *page = NULL;
block_t blkaddr;
unsigned int loop_cnt = 0;
- unsigned int free_blocks = sbi->user_block_count -
- valid_user_blocks(sbi);
+ unsigned int free_blocks = MAIN_SEGS(sbi) * sbi->blocks_per_seg -
+ valid_user_blocks(sbi);
int err = 0;
/* get node pages in the current segment */
@@ -252,10 +252,14 @@
while (1) {
struct fsync_inode_entry *entry;
- if (!f2fs_is_valid_meta_blkaddr(sbi, blkaddr, META_POR))
+ if (!f2fs_is_valid_blkaddr(sbi, blkaddr, META_POR))
return 0;
page = f2fs_get_tmp_page(sbi, blkaddr);
+ if (IS_ERR(page)) {
+ err = PTR_ERR(page);
+ break;
+ }
if (!is_recoverable_dnode(page))
break;
@@ -471,7 +475,10 @@
f2fs_wait_on_page_writeback(dn.node_page, NODE, true);
- f2fs_get_node_info(sbi, dn.nid, &ni);
+ err = f2fs_get_node_info(sbi, dn.nid, &ni);
+ if (err)
+ goto err;
+
f2fs_bug_on(sbi, ni.ino != ino_of_node(page));
f2fs_bug_on(sbi, ofs_of_node(dn.node_page) != ofs_of_node(page));
@@ -507,14 +514,13 @@
}
/* dest is valid block, try to recover from src to dest */
- if (f2fs_is_valid_meta_blkaddr(sbi, dest, META_POR)) {
+ if (f2fs_is_valid_blkaddr(sbi, dest, META_POR)) {
if (src == NULL_ADDR) {
err = f2fs_reserve_new_block(&dn);
-#ifdef CONFIG_F2FS_FAULT_INJECTION
- while (err)
+ while (err &&
+ IS_ENABLED(CONFIG_F2FS_FAULT_INJECTION))
err = f2fs_reserve_new_block(&dn);
-#endif
/* We should not get -ENOSPC */
f2fs_bug_on(sbi, err);
if (err)
@@ -568,12 +574,16 @@
while (1) {
struct fsync_inode_entry *entry;
- if (!f2fs_is_valid_meta_blkaddr(sbi, blkaddr, META_POR))
+ if (!f2fs_is_valid_blkaddr(sbi, blkaddr, META_POR))
break;
f2fs_ra_meta_pages_cond(sbi, blkaddr);
page = f2fs_get_tmp_page(sbi, blkaddr);
+ if (IS_ERR(page)) {
+ err = PTR_ERR(page);
+ break;
+ }
if (!is_recoverable_dnode(page)) {
f2fs_put_page(page, 1);
@@ -628,7 +638,8 @@
#endif
if (s_flags & MS_RDONLY) {
- f2fs_msg(sbi->sb, KERN_INFO, "orphan cleanup on readonly fs");
+ f2fs_msg(sbi->sb, KERN_INFO,
+ "recover fsync data on readonly fs");
sbi->sb->s_flags &= ~MS_RDONLY;
}
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 52e85a0..1da9a3c 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -250,7 +250,13 @@
err = -EAGAIN;
goto next;
}
- f2fs_get_node_info(sbi, dn.nid, &ni);
+
+ err = f2fs_get_node_info(sbi, dn.nid, &ni);
+ if (err) {
+ f2fs_put_dnode(&dn);
+ return err;
+ }
+
if (cur->old_addr == NEW_ADDR) {
f2fs_invalidate_blocks(sbi, dn.data_blkaddr);
f2fs_update_data_blkaddr(&dn, NEW_ADDR);
@@ -439,8 +445,10 @@
int err;
f2fs_balance_fs(sbi, true);
- f2fs_lock_op(sbi);
+ down_write(&fi->i_gc_rwsem[WRITE]);
+
+ f2fs_lock_op(sbi);
set_inode_flag(inode, FI_ATOMIC_COMMIT);
mutex_lock(&fi->inmem_lock);
@@ -455,6 +463,8 @@
clear_inode_flag(inode, FI_ATOMIC_COMMIT);
f2fs_unlock_op(sbi);
+ up_write(&fi->i_gc_rwsem[WRITE]);
+
return err;
}
@@ -464,12 +474,10 @@
*/
void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need)
{
-#ifdef CONFIG_F2FS_FAULT_INJECTION
if (time_to_inject(sbi, FAULT_CHECKPOINT)) {
f2fs_show_injection_info(FAULT_CHECKPOINT);
f2fs_stop_checkpoint(sbi, false);
}
-#endif
/* balance_fs_bg is able to be pending */
if (need && excess_cached_nats(sbi))
@@ -503,7 +511,8 @@
else
f2fs_build_free_nids(sbi, false, false);
- if (!is_idle(sbi) && !excess_dirty_nats(sbi))
+ if (!is_idle(sbi) &&
+ (!excess_dirty_nats(sbi) && !excess_dirty_nodes(sbi)))
return;
/* checkpoint is the only way to shrink partial cached entries */
@@ -511,6 +520,7 @@
!f2fs_available_free_memory(sbi, INO_ENTRIES) ||
excess_prefree_segs(sbi) ||
excess_dirty_nats(sbi) ||
+ excess_dirty_nodes(sbi) ||
f2fs_time_over(sbi, CP_TIME)) {
if (test_opt(sbi, DATA_FLUSH)) {
struct blk_plug plug;
@@ -831,9 +841,12 @@
dc->len = len;
dc->ref = 0;
dc->state = D_PREP;
+ dc->issuing = 0;
dc->error = 0;
init_completion(&dc->wait);
list_add_tail(&dc->list, pend_list);
+ spin_lock_init(&dc->lock);
+ dc->bio_ref = 0;
atomic_inc(&dcc->discard_cmd_cnt);
dcc->undiscard_blks += len;
@@ -860,7 +873,7 @@
struct discard_cmd *dc)
{
if (dc->state == D_DONE)
- atomic_dec(&dcc->issing_discard);
+ atomic_sub(dc->issuing, &dcc->issing_discard);
list_del(&dc->list);
rb_erase(&dc->rb_node, &dcc->root);
@@ -875,9 +888,17 @@
struct discard_cmd *dc)
{
struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
+ unsigned long flags;
trace_f2fs_remove_discard(dc->bdev, dc->start, dc->len);
+ spin_lock_irqsave(&dc->lock, flags);
+ if (dc->bio_ref) {
+ spin_unlock_irqrestore(&dc->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&dc->lock, flags);
+
f2fs_bug_on(sbi, dc->ref);
if (dc->error == -EOPNOTSUPP)
@@ -893,10 +914,17 @@
static void f2fs_submit_discard_endio(struct bio *bio)
{
struct discard_cmd *dc = (struct discard_cmd *)bio->bi_private;
+ unsigned long flags;
dc->error = bio->bi_error;
- dc->state = D_DONE;
- complete_all(&dc->wait);
+
+ spin_lock_irqsave(&dc->lock, flags);
+ dc->bio_ref--;
+ if (!dc->bio_ref && dc->state == D_SUBMIT) {
+ dc->state = D_DONE;
+ complete_all(&dc->wait);
+ }
+ spin_unlock_irqrestore(&dc->lock, flags);
bio_put(bio);
}
@@ -934,6 +962,7 @@
/* common policy */
dpolicy->type = discard_type;
dpolicy->sync = true;
+ dpolicy->ordered = false;
dpolicy->granularity = granularity;
dpolicy->max_requests = DEF_MAX_DISCARD_REQUEST;
@@ -945,6 +974,7 @@
dpolicy->max_interval = DEF_MAX_DISCARD_ISSUE_TIME;
dpolicy->io_aware = true;
dpolicy->sync = false;
+ dpolicy->ordered = true;
if (utilization(sbi) > DEF_DISCARD_URGENT_UTIL) {
dpolicy->granularity = 1;
dpolicy->max_interval = DEF_MIN_DISCARD_ISSUE_TIME;
@@ -962,48 +992,115 @@
}
}
-
+static void __update_discard_tree_range(struct f2fs_sb_info *sbi,
+ struct block_device *bdev, block_t lstart,
+ block_t start, block_t len);
/* this function is copied from blkdev_issue_discard from block/blk-lib.c */
-static void __submit_discard_cmd(struct f2fs_sb_info *sbi,
+static int __submit_discard_cmd(struct f2fs_sb_info *sbi,
struct discard_policy *dpolicy,
- struct discard_cmd *dc)
+ struct discard_cmd *dc,
+ unsigned int *issued)
{
+ struct block_device *bdev = dc->bdev;
+ struct request_queue *q = bdev_get_queue(bdev);
+ unsigned int max_discard_blocks =
+ SECTOR_TO_BLOCK(q->limits.max_discard_sectors);
struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
struct list_head *wait_list = (dpolicy->type == DPOLICY_FSTRIM) ?
&(dcc->fstrim_list) : &(dcc->wait_list);
- struct bio *bio = NULL;
int flag = dpolicy->sync ? REQ_SYNC : 0;
+ block_t lstart, start, len, total_len;
+ int err = 0;
if (dc->state != D_PREP)
- return;
+ return 0;
if (is_sbi_flag_set(sbi, SBI_NEED_FSCK))
- return;
+ return 0;
- trace_f2fs_issue_discard(dc->bdev, dc->start, dc->len);
+ trace_f2fs_issue_discard(bdev, dc->start, dc->len);
- dc->error = __blkdev_issue_discard(dc->bdev,
- SECTOR_FROM_BLOCK(dc->start),
- SECTOR_FROM_BLOCK(dc->len),
- GFP_NOFS, 0, &bio);
- if (!dc->error) {
- /* should keep before submission to avoid D_DONE right away */
- dc->state = D_SUBMIT;
- atomic_inc(&dcc->issued_discard);
- atomic_inc(&dcc->issing_discard);
- if (bio) {
- bio->bi_private = dc;
- bio->bi_end_io = f2fs_submit_discard_endio;
- bio->bi_opf |= flag;
- submit_bio(bio);
- list_move_tail(&dc->list, wait_list);
- __check_sit_bitmap(sbi, dc->start, dc->start + dc->len);
+ lstart = dc->lstart;
+ start = dc->start;
+ len = dc->len;
+ total_len = len;
- f2fs_update_iostat(sbi, FS_DISCARD, 1);
+ dc->len = 0;
+
+ while (total_len && *issued < dpolicy->max_requests && !err) {
+ struct bio *bio = NULL;
+ unsigned long flags;
+ bool last = true;
+
+ if (len > max_discard_blocks) {
+ len = max_discard_blocks;
+ last = false;
}
- } else {
- __remove_discard_cmd(sbi, dc);
+
+ (*issued)++;
+ if (*issued == dpolicy->max_requests)
+ last = true;
+
+ dc->len += len;
+
+ if (time_to_inject(sbi, FAULT_DISCARD)) {
+ f2fs_show_injection_info(FAULT_DISCARD);
+ err = -EIO;
+ goto submit;
+ }
+ err = __blkdev_issue_discard(bdev,
+ SECTOR_FROM_BLOCK(start),
+ SECTOR_FROM_BLOCK(len),
+ GFP_NOFS, 0, &bio);
+submit:
+ if (err) {
+ spin_lock_irqsave(&dc->lock, flags);
+ if (dc->state == D_PARTIAL)
+ dc->state = D_SUBMIT;
+ spin_unlock_irqrestore(&dc->lock, flags);
+
+ break;
+ }
+
+ f2fs_bug_on(sbi, !bio);
+
+ /*
+ * should keep before submission to avoid D_DONE
+ * right away
+ */
+ spin_lock_irqsave(&dc->lock, flags);
+ if (last)
+ dc->state = D_SUBMIT;
+ else
+ dc->state = D_PARTIAL;
+ dc->bio_ref++;
+ spin_unlock_irqrestore(&dc->lock, flags);
+
+ atomic_inc(&dcc->issing_discard);
+ dc->issuing++;
+ list_move_tail(&dc->list, wait_list);
+
+ /* sanity check on discard range */
+ __check_sit_bitmap(sbi, start, start + len);
+
+ bio->bi_private = dc;
+ bio->bi_end_io = f2fs_submit_discard_endio;
+ bio->bi_opf |= flag;
+ submit_bio(bio);
+
+ atomic_inc(&dcc->issued_discard);
+
+ f2fs_update_iostat(sbi, FS_DISCARD, 1);
+
+ lstart += len;
+ start += len;
+ total_len -= len;
+ len = total_len;
}
+
+ if (!err && len)
+ __update_discard_tree_range(sbi, bdev, lstart, start, len);
+ return err;
}
static struct discard_cmd *__insert_discard_tree(struct f2fs_sb_info *sbi,
@@ -1084,10 +1181,11 @@
struct discard_cmd *dc;
struct discard_info di = {0};
struct rb_node **insert_p = NULL, *insert_parent = NULL;
+ struct request_queue *q = bdev_get_queue(bdev);
+ unsigned int max_discard_blocks =
+ SECTOR_TO_BLOCK(q->limits.max_discard_sectors);
block_t end = lstart + len;
- mutex_lock(&dcc->cmd_lock);
-
dc = (struct discard_cmd *)f2fs_lookup_rb_tree_ret(&dcc->root,
NULL, lstart,
(struct rb_entry **)&prev_dc,
@@ -1127,7 +1225,8 @@
if (prev_dc && prev_dc->state == D_PREP &&
prev_dc->bdev == bdev &&
- __is_discard_back_mergeable(&di, &prev_dc->di)) {
+ __is_discard_back_mergeable(&di, &prev_dc->di,
+ max_discard_blocks)) {
prev_dc->di.len += di.len;
dcc->undiscard_blks += di.len;
__relocate_discard_cmd(dcc, prev_dc);
@@ -1138,7 +1237,8 @@
if (next_dc && next_dc->state == D_PREP &&
next_dc->bdev == bdev &&
- __is_discard_front_mergeable(&di, &next_dc->di)) {
+ __is_discard_front_mergeable(&di, &next_dc->di,
+ max_discard_blocks)) {
next_dc->di.lstart = di.lstart;
next_dc->di.len += di.len;
next_dc->di.start = di.start;
@@ -1161,8 +1261,6 @@
node = rb_next(&prev_dc->rb_node);
next_dc = rb_entry_safe(node, struct discard_cmd, rb_node);
}
-
- mutex_unlock(&dcc->cmd_lock);
}
static int __queue_discard_cmd(struct f2fs_sb_info *sbi,
@@ -1177,10 +1275,72 @@
blkstart -= FDEV(devi).start_blk;
}
+ mutex_lock(&SM_I(sbi)->dcc_info->cmd_lock);
__update_discard_tree_range(sbi, bdev, lblkstart, blkstart, blklen);
+ mutex_unlock(&SM_I(sbi)->dcc_info->cmd_lock);
return 0;
}
+static unsigned int __issue_discard_cmd_orderly(struct f2fs_sb_info *sbi,
+ struct discard_policy *dpolicy)
+{
+ struct discard_cmd_control *dcc = SM_I(sbi)->dcc_info;
+ struct discard_cmd *prev_dc = NULL, *next_dc = NULL;
+ struct rb_node **insert_p = NULL, *insert_parent = NULL;
+ struct discard_cmd *dc;
+ struct blk_plug plug;
+ unsigned int pos = dcc->next_pos;
+ unsigned int issued = 0;
+ bool io_interrupted = false;
+
+ mutex_lock(&dcc->cmd_lock);
+ dc = (struct discard_cmd *)f2fs_lookup_rb_tree_ret(&dcc->root,
+ NULL, pos,
+ (struct rb_entry **)&prev_dc,
+ (struct rb_entry **)&next_dc,
+ &insert_p, &insert_parent, true);
+ if (!dc)
+ dc = next_dc;
+
+ blk_start_plug(&plug);
+
+ while (dc) {
+ struct rb_node *node;
+ int err = 0;
+
+ if (dc->state != D_PREP)
+ goto next;
+
+ if (dpolicy->io_aware && !is_idle(sbi)) {
+ io_interrupted = true;
+ break;
+ }
+
+ dcc->next_pos = dc->lstart + dc->len;
+ err = __submit_discard_cmd(sbi, dpolicy, dc, &issued);
+
+ if (issued >= dpolicy->max_requests)
+ break;
+next:
+ node = rb_next(&dc->rb_node);
+ if (err)
+ __remove_discard_cmd(sbi, dc);
+ dc = rb_entry_safe(node, struct discard_cmd, rb_node);
+ }
+
+ blk_finish_plug(&plug);
+
+ if (!dc)
+ dcc->next_pos = 0;
+
+ mutex_unlock(&dcc->cmd_lock);
+
+ if (!issued && io_interrupted)
+ issued = -1;
+
+ return issued;
+}
+
static int __issue_discard_cmd(struct f2fs_sb_info *sbi,
struct discard_policy *dpolicy)
{
@@ -1188,19 +1348,24 @@
struct list_head *pend_list;
struct discard_cmd *dc, *tmp;
struct blk_plug plug;
- int i, iter = 0, issued = 0;
+ int i, issued = 0;
bool io_interrupted = false;
for (i = MAX_PLIST_NUM - 1; i >= 0; i--) {
if (i + 1 < dpolicy->granularity)
break;
+
+ if (i < DEFAULT_DISCARD_GRANULARITY && dpolicy->ordered)
+ return __issue_discard_cmd_orderly(sbi, dpolicy);
+
pend_list = &dcc->pend_list[i];
mutex_lock(&dcc->cmd_lock);
if (list_empty(pend_list))
goto next;
- f2fs_bug_on(sbi,
- !f2fs_check_rb_tree_consistence(sbi, &dcc->root));
+ if (unlikely(dcc->rbtree_check))
+ f2fs_bug_on(sbi, !f2fs_check_rb_tree_consistence(sbi,
+ &dcc->root));
blk_start_plug(&plug);
list_for_each_entry_safe(dc, tmp, pend_list, list) {
f2fs_bug_on(sbi, dc->state != D_PREP);
@@ -1208,20 +1373,19 @@
if (dpolicy->io_aware && i < dpolicy->io_aware_gran &&
!is_idle(sbi)) {
io_interrupted = true;
- goto skip;
+ break;
}
- __submit_discard_cmd(sbi, dpolicy, dc);
- issued++;
-skip:
- if (++iter >= dpolicy->max_requests)
+ __submit_discard_cmd(sbi, dpolicy, dc, &issued);
+
+ if (issued >= dpolicy->max_requests)
break;
}
blk_finish_plug(&plug);
next:
mutex_unlock(&dcc->cmd_lock);
- if (iter >= dpolicy->max_requests)
+ if (issued >= dpolicy->max_requests || io_interrupted)
break;
}
@@ -1319,21 +1483,22 @@
return trimmed;
}
-static void __wait_all_discard_cmd(struct f2fs_sb_info *sbi,
+static unsigned int __wait_all_discard_cmd(struct f2fs_sb_info *sbi,
struct discard_policy *dpolicy)
{
struct discard_policy dp;
+ unsigned int discard_blks;
- if (dpolicy) {
- __wait_discard_cmd_range(sbi, dpolicy, 0, UINT_MAX);
- return;
- }
+ if (dpolicy)
+ return __wait_discard_cmd_range(sbi, dpolicy, 0, UINT_MAX);
/* wait all */
__init_discard_policy(sbi, &dp, DPOLICY_FSTRIM, 1);
- __wait_discard_cmd_range(sbi, &dp, 0, UINT_MAX);
+ discard_blks = __wait_discard_cmd_range(sbi, &dp, 0, UINT_MAX);
__init_discard_policy(sbi, &dp, DPOLICY_UMOUNT, 1);
- __wait_discard_cmd_range(sbi, &dp, 0, UINT_MAX);
+ discard_blks += __wait_discard_cmd_range(sbi, &dp, 0, UINT_MAX);
+
+ return discard_blks;
}
/* This should be covered by global mutex, &sit_i->sentry_lock */
@@ -1386,6 +1551,8 @@
/* just to make sure there is no pending discard commands */
__wait_all_discard_cmd(sbi, NULL);
+
+ f2fs_bug_on(sbi, atomic_read(&dcc->discard_cmd_cnt));
return dropped;
}
@@ -1649,21 +1816,30 @@
unsigned int start = 0, end = -1;
unsigned int secno, start_segno;
bool force = (cpc->reason & CP_DISCARD);
+ bool need_align = test_opt(sbi, LFS) && sbi->segs_per_sec > 1;
mutex_lock(&dirty_i->seglist_lock);
while (1) {
int i;
+
+ if (need_align && end != -1)
+ end--;
start = find_next_bit(prefree_map, MAIN_SEGS(sbi), end + 1);
if (start >= MAIN_SEGS(sbi))
break;
end = find_next_zero_bit(prefree_map, MAIN_SEGS(sbi),
start + 1);
- for (i = start; i < end; i++)
- clear_bit(i, prefree_map);
+ if (need_align) {
+ start = rounddown(start, sbi->segs_per_sec);
+ end = roundup(end, sbi->segs_per_sec);
+ }
- dirty_i->nr_dirty[PRE] -= end - start;
+ for (i = start; i < end; i++) {
+ if (test_and_clear_bit(i, prefree_map))
+ dirty_i->nr_dirty[PRE]--;
+ }
if (!test_opt(sbi, DISCARD))
continue;
@@ -1757,7 +1933,9 @@
dcc->nr_discards = 0;
dcc->max_discards = MAIN_SEGS(sbi) << sbi->log_blocks_per_seg;
dcc->undiscard_blks = 0;
+ dcc->next_pos = 0;
dcc->root = RB_ROOT;
+ dcc->rbtree_check = false;
init_waitqueue_head(&dcc->discard_wait_queue);
SM_I(sbi)->dcc_info = dcc;
@@ -1907,6 +2085,8 @@
if (addr == NEW_ADDR)
return;
+ invalidate_mapping_pages(META_MAPPING(sbi), addr, addr);
+
/* add it into sit main buffer */
down_write(&sit_i->sentry_lock);
@@ -1925,7 +2105,7 @@
struct seg_entry *se;
bool is_cp = false;
- if (!is_valid_blkaddr(blkaddr))
+ if (!is_valid_data_blkaddr(sbi, blkaddr))
return true;
down_read(&sit_i->sentry_lock);
@@ -1989,7 +2169,7 @@
*/
struct page *f2fs_get_sum_page(struct f2fs_sb_info *sbi, unsigned int segno)
{
- return f2fs_get_meta_page(sbi, GET_SUM_BLOCK(sbi, segno));
+ return f2fs_get_meta_page_nofail(sbi, GET_SUM_BLOCK(sbi, segno));
}
void f2fs_update_meta_page(struct f2fs_sb_info *sbi,
@@ -2372,7 +2552,7 @@
return has_candidate;
}
-static void __issue_discard_cmd_range(struct f2fs_sb_info *sbi,
+static unsigned int __issue_discard_cmd_range(struct f2fs_sb_info *sbi,
struct discard_policy *dpolicy,
unsigned int start, unsigned int end)
{
@@ -2382,12 +2562,15 @@
struct discard_cmd *dc;
struct blk_plug plug;
int issued;
+ unsigned int trimmed = 0;
next:
issued = 0;
mutex_lock(&dcc->cmd_lock);
- f2fs_bug_on(sbi, !f2fs_check_rb_tree_consistence(sbi, &dcc->root));
+ if (unlikely(dcc->rbtree_check))
+ f2fs_bug_on(sbi, !f2fs_check_rb_tree_consistence(sbi,
+ &dcc->root));
dc = (struct discard_cmd *)f2fs_lookup_rb_tree_ret(&dcc->root,
NULL, start,
@@ -2401,6 +2584,7 @@
while (dc && dc->lstart <= end) {
struct rb_node *node;
+ int err = 0;
if (dc->len < dpolicy->granularity)
goto skip;
@@ -2410,19 +2594,24 @@
goto skip;
}
- __submit_discard_cmd(sbi, dpolicy, dc);
+ err = __submit_discard_cmd(sbi, dpolicy, dc, &issued);
- if (++issued >= dpolicy->max_requests) {
+ if (issued >= dpolicy->max_requests) {
start = dc->lstart + dc->len;
+ if (err)
+ __remove_discard_cmd(sbi, dc);
+
blk_finish_plug(&plug);
mutex_unlock(&dcc->cmd_lock);
- __wait_all_discard_cmd(sbi, NULL);
+ trimmed += __wait_all_discard_cmd(sbi, NULL);
congestion_wait(BLK_RW_ASYNC, HZ/50);
goto next;
}
skip:
node = rb_next(&dc->rb_node);
+ if (err)
+ __remove_discard_cmd(sbi, dc);
dc = rb_entry_safe(node, struct discard_cmd, rb_node);
if (fatal_signal_pending(current))
@@ -2431,6 +2620,8 @@
blk_finish_plug(&plug);
mutex_unlock(&dcc->cmd_lock);
+
+ return trimmed;
}
int f2fs_trim_fs(struct f2fs_sb_info *sbi, struct fstrim_range *range)
@@ -2443,12 +2634,13 @@
struct discard_policy dpolicy;
unsigned long long trimmed = 0;
int err = 0;
+ bool need_align = test_opt(sbi, LFS) && sbi->segs_per_sec > 1;
if (start >= MAX_BLKADDR(sbi) || range->len < sbi->blocksize)
return -EINVAL;
- if (end <= MAIN_BLKADDR(sbi))
- return -EINVAL;
+ if (end < MAIN_BLKADDR(sbi))
+ goto out;
if (is_sbi_flag_set(sbi, SBI_NEED_FSCK)) {
f2fs_msg(sbi->sb, KERN_WARNING,
@@ -2460,6 +2652,10 @@
start_segno = (start <= MAIN_BLKADDR(sbi)) ? 0 : GET_SEGNO(sbi, start);
end_segno = (end >= MAX_BLKADDR(sbi)) ? MAIN_SEGS(sbi) - 1 :
GET_SEGNO(sbi, end);
+ if (need_align) {
+ start_segno = rounddown(start_segno, sbi->segs_per_sec);
+ end_segno = roundup(end_segno + 1, sbi->segs_per_sec) - 1;
+ }
cpc.reason = CP_DISCARD;
cpc.trim_minlen = max_t(__u64, 1, F2FS_BYTES_TO_BLK(range->minlen));
@@ -2475,24 +2671,27 @@
if (err)
goto out;
- start_block = START_BLOCK(sbi, start_segno);
- end_block = START_BLOCK(sbi, end_segno + 1);
-
- __init_discard_policy(sbi, &dpolicy, DPOLICY_FSTRIM, cpc.trim_minlen);
- __issue_discard_cmd_range(sbi, &dpolicy, start_block, end_block);
-
/*
* We filed discard candidates, but actually we don't need to wait for
* all of them, since they'll be issued in idle time along with runtime
* discard option. User configuration looks like using runtime discard
* or periodic fstrim instead of it.
*/
- if (!test_opt(sbi, DISCARD)) {
- trimmed = __wait_discard_cmd_range(sbi, &dpolicy,
+ if (test_opt(sbi, DISCARD))
+ goto out;
+
+ start_block = START_BLOCK(sbi, start_segno);
+ end_block = START_BLOCK(sbi, end_segno + 1);
+
+ __init_discard_policy(sbi, &dpolicy, DPOLICY_FSTRIM, cpc.trim_minlen);
+ trimmed = __issue_discard_cmd_range(sbi, &dpolicy,
start_block, end_block);
- range->len = F2FS_BLK_TO_BYTES(trimmed);
- }
+
+ trimmed += __wait_discard_cmd_range(sbi, &dpolicy,
+ start_block, end_block);
out:
+ if (!err)
+ range->len = F2FS_BLK_TO_BYTES(trimmed);
return err;
}
@@ -2645,8 +2844,8 @@
return CURSEG_COLD_DATA;
if (file_is_hot(inode) ||
is_inode_flag_set(inode, FI_HOT_DATA) ||
- is_inode_flag_set(inode, FI_ATOMIC_FILE) ||
- is_inode_flag_set(inode, FI_VOLATILE_FILE))
+ f2fs_is_atomic_file(inode) ||
+ f2fs_is_volatile_file(inode))
return CURSEG_HOT_DATA;
/* f2fs_rw_hint_to_seg_type(inode->i_write_hint); */
return CURSEG_WARM_DATA;
@@ -2788,6 +2987,9 @@
reallocate:
f2fs_allocate_data_block(fio->sbi, fio->page, fio->old_blkaddr,
&fio->new_blkaddr, sum, type, fio, true);
+ if (GET_SEGNO(fio->sbi, fio->old_blkaddr) != NULL_SEGNO)
+ invalidate_mapping_pages(META_MAPPING(fio->sbi),
+ fio->old_blkaddr, fio->old_blkaddr);
/* writeout dirty page into bdev */
f2fs_submit_page_write(fio);
@@ -2843,11 +3045,9 @@
{
struct f2fs_sb_info *sbi = fio->sbi;
struct f2fs_summary sum;
- struct node_info ni;
f2fs_bug_on(sbi, dn->data_blkaddr == NULL_ADDR);
- f2fs_get_node_info(sbi, dn->nid, &ni);
- set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
+ set_summary(&sum, dn->nid, dn->ofs_in_node, fio->version);
do_write_page(&sum, fio);
f2fs_update_data_blkaddr(dn, fio->new_blkaddr);
@@ -2944,8 +3144,11 @@
if (!recover_curseg || recover_newaddr)
update_sit_entry(sbi, new_blkaddr, 1);
- if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO)
+ if (GET_SEGNO(sbi, old_blkaddr) != NULL_SEGNO) {
+ invalidate_mapping_pages(META_MAPPING(sbi),
+ old_blkaddr, old_blkaddr);
update_sit_entry(sbi, old_blkaddr, -1);
+ }
locate_dirty_segment(sbi, GET_SEGNO(sbi, old_blkaddr));
locate_dirty_segment(sbi, GET_SEGNO(sbi, new_blkaddr));
@@ -2999,7 +3202,7 @@
{
struct page *cpage;
- if (!is_valid_blkaddr(blkaddr))
+ if (!is_valid_data_blkaddr(sbi, blkaddr))
return;
cpage = find_lock_page(META_MAPPING(sbi), blkaddr);
@@ -3009,7 +3212,7 @@
}
}
-static void read_compacted_summaries(struct f2fs_sb_info *sbi)
+static int read_compacted_summaries(struct f2fs_sb_info *sbi)
{
struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
struct curseg_info *seg_i;
@@ -3021,6 +3224,8 @@
start = start_sum_block(sbi);
page = f2fs_get_meta_page(sbi, start++);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
kaddr = (unsigned char *)page_address(page);
/* Step 1: restore nat cache */
@@ -3061,11 +3266,14 @@
page = NULL;
page = f2fs_get_meta_page(sbi, start++);
+ if (IS_ERR(page))
+ return PTR_ERR(page);
kaddr = (unsigned char *)page_address(page);
offset = 0;
}
}
f2fs_put_page(page, 1);
+ return 0;
}
static int read_normal_summaries(struct f2fs_sb_info *sbi, int type)
@@ -3077,6 +3285,7 @@
unsigned short blk_off;
unsigned int segno = 0;
block_t blk_addr = 0;
+ int err = 0;
/* get segment number and block addr */
if (IS_DATASEG(type)) {
@@ -3100,6 +3309,8 @@
}
new = f2fs_get_meta_page(sbi, blk_addr);
+ if (IS_ERR(new))
+ return PTR_ERR(new);
sum = (struct f2fs_summary_block *)page_address(new);
if (IS_NODESEG(type)) {
@@ -3111,7 +3322,9 @@
ns->ofs_in_node = 0;
}
} else {
- f2fs_restore_node_summary(sbi, segno, sum);
+ err = f2fs_restore_node_summary(sbi, segno, sum);
+ if (err)
+ goto out;
}
}
@@ -3131,8 +3344,9 @@
curseg->alloc_type = ckpt->alloc_type[type];
curseg->next_blkoff = blk_off;
mutex_unlock(&curseg->curseg_mutex);
+out:
f2fs_put_page(new, 1);
- return 0;
+ return err;
}
static int restore_curseg_summaries(struct f2fs_sb_info *sbi)
@@ -3150,7 +3364,9 @@
META_CP, true);
/* restore for compacted data summary */
- read_compacted_summaries(sbi);
+ err = read_compacted_summaries(sbi);
+ if (err)
+ return err;
type = CURSEG_HOT_NODE;
}
@@ -3281,7 +3497,7 @@
static struct page *get_current_sit_page(struct f2fs_sb_info *sbi,
unsigned int segno)
{
- return f2fs_get_meta_page(sbi, current_sit_addr(sbi, segno));
+ return f2fs_get_meta_page_nofail(sbi, current_sit_addr(sbi, segno));
}
static struct page *get_next_sit_page(struct f2fs_sb_info *sbi,
diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h
index 38c549d..b3d9e31 100644
--- a/fs/f2fs/segment.h
+++ b/fs/f2fs/segment.h
@@ -85,7 +85,7 @@
(GET_SEGOFF_FROM_SEG0(sbi, blk_addr) & ((sbi)->blocks_per_seg - 1))
#define GET_SEGNO(sbi, blk_addr) \
- ((!is_valid_blkaddr(blk_addr)) ? \
+ ((!is_valid_data_blkaddr(sbi, blk_addr)) ? \
NULL_SEGNO : GET_L2R_SEGNO(FREE_I(sbi), \
GET_SEGNO_FROM_SEG0(sbi, blk_addr)))
#define BLKS_PER_SEC(sbi) \
@@ -215,7 +215,7 @@
#define IS_DUMMY_WRITTEN_PAGE(page) \
(page_private(page) == (unsigned long)DUMMY_WRITTEN_PAGE)
-#define MAX_SKIP_ATOMIC_COUNT 16
+#define MAX_SKIP_GC_COUNT 16
struct inmem_pages {
struct list_head list;
@@ -648,13 +648,10 @@
{
struct f2fs_sb_info *sbi = fio->sbi;
- if (PAGE_TYPE_OF_BIO(fio->type) == META &&
- (!is_read_io(fio->op) || fio->is_meta))
- BUG_ON(blk_addr < SEG0_BLKADDR(sbi) ||
- blk_addr >= MAIN_BLKADDR(sbi));
+ if (__is_meta_io(fio))
+ verify_blkaddr(sbi, blk_addr, META_GENERIC);
else
- BUG_ON(blk_addr < MAIN_BLKADDR(sbi) ||
- blk_addr >= MAX_BLKADDR(sbi));
+ verify_blkaddr(sbi, blk_addr, DATA_GENERIC);
}
/*
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 343a5cf..bba0cd4 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -41,7 +41,7 @@
#ifdef CONFIG_F2FS_FAULT_INJECTION
-char *fault_name[FAULT_MAX] = {
+char *f2fs_fault_name[FAULT_MAX] = {
[FAULT_KMALLOC] = "kmalloc",
[FAULT_KVMALLOC] = "kvmalloc",
[FAULT_PAGE_ALLOC] = "page alloc",
@@ -55,20 +55,24 @@
[FAULT_TRUNCATE] = "truncate fail",
[FAULT_IO] = "IO error",
[FAULT_CHECKPOINT] = "checkpoint error",
+ [FAULT_DISCARD] = "discard error",
};
-static void f2fs_build_fault_attr(struct f2fs_sb_info *sbi,
- unsigned int rate)
+void f2fs_build_fault_attr(struct f2fs_sb_info *sbi, unsigned int rate,
+ unsigned int type)
{
struct f2fs_fault_info *ffi = &F2FS_OPTION(sbi).fault_info;
if (rate) {
atomic_set(&ffi->inject_ops, 0);
ffi->inject_rate = rate;
- ffi->inject_type = (1 << FAULT_MAX) - 1;
- } else {
- memset(ffi, 0, sizeof(struct f2fs_fault_info));
}
+
+ if (type)
+ ffi->inject_type = type;
+
+ if (!rate && !type)
+ memset(ffi, 0, sizeof(struct f2fs_fault_info));
}
#endif
@@ -113,6 +117,7 @@
Opt_mode,
Opt_io_size_bits,
Opt_fault_injection,
+ Opt_fault_type,
Opt_lazytime,
Opt_nolazytime,
Opt_quota,
@@ -170,6 +175,7 @@
{Opt_mode, "mode=%s"},
{Opt_io_size_bits, "io_bits=%u"},
{Opt_fault_injection, "fault_injection=%u"},
+ {Opt_fault_type, "fault_type=%u"},
{Opt_lazytime, "lazytime"},
{Opt_nolazytime, "nolazytime"},
{Opt_quota, "quota"},
@@ -347,12 +353,6 @@
"QUOTA feature is enabled, so ignore jquota_fmt");
F2FS_OPTION(sbi).s_jquota_fmt = 0;
}
- if (f2fs_sb_has_quota_ino(sbi->sb) && f2fs_readonly(sbi->sb)) {
- f2fs_msg(sbi->sb, KERN_INFO,
- "Filesystem with quota feature cannot be mounted RDWR "
- "without CONFIG_QUOTA");
- return -1;
- }
return 0;
}
#endif
@@ -606,7 +606,18 @@
if (args->from && match_int(args, &arg))
return -EINVAL;
#ifdef CONFIG_F2FS_FAULT_INJECTION
- f2fs_build_fault_attr(sbi, arg);
+ f2fs_build_fault_attr(sbi, arg, F2FS_ALL_FAULT_TYPE);
+ set_opt(sbi, FAULT_INJECTION);
+#else
+ f2fs_msg(sb, KERN_INFO,
+ "FAULT_INJECTION was not selected");
+#endif
+ break;
+ case Opt_fault_type:
+ if (args->from && match_int(args, &arg))
+ return -EINVAL;
+#ifdef CONFIG_F2FS_FAULT_INJECTION
+ f2fs_build_fault_attr(sbi, 0, arg);
set_opt(sbi, FAULT_INJECTION);
#else
f2fs_msg(sb, KERN_INFO,
@@ -775,6 +786,19 @@
#ifdef CONFIG_QUOTA
if (f2fs_check_quota_options(sbi))
return -EINVAL;
+#else
+ if (f2fs_sb_has_quota_ino(sbi->sb) && !f2fs_readonly(sbi->sb)) {
+ f2fs_msg(sbi->sb, KERN_INFO,
+ "Filesystem with quota feature cannot be mounted RDWR "
+ "without CONFIG_QUOTA");
+ return -EINVAL;
+ }
+ if (f2fs_sb_has_project_quota(sbi->sb) && !f2fs_readonly(sbi->sb)) {
+ f2fs_msg(sb, KERN_ERR,
+ "Filesystem with project quota feature cannot be "
+ "mounted RDWR without CONFIG_QUOTA");
+ return -EINVAL;
+ }
#endif
if (F2FS_IO_SIZE_BITS(sbi) && !test_opt(sbi, LFS)) {
@@ -1030,6 +1054,10 @@
/* our cp_error case, we can wait for any writeback page */
f2fs_flush_merged_writes(sbi);
+ f2fs_wait_on_all_pages_writeback(sbi);
+
+ f2fs_bug_on(sbi, sbi->fsync_node_num);
+
iput(sbi->node_inode);
iput(sbi->meta_inode);
@@ -1310,9 +1338,12 @@
if (F2FS_IO_SIZE_BITS(sbi))
seq_printf(seq, ",io_size=%uKB", F2FS_IO_SIZE_KB(sbi));
#ifdef CONFIG_F2FS_FAULT_INJECTION
- if (test_opt(sbi, FAULT_INJECTION))
+ if (test_opt(sbi, FAULT_INJECTION)) {
seq_printf(seq, ",fault_injection=%u",
F2FS_OPTION(sbi).fault_info.inject_rate);
+ seq_printf(seq, ",fault_type=%u",
+ F2FS_OPTION(sbi).fault_info.inject_type);
+ }
#endif
#ifdef CONFIG_QUOTA
if (test_opt(sbi, QUOTA))
@@ -1357,7 +1388,8 @@
F2FS_OPTION(sbi).alloc_mode = ALLOC_MODE_DEFAULT;
F2FS_OPTION(sbi).fsync_mode = FSYNC_MODE_POSIX;
F2FS_OPTION(sbi).test_dummy_encryption = false;
- sbi->readdir_ra = 1;
+ F2FS_OPTION(sbi).s_resuid = make_kuid(&init_user_ns, F2FS_DEF_RESUID);
+ F2FS_OPTION(sbi).s_resgid = make_kgid(&init_user_ns, F2FS_DEF_RESGID);
set_opt(sbi, BG_GC);
set_opt(sbi, INLINE_XATTR);
@@ -1367,12 +1399,12 @@
set_opt(sbi, NOHEAP);
sbi->sb->s_flags |= MS_LAZYTIME;
set_opt(sbi, FLUSH_MERGE);
- if (f2fs_sb_has_blkzoned(sbi->sb)) {
- set_opt_mode(sbi, F2FS_MOUNT_LFS);
+ if (blk_queue_discard(bdev_get_queue(sbi->sb->s_bdev)))
set_opt(sbi, DISCARD);
- } else {
+ if (f2fs_sb_has_blkzoned(sbi->sb))
+ set_opt_mode(sbi, F2FS_MOUNT_LFS);
+ else
set_opt_mode(sbi, F2FS_MOUNT_ADAPTIVE);
- }
#ifdef CONFIG_F2FS_FS_XATTR
set_opt(sbi, XATTR_USER);
@@ -1381,9 +1413,7 @@
set_opt(sbi, POSIX_ACL);
#endif
-#ifdef CONFIG_F2FS_FAULT_INJECTION
- f2fs_build_fault_attr(sbi, 0);
-#endif
+ f2fs_build_fault_attr(sbi, 0, 0);
}
#ifdef CONFIG_QUOTA
@@ -2231,9 +2261,9 @@
return 1;
}
- if (secs_per_zone > total_sections) {
+ if (secs_per_zone > total_sections || !secs_per_zone) {
f2fs_msg(sb, KERN_INFO,
- "Wrong secs_per_zone (%u > %u)",
+ "Wrong secs_per_zone / total_sections (%u, %u)",
secs_per_zone, total_sections);
return 1;
}
@@ -2287,6 +2317,9 @@
unsigned int sit_segs, nat_segs;
unsigned int sit_bitmap_size, nat_bitmap_size;
unsigned int log_blocks_per_seg;
+ unsigned int segment_count_main;
+ unsigned int cp_pack_start_sum, cp_payload;
+ block_t user_block_count;
int i;
total = le32_to_cpu(raw_super->segment_count);
@@ -2311,6 +2344,16 @@
return 1;
}
+ user_block_count = le64_to_cpu(ckpt->user_block_count);
+ segment_count_main = le32_to_cpu(raw_super->segment_count_main);
+ log_blocks_per_seg = le32_to_cpu(raw_super->log_blocks_per_seg);
+ if (!user_block_count || user_block_count >=
+ segment_count_main << log_blocks_per_seg) {
+ f2fs_msg(sbi->sb, KERN_ERR,
+ "Wrong user_block_count: %u", user_block_count);
+ return 1;
+ }
+
main_segs = le32_to_cpu(raw_super->segment_count_main);
blocks_per_seg = sbi->blocks_per_seg;
@@ -2327,7 +2370,6 @@
sit_bitmap_size = le32_to_cpu(ckpt->sit_ver_bitmap_bytesize);
nat_bitmap_size = le32_to_cpu(ckpt->nat_ver_bitmap_bytesize);
- log_blocks_per_seg = le32_to_cpu(raw_super->log_blocks_per_seg);
if (sit_bitmap_size != ((sit_segs / 2) << log_blocks_per_seg) / 8 ||
nat_bitmap_size != ((nat_segs / 2) << log_blocks_per_seg) / 8) {
@@ -2337,6 +2379,17 @@
return 1;
}
+ cp_pack_start_sum = __start_sum_addr(sbi);
+ cp_payload = __cp_payload(sbi);
+ if (cp_pack_start_sum < cp_payload + 1 ||
+ cp_pack_start_sum > blocks_per_seg - 1 -
+ NR_CURSEG_TYPE) {
+ f2fs_msg(sbi->sb, KERN_ERR,
+ "Wrong cp_pack_start_sum: %u",
+ cp_pack_start_sum);
+ return 1;
+ }
+
if (unlikely(f2fs_cp_error(sbi))) {
f2fs_msg(sbi->sb, KERN_ERR, "A bug case: need to run fsck");
return 1;
@@ -2674,6 +2727,8 @@
sm_i->dcc_info->discard_granularity = 1;
sm_i->ipu_policy = 1 << F2FS_IPU_FORCE;
}
+
+ sbi->readdir_ra = 1;
}
static int f2fs_fill_super(struct super_block *sb, void *data, int silent)
@@ -2723,9 +2778,6 @@
sb->s_fs_info = sbi;
sbi->raw_super = raw_super;
- F2FS_OPTION(sbi).s_resuid = make_kuid(&init_user_ns, F2FS_DEF_RESUID);
- F2FS_OPTION(sbi).s_resgid = make_kgid(&init_user_ns, F2FS_DEF_RESGID);
-
/* precompute checksum seed for metadata */
if (f2fs_sb_has_inode_chksum(sb))
sbi->s_chksum_seed = f2fs_chksum(sbi, ~0, raw_super->uuid,
@@ -2889,6 +2941,8 @@
f2fs_init_ino_entry_info(sbi);
+ f2fs_init_fsync_node_info(sbi);
+
/* setup f2fs internal modules */
err = f2fs_build_segment_manager(sbi);
if (err) {
@@ -2935,10 +2989,11 @@
err = PTR_ERR(root);
goto free_stats;
}
- if (!S_ISDIR(root->i_mode) || !root->i_blocks || !root->i_size) {
+ if (!S_ISDIR(root->i_mode) || !root->i_blocks ||
+ !root->i_size || !root->i_nlink) {
iput(root);
err = -EINVAL;
- goto free_node_inode;
+ goto free_stats;
}
sb->s_root = d_make_root(root); /* allocate root dentry */
@@ -2952,10 +3007,7 @@
goto free_root_inode;
#ifdef CONFIG_QUOTA
- /*
- * Turn on quotas which were not enabled for read-only mounts if
- * filesystem has quota feature, so that they are updated correctly.
- */
+ /* Enable quota usage during mount */
if (f2fs_sb_has_quota_ino(sb) && !f2fs_readonly(sb)) {
err = f2fs_enable_quotas(sb);
if (err) {
@@ -3113,9 +3165,19 @@
static void kill_f2fs_super(struct super_block *sb)
{
if (sb->s_root) {
- set_sbi_flag(F2FS_SB(sb), SBI_IS_CLOSE);
- f2fs_stop_gc_thread(F2FS_SB(sb));
- f2fs_stop_discard_thread(F2FS_SB(sb));
+ struct f2fs_sb_info *sbi = F2FS_SB(sb);
+
+ set_sbi_flag(sbi, SBI_IS_CLOSE);
+ f2fs_stop_gc_thread(sbi);
+ f2fs_stop_discard_thread(sbi);
+
+ if (is_sbi_flag_set(sbi, SBI_IS_DIRTY) ||
+ !is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG)) {
+ struct cp_control cpc = {
+ .reason = CP_UMOUNT,
+ };
+ f2fs_write_checkpoint(sbi, &cpc);
+ }
}
kill_block_super(sb);
}
diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
index e1b1b31..30fd016 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -9,6 +9,7 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
+#include <linux/compiler.h>
#include <linux/proc_fs.h>
#include <linux/f2fs_fs.h>
#include <linux/seq_file.h>
@@ -252,6 +253,7 @@
if (t >= 1) {
sbi->gc_mode = GC_URGENT;
if (sbi->gc_thread) {
+ sbi->gc_thread->gc_wake = 1;
wake_up_interruptible_all(
&sbi->gc_thread->gc_wait_queue_head);
wake_up_discard_thread(sbi, true);
@@ -286,8 +288,10 @@
bool gc_entry = (!strcmp(a->attr.name, "gc_urgent") ||
a->struct_type == GC_THREAD);
- if (gc_entry)
- down_read(&sbi->sb->s_umount);
+ if (gc_entry) {
+ if (!down_read_trylock(&sbi->sb->s_umount))
+ return -EAGAIN;
+ }
ret = __sbi_store(a, sbi, buf, count);
if (gc_entry)
up_read(&sbi->sb->s_umount);
@@ -518,7 +522,8 @@
.kset = &f2fs_kset,
};
-static int segment_info_seq_show(struct seq_file *seq, void *offset)
+static int __maybe_unused segment_info_seq_show(struct seq_file *seq,
+ void *offset)
{
struct super_block *sb = seq->private;
struct f2fs_sb_info *sbi = F2FS_SB(sb);
@@ -545,7 +550,8 @@
return 0;
}
-static int segment_bits_seq_show(struct seq_file *seq, void *offset)
+static int __maybe_unused segment_bits_seq_show(struct seq_file *seq,
+ void *offset)
{
struct super_block *sb = seq->private;
struct f2fs_sb_info *sbi = F2FS_SB(sb);
@@ -569,7 +575,8 @@
return 0;
}
-static int iostat_info_seq_show(struct seq_file *seq, void *offset)
+static int __maybe_unused iostat_info_seq_show(struct seq_file *seq,
+ void *offset)
{
struct super_block *sb = seq->private;
struct f2fs_sb_info *sbi = F2FS_SB(sb);
@@ -611,6 +618,28 @@
return 0;
}
+static int __maybe_unused victim_bits_seq_show(struct seq_file *seq,
+ void *offset)
+{
+ struct super_block *sb = seq->private;
+ struct f2fs_sb_info *sbi = F2FS_SB(sb);
+ struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
+ int i;
+
+ seq_puts(seq, "format: victim_secmap bitmaps\n");
+
+ for (i = 0; i < MAIN_SECS(sbi); i++) {
+ if ((i % 10) == 0)
+ seq_printf(seq, "%-10d", i);
+ seq_printf(seq, "%d", test_bit(i, dirty_i->victim_secmap) ? 1 : 0);
+ if ((i % 10) == 9 || i == (MAIN_SECS(sbi) - 1))
+ seq_putc(seq, '\n');
+ else
+ seq_putc(seq, ' ');
+ }
+ return 0;
+}
+
#define F2FS_PROC_FILE_DEF(_name) \
static int _name##_open_fs(struct inode *inode, struct file *file) \
{ \
@@ -627,6 +656,7 @@
F2FS_PROC_FILE_DEF(segment_info);
F2FS_PROC_FILE_DEF(segment_bits);
F2FS_PROC_FILE_DEF(iostat_info);
+F2FS_PROC_FILE_DEF(victim_bits);
int __init f2fs_init_sysfs(void)
{
@@ -677,6 +707,8 @@
&f2fs_seq_segment_bits_fops, sb);
proc_create_data("iostat_info", S_IRUGO, sbi->s_proc,
&f2fs_seq_iostat_info_fops, sb);
+ proc_create_data("victim_bits", S_IRUGO, sbi->s_proc,
+ &f2fs_seq_victim_bits_fops, sb);
}
return 0;
}
@@ -687,6 +719,7 @@
remove_proc_entry("iostat_info", sbi->s_proc);
remove_proc_entry("segment_info", sbi->s_proc);
remove_proc_entry("segment_bits", sbi->s_proc);
+ remove_proc_entry("victim_bits", sbi->s_proc);
remove_proc_entry(sbi->sb->s_id, f2fs_proc_root);
}
kobject_del(&sbi->s_kobj);
diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c
index 7082718..77a010e 100644
--- a/fs/f2fs/xattr.c
+++ b/fs/f2fs/xattr.c
@@ -37,9 +37,6 @@
return -EOPNOTSUPP;
break;
case F2FS_XATTR_INDEX_TRUSTED:
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
- break;
case F2FS_XATTR_INDEX_SECURITY:
break;
default:
@@ -62,9 +59,6 @@
return -EOPNOTSUPP;
break;
case F2FS_XATTR_INDEX_TRUSTED:
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
- break;
case F2FS_XATTR_INDEX_SECURITY:
break;
default:
@@ -100,12 +94,22 @@
const char *name, const void *value,
size_t size, int flags)
{
+ unsigned char old_advise = F2FS_I(inode)->i_advise;
+ unsigned char new_advise;
+
if (!inode_owner_or_capable(inode))
return -EPERM;
if (value == NULL)
return -EINVAL;
- F2FS_I(inode)->i_advise |= *(char *)value;
+ new_advise = *(char *)value;
+ if (new_advise & ~FADVISE_MODIFIABLE_BITS)
+ return -EINVAL;
+
+ new_advise = new_advise & FADVISE_MODIFIABLE_BITS;
+ new_advise |= old_advise & ~FADVISE_MODIFIABLE_BITS;
+
+ F2FS_I(inode)->i_advise = new_advise;
f2fs_mark_inode_dirty_sync(inode, true);
return 0;
}
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index eef0caf..e949551 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -1725,6 +1725,7 @@
if (status) {
op = &args->ops[0];
op->status = status;
+ resp->opcnt = 1;
goto encode_op;
}
diff --git a/fs/ocfs2/buffer_head_io.c b/fs/ocfs2/buffer_head_io.c
index 8f040f8..25c8b32 100644
--- a/fs/ocfs2/buffer_head_io.c
+++ b/fs/ocfs2/buffer_head_io.c
@@ -341,6 +341,7 @@
* for this bh as it's not marked locally
* uptodate. */
status = -EIO;
+ clear_buffer_needs_validate(bh);
put_bh(bh);
bhs[i] = NULL;
continue;
diff --git a/fs/ocfs2/dlm/dlmmaster.c b/fs/ocfs2/dlm/dlmmaster.c
index 3f828a1..0cc30a5 100644
--- a/fs/ocfs2/dlm/dlmmaster.c
+++ b/fs/ocfs2/dlm/dlmmaster.c
@@ -589,9 +589,9 @@
res->last_used = 0;
- spin_lock(&dlm->spinlock);
+ spin_lock(&dlm->track_lock);
list_add_tail(&res->tracking, &dlm->tracking_list);
- spin_unlock(&dlm->spinlock);
+ spin_unlock(&dlm->track_lock);
memset(res->lvb, 0, DLM_LVB_LEN);
memset(res->refmap, 0, sizeof(res->refmap));
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 0f23b3b..37e04a0 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -455,6 +455,20 @@
int err;
int i;
+ /*
+ * The ability to racily run the kernel stack unwinder on a running task
+ * and then observe the unwinder output is scary; while it is useful for
+ * debugging kernel issues, it can also allow an attacker to leak kernel
+ * stack contents.
+ * Doing this in a manner that is at least safe from races would require
+ * some work to ensure that the remote task can not be scheduled; and
+ * even then, this would still expose the unwinder as local attack
+ * surface.
+ * Therefore, this interface is restricted to root.
+ */
+ if (!file_ns_capable(m->file, &init_user_ns, CAP_SYS_ADMIN))
+ return -EACCES;
+
entries = kmalloc(MAX_STACK_TRACE_DEPTH * sizeof(*entries), GFP_KERNEL);
if (!entries)
return -ENOMEM;
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index c2afe39..4298a39 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -457,17 +457,12 @@
return inode;
}
-int proc_fill_super(struct super_block *s, void *data, int silent)
+int proc_fill_super(struct super_block *s)
{
- struct pid_namespace *ns = get_pid_ns(s->s_fs_info);
struct inode *root_inode;
int ret;
- if (!proc_parse_options(data, ns))
- return -EINVAL;
-
- /* User space would break if executables or devices appear on proc */
- s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
+ s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NODEV;
s->s_flags |= MS_NODIRATIME | MS_NOSUID | MS_NOEXEC;
s->s_blocksize = 1024;
s->s_blocksize_bits = 10;
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 03d8d14..d960512 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -214,7 +214,7 @@
extern void proc_init_inodecache(void);
extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
-extern int proc_fill_super(struct super_block *, void *data, int flags);
+extern int proc_fill_super(struct super_block *);
extern void proc_entry_rundown(struct proc_dir_entry *);
/*
@@ -281,7 +281,6 @@
* root.c
*/
extern struct proc_dir_entry proc_root;
-extern int proc_parse_options(char *options, struct pid_namespace *pid);
extern void proc_self_init(void);
extern int proc_remount(struct super_block *, int *, char *);
diff --git a/fs/proc/root.c b/fs/proc/root.c
index c2f5014..1d68fcd 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -23,6 +23,21 @@
#include "internal.h"
+static int proc_test_super(struct super_block *sb, void *data)
+{
+ return sb->s_fs_info == data;
+}
+
+static int proc_set_super(struct super_block *sb, void *data)
+{
+ int err = set_anon_super(sb, NULL);
+ if (!err) {
+ struct pid_namespace *ns = (struct pid_namespace *)data;
+ sb->s_fs_info = get_pid_ns(ns);
+ }
+ return err;
+}
+
enum {
Opt_gid, Opt_hidepid, Opt_err,
};
@@ -33,7 +48,7 @@
{Opt_err, NULL},
};
-int proc_parse_options(char *options, struct pid_namespace *pid)
+static int proc_parse_options(char *options, struct pid_namespace *pid)
{
char *p;
substring_t args[MAX_OPT_ARGS];
@@ -85,16 +100,45 @@
static struct dentry *proc_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
+ int err;
+ struct super_block *sb;
struct pid_namespace *ns;
+ char *options;
if (flags & MS_KERNMOUNT) {
- ns = data;
- data = NULL;
+ ns = (struct pid_namespace *)data;
+ options = NULL;
} else {
ns = task_active_pid_ns(current);
+ options = data;
+
+ /* Does the mounter have privilege over the pid namespace? */
+ if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN))
+ return ERR_PTR(-EPERM);
}
- return mount_ns(fs_type, flags, data, ns, ns->user_ns, proc_fill_super);
+ sb = sget(fs_type, proc_test_super, proc_set_super, flags, ns);
+ if (IS_ERR(sb))
+ return ERR_CAST(sb);
+
+ if (!proc_parse_options(options, ns)) {
+ deactivate_locked_super(sb);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (!sb->s_root) {
+ err = proc_fill_super(sb);
+ if (err) {
+ deactivate_locked_super(sb);
+ return ERR_PTR(err);
+ }
+
+ sb->s_flags |= MS_ACTIVE;
+ /* User space would break if executables appear on proc */
+ sb->s_iflags |= SB_I_NOEXEC;
+ }
+
+ return dget(sb->s_root);
}
static void proc_kill_sb(struct super_block *sb)
diff --git a/fs/sdcardfs/file.c b/fs/sdcardfs/file.c
index 1461254..271c4c4 100644
--- a/fs/sdcardfs/file.c
+++ b/fs/sdcardfs/file.c
@@ -118,7 +118,11 @@
goto out;
/* save current_cred and override it */
- OVERRIDE_CRED(sbi, saved_cred, SDCARDFS_I(file_inode(file)));
+ saved_cred = override_fsids(sbi, SDCARDFS_I(file_inode(file))->data);
+ if (!saved_cred) {
+ err = -ENOMEM;
+ goto out;
+ }
if (lower_file->f_op->unlocked_ioctl)
err = lower_file->f_op->unlocked_ioctl(lower_file, cmd, arg);
@@ -127,7 +131,7 @@
if (!err)
sdcardfs_copy_and_fix_attrs(file_inode(file),
file_inode(lower_file));
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out:
return err;
}
@@ -149,12 +153,16 @@
goto out;
/* save current_cred and override it */
- OVERRIDE_CRED(sbi, saved_cred, SDCARDFS_I(file_inode(file)));
+ saved_cred = override_fsids(sbi, SDCARDFS_I(file_inode(file))->data);
+ if (!saved_cred) {
+ err = -ENOMEM;
+ goto out;
+ }
if (lower_file->f_op->compat_ioctl)
err = lower_file->f_op->compat_ioctl(lower_file, cmd, arg);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out:
return err;
}
@@ -241,7 +249,11 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED(sbi, saved_cred, SDCARDFS_I(inode));
+ saved_cred = override_fsids(sbi, SDCARDFS_I(inode)->data);
+ if (!saved_cred) {
+ err = -ENOMEM;
+ goto out_err;
+ }
file->private_data =
kzalloc(sizeof(struct sdcardfs_file_info), GFP_KERNEL);
@@ -271,7 +283,7 @@
sdcardfs_copy_and_fix_attrs(inode, sdcardfs_lower_inode(inode));
out_revert_cred:
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_err:
dput(parent);
return err;
diff --git a/fs/sdcardfs/inode.c b/fs/sdcardfs/inode.c
index 30d4db2..7c08ffe 100644
--- a/fs/sdcardfs/inode.c
+++ b/fs/sdcardfs/inode.c
@@ -22,7 +22,6 @@
#include <linux/fs_struct.h>
#include <linux/ratelimit.h>
-/* Do not directly use this function. Use OVERRIDE_CRED() instead. */
const struct cred *override_fsids(struct sdcardfs_sb_info *sbi,
struct sdcardfs_inode_data *data)
{
@@ -50,7 +49,6 @@
return old_cred;
}
-/* Do not directly use this function, use REVERT_CRED() instead. */
void revert_fsids(const struct cred *old_cred)
{
const struct cred *cur_cred;
@@ -78,7 +76,10 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
+ saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb),
+ SDCARDFS_I(dir)->data);
+ if (!saved_cred)
+ return -ENOMEM;
sdcardfs_get_lower_path(dentry, &lower_path);
lower_dentry = lower_path.dentry;
@@ -95,8 +96,11 @@
err = -ENOMEM;
goto out_unlock;
}
+ copied_fs->umask = 0;
+ task_lock(current);
current->fs = copied_fs;
- current->fs->umask = 0;
+ task_unlock(current);
+
err = vfs_create2(lower_dentry_mnt, d_inode(lower_parent_dentry), lower_dentry, mode, want_excl);
if (err)
goto out;
@@ -110,58 +114,18 @@
fixup_lower_ownership(dentry, dentry->d_name.name);
out:
+ task_lock(current);
current->fs = saved_fs;
+ task_unlock(current);
free_fs_struct(copied_fs);
out_unlock:
unlock_dir(lower_parent_dentry);
sdcardfs_put_lower_path(dentry, &lower_path);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_eacces:
return err;
}
-#if 0
-static int sdcardfs_link(struct dentry *old_dentry, struct inode *dir,
- struct dentry *new_dentry)
-{
- struct dentry *lower_old_dentry;
- struct dentry *lower_new_dentry;
- struct dentry *lower_dir_dentry;
- u64 file_size_save;
- int err;
- struct path lower_old_path, lower_new_path;
-
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb));
-
- file_size_save = i_size_read(d_inode(old_dentry));
- sdcardfs_get_lower_path(old_dentry, &lower_old_path);
- sdcardfs_get_lower_path(new_dentry, &lower_new_path);
- lower_old_dentry = lower_old_path.dentry;
- lower_new_dentry = lower_new_path.dentry;
- lower_dir_dentry = lock_parent(lower_new_dentry);
-
- err = vfs_link(lower_old_dentry, d_inode(lower_dir_dentry),
- lower_new_dentry, NULL);
- if (err || !d_inode(lower_new_dentry))
- goto out;
-
- err = sdcardfs_interpose(new_dentry, dir->i_sb, &lower_new_path);
- if (err)
- goto out;
- fsstack_copy_attr_times(dir, d_inode(lower_new_dentry));
- fsstack_copy_inode_size(dir, d_inode(lower_new_dentry));
- set_nlink(d_inode(old_dentry),
- sdcardfs_lower_inode(d_inode(old_dentry))->i_nlink);
- i_size_write(d_inode(new_dentry), file_size_save);
-out:
- unlock_dir(lower_dir_dentry);
- sdcardfs_put_lower_path(old_dentry, &lower_old_path);
- sdcardfs_put_lower_path(new_dentry, &lower_new_path);
- REVERT_CRED();
- return err;
-}
-#endif
-
static int sdcardfs_unlink(struct inode *dir, struct dentry *dentry)
{
int err;
@@ -178,7 +142,10 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
+ saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb),
+ SDCARDFS_I(dir)->data);
+ if (!saved_cred)
+ return -ENOMEM;
sdcardfs_get_lower_path(dentry, &lower_path);
lower_dentry = lower_path.dentry;
@@ -209,43 +176,11 @@
unlock_dir(lower_dir_dentry);
dput(lower_dentry);
sdcardfs_put_lower_path(dentry, &lower_path);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_eacces:
return err;
}
-#if 0
-static int sdcardfs_symlink(struct inode *dir, struct dentry *dentry,
- const char *symname)
-{
- int err;
- struct dentry *lower_dentry;
- struct dentry *lower_parent_dentry = NULL;
- struct path lower_path;
-
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb));
-
- sdcardfs_get_lower_path(dentry, &lower_path);
- lower_dentry = lower_path.dentry;
- lower_parent_dentry = lock_parent(lower_dentry);
-
- err = vfs_symlink(d_inode(lower_parent_dentry), lower_dentry, symname);
- if (err)
- goto out;
- err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path);
- if (err)
- goto out;
- fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir));
- fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));
-
-out:
- unlock_dir(lower_parent_dentry);
- sdcardfs_put_lower_path(dentry, &lower_path);
- REVERT_CRED();
- return err;
-}
-#endif
-
static int touch(char *abs_path, mode_t mode)
{
struct file *filp = filp_open(abs_path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, mode);
@@ -287,7 +222,10 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
+ saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb),
+ SDCARDFS_I(dir)->data);
+ if (!saved_cred)
+ return -ENOMEM;
/* check disk space */
parent_dentry = dget_parent(dentry);
@@ -316,8 +254,11 @@
unlock_dir(lower_parent_dentry);
goto out_unlock;
}
+ copied_fs->umask = 0;
+ task_lock(current);
current->fs = copied_fs;
- current->fs->umask = 0;
+ task_unlock(current);
+
err = vfs_mkdir2(lower_mnt, d_inode(lower_parent_dentry), lower_dentry, mode);
if (err) {
@@ -366,23 +307,34 @@
if (make_nomedia_in_obb ||
((pd->perm == PERM_ANDROID)
&& (qstr_case_eq(&dentry->d_name, &q_data)))) {
- REVERT_CRED(saved_cred);
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(d_inode(dentry)));
+ revert_fsids(saved_cred);
+ saved_cred = override_fsids(sbi,
+ SDCARDFS_I(d_inode(dentry))->data);
+ if (!saved_cred) {
+ pr_err("sdcardfs: failed to set up .nomedia in %s: %d\n",
+ lower_path.dentry->d_name.name,
+ -ENOMEM);
+ goto out;
+ }
set_fs_pwd(current->fs, &lower_path);
touch_err = touch(".nomedia", 0664);
if (touch_err) {
pr_err("sdcardfs: failed to create .nomedia in %s: %d\n",
- lower_path.dentry->d_name.name, touch_err);
+ lower_path.dentry->d_name.name,
+ touch_err);
goto out;
}
}
out:
+ task_lock(current);
current->fs = saved_fs;
+ task_unlock(current);
+
free_fs_struct(copied_fs);
out_unlock:
sdcardfs_put_lower_path(dentry, &lower_path);
out_revert:
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_eacces:
return err;
}
@@ -402,7 +354,10 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
+ saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb),
+ SDCARDFS_I(dir)->data);
+ if (!saved_cred)
+ return -ENOMEM;
/* sdcardfs_get_real_lower(): in case of remove an user's obb dentry
* the dentry on the original path should be deleted.
@@ -427,44 +382,11 @@
out:
unlock_dir(lower_dir_dentry);
sdcardfs_put_real_lower(dentry, &lower_path);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_eacces:
return err;
}
-#if 0
-static int sdcardfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
- dev_t dev)
-{
- int err;
- struct dentry *lower_dentry;
- struct dentry *lower_parent_dentry = NULL;
- struct path lower_path;
-
- OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb));
-
- sdcardfs_get_lower_path(dentry, &lower_path);
- lower_dentry = lower_path.dentry;
- lower_parent_dentry = lock_parent(lower_dentry);
-
- err = vfs_mknod(d_inode(lower_parent_dentry), lower_dentry, mode, dev);
- if (err)
- goto out;
-
- err = sdcardfs_interpose(dentry, dir->i_sb, &lower_path);
- if (err)
- goto out;
- fsstack_copy_attr_times(dir, sdcardfs_lower_inode(dir));
- fsstack_copy_inode_size(dir, d_inode(lower_parent_dentry));
-
-out:
- unlock_dir(lower_parent_dentry);
- sdcardfs_put_lower_path(dentry, &lower_path);
- REVERT_CRED();
- return err;
-}
-#endif
-
/*
* The locking rules in sdcardfs_rename are complex. We could use a simpler
* superblock-level name-space lock for renames and copy-ups.
@@ -493,7 +415,10 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED(SDCARDFS_SB(old_dir->i_sb), saved_cred, SDCARDFS_I(new_dir));
+ saved_cred = override_fsids(SDCARDFS_SB(old_dir->i_sb),
+ SDCARDFS_I(new_dir)->data);
+ if (!saved_cred)
+ return -ENOMEM;
sdcardfs_get_real_lower(old_dentry, &lower_old_path);
sdcardfs_get_lower_path(new_dentry, &lower_new_path);
@@ -540,7 +465,7 @@
dput(lower_new_dir_dentry);
sdcardfs_put_real_lower(old_dentry, &lower_old_path);
sdcardfs_put_lower_path(new_dentry, &lower_new_path);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_eacces:
return err;
}
@@ -659,33 +584,7 @@
if (IS_POSIXACL(inode))
pr_warn("%s: This may be undefined behavior...\n", __func__);
err = generic_permission(&tmp, mask);
- /* XXX
- * Original sdcardfs code calls inode_permission(lower_inode,.. )
- * for checking inode permission. But doing such things here seems
- * duplicated work, because the functions called after this func,
- * such as vfs_create, vfs_unlink, vfs_rename, and etc,
- * does exactly same thing, i.e., they calls inode_permission().
- * So we just let they do the things.
- * If there are any security hole, just uncomment following if block.
- */
-#if 0
- if (!err) {
- /*
- * Permission check on lower_inode(=EXT4).
- * we check it with AID_MEDIA_RW permission
- */
- struct inode *lower_inode;
-
- OVERRIDE_CRED(SDCARDFS_SB(inode->sb));
-
- lower_inode = sdcardfs_lower_inode(inode);
- err = inode_permission(lower_inode, mask);
-
- REVERT_CRED();
- }
-#endif
return err;
-
}
static int sdcardfs_setattr_wrn(struct dentry *dentry, struct iattr *ia)
@@ -763,7 +662,10 @@
goto out_err;
/* save current_cred and override it */
- OVERRIDE_CRED(SDCARDFS_SB(dentry->d_sb), saved_cred, SDCARDFS_I(inode));
+ saved_cred = override_fsids(SDCARDFS_SB(dentry->d_sb),
+ SDCARDFS_I(inode)->data);
+ if (!saved_cred)
+ return -ENOMEM;
sdcardfs_get_lower_path(dentry, &lower_path);
lower_dentry = lower_path.dentry;
@@ -822,7 +724,7 @@
out:
sdcardfs_put_lower_path(dentry, &lower_path);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_err:
return err;
}
@@ -905,13 +807,6 @@
.setattr = sdcardfs_setattr_wrn,
.setattr2 = sdcardfs_setattr,
.getattr = sdcardfs_getattr,
- /* XXX Following operations are implemented,
- * but FUSE(sdcard) or FAT does not support them
- * These methods are *NOT* perfectly tested.
- .symlink = sdcardfs_symlink,
- .link = sdcardfs_link,
- .mknod = sdcardfs_mknod,
- */
};
const struct inode_operations sdcardfs_main_iops = {
diff --git a/fs/sdcardfs/lookup.c b/fs/sdcardfs/lookup.c
index 98051996..beec63b 100644
--- a/fs/sdcardfs/lookup.c
+++ b/fs/sdcardfs/lookup.c
@@ -426,7 +426,12 @@
}
/* save current_cred and override it */
- OVERRIDE_CRED_PTR(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
+ saved_cred = override_fsids(SDCARDFS_SB(dir->i_sb),
+ SDCARDFS_I(dir)->data);
+ if (!saved_cred) {
+ ret = ERR_PTR(-ENOMEM);
+ goto out_err;
+ }
sdcardfs_get_lower_path(parent, &lower_parent_path);
@@ -457,7 +462,7 @@
out:
sdcardfs_put_lower_path(parent, &lower_parent_path);
- REVERT_CRED(saved_cred);
+ revert_fsids(saved_cred);
out_err:
dput(parent);
return ret;
diff --git a/fs/sdcardfs/sdcardfs.h b/fs/sdcardfs/sdcardfs.h
index 826afb5..ec2290a 100644
--- a/fs/sdcardfs/sdcardfs.h
+++ b/fs/sdcardfs/sdcardfs.h
@@ -88,31 +88,6 @@
(x)->i_mode = ((x)->i_mode & S_IFMT) | 0775;\
} while (0)
-/* OVERRIDE_CRED() and REVERT_CRED()
- * OVERRIDE_CRED()
- * backup original task->cred
- * and modifies task->cred->fsuid/fsgid to specified value.
- * REVERT_CRED()
- * restore original task->cred->fsuid/fsgid.
- * These two macro should be used in pair, and OVERRIDE_CRED() should be
- * placed at the beginning of a function, right after variable declaration.
- */
-#define OVERRIDE_CRED(sdcardfs_sbi, saved_cred, info) \
- do { \
- saved_cred = override_fsids(sdcardfs_sbi, info->data); \
- if (!saved_cred) \
- return -ENOMEM; \
- } while (0)
-
-#define OVERRIDE_CRED_PTR(sdcardfs_sbi, saved_cred, info) \
- do { \
- saved_cred = override_fsids(sdcardfs_sbi, info->data); \
- if (!saved_cred) \
- return ERR_PTR(-ENOMEM); \
- } while (0)
-
-#define REVERT_CRED(saved_cred) revert_fsids(saved_cred)
-
/* Android 5.0 support */
/* Permission mode for a specific node. Controls how file permissions
diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c
index 03dda1c..727a9e3 100644
--- a/fs/ubifs/super.c
+++ b/fs/ubifs/super.c
@@ -1918,6 +1918,9 @@
int dev, vol;
char *endptr;
+ if (!name || !*name)
+ return ERR_PTR(-EINVAL);
+
/* First, try to open using the device node path method */
ubi = ubi_open_volume_path(name, mode);
if (!IS_ERR(ubi))
diff --git a/fs/xattr.c b/fs/xattr.c
index 2c2c28e..1c91835 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -953,17 +953,19 @@
int err = 0;
#ifdef CONFIG_FS_POSIX_ACL
- if (inode->i_acl) {
- err = xattr_list_one(&buffer, &remaining_size,
- XATTR_NAME_POSIX_ACL_ACCESS);
- if (err)
- return err;
- }
- if (inode->i_default_acl) {
- err = xattr_list_one(&buffer, &remaining_size,
- XATTR_NAME_POSIX_ACL_DEFAULT);
- if (err)
- return err;
+ if (IS_POSIXACL(inode)) {
+ if (inode->i_acl) {
+ err = xattr_list_one(&buffer, &remaining_size,
+ XATTR_NAME_POSIX_ACL_ACCESS);
+ if (err)
+ return err;
+ }
+ if (inode->i_default_acl) {
+ err = xattr_list_one(&buffer, &remaining_size,
+ XATTR_NAME_POSIX_ACL_DEFAULT);
+ if (err)
+ return err;
+ }
}
#endif
diff --git a/include/Kbuild b/include/Kbuild
index bab1145..9205b04 100644
--- a/include/Kbuild
+++ b/include/Kbuild
@@ -1,2 +1,6 @@
# Top-level Makefile calls into asm-$(ARCH)
# List only non-arch directories below
+
+ifneq ($(VSERVICES_SUPPORT), "")
+header-y += vservices/
+endif
diff --git a/include/linux/Kbuild.vservices b/include/linux/Kbuild.vservices
new file mode 100644
index 0000000..392f559
--- /dev/null
+++ b/include/linux/Kbuild.vservices
@@ -0,0 +1,3 @@
+#
+# Virtual Services headers which need to be exported for user-space
+#
diff --git a/include/linux/arm-smccc.h b/include/linux/arm-smccc.h
index 43072b1..9f721ee 100644
--- a/include/linux/arm-smccc.h
+++ b/include/linux/arm-smccc.h
@@ -201,47 +201,57 @@
#define __declare_arg_0(a0, res) \
struct arm_smccc_res *___res = res; \
- register u32 r0 SMCCC_REG(0) = a0; \
+ register unsigned long r0 SMCCC_REG(0) = (u32)a0; \
register unsigned long r1 SMCCC_REG(1); \
register unsigned long r2 SMCCC_REG(2); \
register unsigned long r3 SMCCC_REG(3)
#define __declare_arg_1(a0, a1, res) \
+ typeof(a1) __a1 = a1; \
struct arm_smccc_res *___res = res; \
- register u32 r0 SMCCC_REG(0) = a0; \
- register typeof(a1) r1 SMCCC_REG(1) = a1; \
+ register unsigned long r0 SMCCC_REG(0) = (u32)a0; \
+ register unsigned long r1 SMCCC_REG(1) = __a1; \
register unsigned long r2 SMCCC_REG(2); \
register unsigned long r3 SMCCC_REG(3)
#define __declare_arg_2(a0, a1, a2, res) \
+ typeof(a1) __a1 = a1; \
+ typeof(a2) __a2 = a2; \
struct arm_smccc_res *___res = res; \
- register u32 r0 SMCCC_REG(0) = a0; \
- register typeof(a1) r1 SMCCC_REG(1) = a1; \
- register typeof(a2) r2 SMCCC_REG(2) = a2; \
+ register unsigned long r0 SMCCC_REG(0) = (u32)a0; \
+ register unsigned long r1 SMCCC_REG(1) = __a1; \
+ register unsigned long r2 SMCCC_REG(2) = __a2; \
register unsigned long r3 SMCCC_REG(3)
#define __declare_arg_3(a0, a1, a2, a3, res) \
+ typeof(a1) __a1 = a1; \
+ typeof(a2) __a2 = a2; \
+ typeof(a3) __a3 = a3; \
struct arm_smccc_res *___res = res; \
- register u32 r0 SMCCC_REG(0) = a0; \
- register typeof(a1) r1 SMCCC_REG(1) = a1; \
- register typeof(a2) r2 SMCCC_REG(2) = a2; \
- register typeof(a3) r3 SMCCC_REG(3) = a3
+ register unsigned long r0 SMCCC_REG(0) = (u32)a0; \
+ register unsigned long r1 SMCCC_REG(1) = __a1; \
+ register unsigned long r2 SMCCC_REG(2) = __a2; \
+ register unsigned long r3 SMCCC_REG(3) = __a3
#define __declare_arg_4(a0, a1, a2, a3, a4, res) \
+ typeof(a4) __a4 = a4; \
__declare_arg_3(a0, a1, a2, a3, res); \
- register typeof(a4) r4 SMCCC_REG(4) = a4
+ register unsigned long r4 SMCCC_REG(4) = __a4
#define __declare_arg_5(a0, a1, a2, a3, a4, a5, res) \
+ typeof(a5) __a5 = a5; \
__declare_arg_4(a0, a1, a2, a3, a4, res); \
- register typeof(a5) r5 SMCCC_REG(5) = a5
+ register unsigned long r5 SMCCC_REG(5) = __a5
#define __declare_arg_6(a0, a1, a2, a3, a4, a5, a6, res) \
+ typeof(a6) __a6 = a6; \
__declare_arg_5(a0, a1, a2, a3, a4, a5, res); \
- register typeof(a6) r6 SMCCC_REG(6) = a6
+ register unsigned long r6 SMCCC_REG(6) = __a6
#define __declare_arg_7(a0, a1, a2, a3, a4, a5, a6, a7, res) \
+ typeof(a7) __a7 = a7; \
__declare_arg_6(a0, a1, a2, a3, a4, a5, a6, res); \
- register typeof(a7) r7 SMCCC_REG(7) = a7
+ register unsigned long r7 SMCCC_REG(7) = __a7
#define ___declare_args(count, ...) __declare_arg_ ## count(__VA_ARGS__)
#define __declare_args(count, ...) ___declare_args(count, __VA_ARGS__)
diff --git a/include/linux/f2fs_fs.h b/include/linux/f2fs_fs.h
index aa5db8b..f70f8ac 100644
--- a/include/linux/f2fs_fs.h
+++ b/include/linux/f2fs_fs.h
@@ -304,11 +304,6 @@
* For NAT entries
*/
#define NAT_ENTRY_PER_BLOCK (PAGE_SIZE / sizeof(struct f2fs_nat_entry))
-#define NAT_ENTRY_BITMAP_SIZE ((NAT_ENTRY_PER_BLOCK + 7) / 8)
-#define NAT_ENTRY_BITMAP_SIZE_ALIGNED \
- ((NAT_ENTRY_BITMAP_SIZE + BITS_PER_LONG - 1) / \
- BITS_PER_LONG * BITS_PER_LONG)
-
struct f2fs_nat_entry {
__u8 version; /* latest version of cached nat entry */
diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h
index c2a0f00..734377a 100644
--- a/include/linux/jiffies.h
+++ b/include/linux/jiffies.h
@@ -292,6 +292,8 @@
return (u64)jiffies_to_usecs(j) * NSEC_PER_USEC;
}
+extern u64 jiffies64_to_nsecs(u64 j);
+
extern unsigned long __msecs_to_jiffies(const unsigned int m);
#if HZ <= MSEC_PER_SEC && !(MSEC_PER_SEC % HZ)
/*
diff --git a/include/linux/netfilter_bridge/ebtables.h b/include/linux/netfilter_bridge/ebtables.h
index 984b211..ea8a977 100644
--- a/include/linux/netfilter_bridge/ebtables.h
+++ b/include/linux/netfilter_bridge/ebtables.h
@@ -123,4 +123,9 @@
/* True if the target is not a standard target */
#define INVALID_TARGET (info->target < -NUM_STANDARD_TARGETS || info->target >= 0)
+static inline bool ebt_invalid_target(int target)
+{
+ return (target < -NUM_STANDARD_TARGETS || target >= 0);
+}
+
#endif
diff --git a/include/linux/platform_data/ina2xx.h b/include/linux/platform_data/ina2xx.h
index 9abc0ca..9f0aa1b 100644
--- a/include/linux/platform_data/ina2xx.h
+++ b/include/linux/platform_data/ina2xx.h
@@ -1,7 +1,7 @@
/*
* Driver for Texas Instruments INA219, INA226 power monitor chips
*
- * Copyright (C) 2012 Lothar Felten <l-felten@ti.com>
+ * Copyright (C) 2012 Lothar Felten <lothar.felten@gmail.com>
*
* 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
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 4c07788..731dcef 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -443,6 +443,7 @@
spinlock_t changed_lock;
bool changed;
bool initialized;
+ bool removing;
atomic_t use_cnt;
#ifdef CONFIG_THERMAL
struct thermal_zone_device *tzd;
diff --git a/include/linux/slub_def.h b/include/linux/slub_def.h
index 75f56c2..b6a59e8 100644
--- a/include/linux/slub_def.h
+++ b/include/linux/slub_def.h
@@ -67,7 +67,8 @@
int size; /* The size of an object including meta data */
int object_size; /* The size of an object without meta data */
int offset; /* Free pointer offset. */
- int cpu_partial; /* Number of per cpu partial objects to keep around */
+ /* Number of per cpu partial objects to keep around */
+ unsigned int cpu_partial;
struct kmem_cache_order_objects oo;
/* Allocation and freeing of slabs */
diff --git a/include/linux/tty.h b/include/linux/tty.h
index fe1b862..bcfeb9e 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -313,6 +313,10 @@
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
+#if defined(CONFIG_TTY_FLUSH_LOCAL_ECHO)
+ int delayed_work;
+ struct delayed_work echo_delayed_work;
+#endif
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
diff --git a/include/media/v4l2-fh.h b/include/media/v4l2-fh.h
index e19e624..d267160 100644
--- a/include/media/v4l2-fh.h
+++ b/include/media/v4l2-fh.h
@@ -42,10 +42,13 @@
* @prio: priority of the file handler, as defined by &enum v4l2_priority
*
* @wait: event' s wait queue
+ * @subscribe_lock: serialise changes to the subscribed list; guarantee that
+ * the add and del event callbacks are orderly called
* @subscribed: list of subscribed events
* @available: list of events waiting to be dequeued
* @navailable: number of available events at @available list
* @sequence: event sequence number
+ *
* @m2m_ctx: pointer to &struct v4l2_m2m_ctx
*/
struct v4l2_fh {
@@ -56,6 +59,7 @@
/* Events */
wait_queue_head_t wait;
+ struct mutex subscribe_lock;
struct list_head subscribed;
struct list_head available;
unsigned int navailable;
diff --git a/include/net/nfc/hci.h b/include/net/nfc/hci.h
index 316694d..008f466 100644
--- a/include/net/nfc/hci.h
+++ b/include/net/nfc/hci.h
@@ -87,7 +87,7 @@
* According to specification 102 622 chapter 4.4 Pipes,
* the pipe identifier is 7 bits long.
*/
-#define NFC_HCI_MAX_PIPES 127
+#define NFC_HCI_MAX_PIPES 128
struct nfc_hci_init_data {
u8 gate_count;
struct nfc_hci_gate gates[NFC_HCI_MAX_CUSTOM_GATES];
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 435cee5..61c6ecb 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -515,3 +515,7 @@
header-y += msm_rotator.h
header-y += bgcom_interface.h
header-y += nfc/
+
+ifneq ($(VSERVICES_SUPPORT), "")
+include include/linux/Kbuild.vservices
+endif
diff --git a/include/vservices/Kbuild b/include/vservices/Kbuild
new file mode 100644
index 0000000..8b955fc
--- /dev/null
+++ b/include/vservices/Kbuild
@@ -0,0 +1,2 @@
+header-y += protocol/
+header-y += ioctl.h
diff --git a/include/vservices/buffer.h b/include/vservices/buffer.h
new file mode 100644
index 0000000..910aa07
--- /dev/null
+++ b/include/vservices/buffer.h
@@ -0,0 +1,239 @@
+/*
+ * include/vservices/buffer.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This file defines simple wrapper types for strings and variable-size buffers
+ * that are stored inside Virtual Services message buffers.
+ */
+
+#ifndef _VSERVICES_BUFFER_H_
+#define _VSERVICES_BUFFER_H_
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+struct vs_mbuf;
+
+/**
+ * struct vs_string - Virtual Services fixed sized string type
+ * @ptr: String pointer
+ * @max_size: Maximum length of the string in bytes
+ *
+ * A handle to a possibly NUL-terminated string stored in a message buffer. If
+ * the size of the string equals to max_size, the string is not NUL-terminated.
+ * If the protocol does not specify an encoding, the encoding is assumed to be
+ * UTF-8. Wide character encodings are not supported by this type; use struct
+ * vs_pbuf for wide character strings.
+ */
+struct vs_string {
+ char *ptr;
+ size_t max_size;
+};
+
+/**
+ * vs_string_copyout - Copy a Virtual Services string to a C string buffer.
+ * @dest: C string to copy to
+ * @src: Virtual Services string to copy from
+ * @max_size: Size of the destination buffer, including the NUL terminator.
+ *
+ * The behaviour is similar to strlcpy(): that is, the copied string
+ * is guaranteed not to exceed the specified size (including the NUL
+ * terminator byte), and is guaranteed to be NUL-terminated as long as
+ * the size is nonzero (unlike strncpy()).
+ *
+ * The return value is the size of the input string (even if the output was
+ * truncated); this is to make truncation easy to detect.
+ */
+static inline size_t
+vs_string_copyout(char *dest, const struct vs_string *src, size_t max_size)
+{
+ size_t src_len = strnlen(src->ptr, src->max_size);
+
+ if (max_size) {
+ size_t dest_len = min(src_len, max_size - 1);
+
+ memcpy(dest, src->ptr, dest_len);
+ dest[dest_len] = '\0';
+ }
+ return src_len;
+}
+
+/**
+ * vs_string_copyin_len - Copy a C string, up to a given length, into a Virtual
+ * Services string.
+ * @dest: Virtual Services string to copy to
+ * @src: C string to copy from
+ * @max_size: Maximum number of bytes to copy
+ *
+ * Returns the number of bytes copied, which may be less than the input
+ * string's length.
+ */
+static inline size_t
+vs_string_copyin_len(struct vs_string *dest, const char *src, size_t max_size)
+{
+ strncpy(dest->ptr, src, min(max_size, dest->max_size));
+
+ return strnlen(dest->ptr, dest->max_size);
+}
+
+/**
+ * vs_string_copyin - Copy a C string into a Virtual Services string.
+ * @dest: Virtual Services string to copy to
+ * @src: C string to copy from
+ *
+ * Returns the number of bytes copied, which may be less than the input
+ * string's length.
+ */
+static inline size_t
+vs_string_copyin(struct vs_string *dest, const char *src)
+{
+ return vs_string_copyin_len(dest, src, dest->max_size);
+}
+
+/**
+ * vs_string_length - Return the size of the string stored in a Virtual Services
+ * string.
+ * @str: Virtual Service string to get the length of
+ */
+static inline size_t
+vs_string_length(struct vs_string *str)
+{
+ return strnlen(str->ptr, str->max_size);
+}
+
+/**
+ * vs_string_dup - Allocate a C string buffer and copy a Virtual Services string
+ * into it.
+ * @str: Virtual Services string to duplicate
+ */
+static inline char *
+vs_string_dup(struct vs_string *str, gfp_t gfp)
+{
+ size_t len;
+ char *ret;
+
+ len = strnlen(str->ptr, str->max_size) + 1;
+ ret = kmalloc(len, gfp);
+ if (ret)
+ vs_string_copyout(ret, str, len);
+ return ret;
+}
+
+/**
+ * vs_string_max_size - Return the maximum size of a Virtual Services string,
+ * not including the NUL terminator if the lenght of the
+ * string is equal to max_size.
+ *
+ * @str Virtual Services string to return the maximum size of.
+ *
+ * @return The maximum size of the string.
+ */
+static inline size_t
+vs_string_max_size(struct vs_string *str)
+{
+ return str->max_size;
+}
+
+/**
+ * struct vs_pbuf - Handle to a variable-size buffered payload.
+ * @data: Data buffer
+ * @size: Current size of the buffer
+ * @max_size: Maximum size of the buffer
+ *
+ * This is similar to struct vs_string, except that has an explicitly
+ * stored size rather than being null-terminated. The functions that
+ * return ssize_t all return the new size of the modified buffer, and
+ * will return a negative size if the buffer overflows.
+ */
+struct vs_pbuf {
+ void *data;
+ size_t size, max_size;
+};
+
+/**
+ * vs_pbuf_size - Get the size of a pbuf
+ * @pbuf: pbuf to get the size of
+ */
+static inline size_t vs_pbuf_size(const struct vs_pbuf *pbuf)
+{
+ return pbuf->size;
+}
+
+/**
+ * vs_pbuf_data - Get the data pointer for a a pbuf
+ * @pbuf: pbuf to get the data pointer for
+ */
+static inline const void *vs_pbuf_data(const struct vs_pbuf *pbuf)
+{
+ return pbuf->data;
+}
+
+/**
+ * vs_pbuf_resize - Resize a pbuf
+ * @pbuf: pbuf to resize
+ * @size: New size
+ */
+static inline ssize_t vs_pbuf_resize(struct vs_pbuf *pbuf, size_t size)
+{
+ if (size > pbuf->max_size)
+ return -EOVERFLOW;
+
+ pbuf->size = size;
+ return size;
+}
+
+/**
+ * vs_pbuf_copyin - Copy data into a pbuf
+ * @pbuf: pbuf to copy data into
+ * @offset: Offset to copy data to
+ * @data: Pointer to data to copy into the pbuf
+ * @nbytes: Number of bytes to copy into the pbuf
+ */
+static inline ssize_t vs_pbuf_copyin(struct vs_pbuf *pbuf, off_t offset,
+ const void *data, size_t nbytes)
+{
+ if (offset + nbytes > pbuf->size)
+ return -EOVERFLOW;
+
+ memcpy(pbuf->data + offset, data, nbytes);
+
+ return nbytes;
+}
+
+/**
+ * vs_pbuf_append - Append data to a pbuf
+ * @pbuf: pbuf to append to
+ * @data: Pointer to data to append to the pbuf
+ * @nbytes: Number of bytes to append
+ */
+static inline ssize_t vs_pbuf_append(struct vs_pbuf *pbuf,
+ const void *data, size_t nbytes)
+{
+ if (pbuf->size + nbytes > pbuf->max_size)
+ return -EOVERFLOW;
+
+ memcpy(pbuf->data + pbuf->size, data, nbytes);
+ pbuf->size += nbytes;
+
+ return pbuf->size;
+}
+
+/**
+ * vs_pbuf_dup_string - Duplicate the contents of a pbuf as a C string. The
+ * string is allocated and must be freed using kfree.
+ * @pbuf: pbuf to convert
+ * @gfp_flags: GFP flags for the string allocation
+ */
+static inline char *vs_pbuf_dup_string(struct vs_pbuf *pbuf, gfp_t gfp_flags)
+{
+ return kstrndup(pbuf->data, pbuf->size, gfp_flags);
+}
+
+#endif /* _VSERVICES_BUFFER_H_ */
diff --git a/include/vservices/ioctl.h b/include/vservices/ioctl.h
new file mode 100644
index 0000000..d96fcab
--- /dev/null
+++ b/include/vservices/ioctl.h
@@ -0,0 +1,48 @@
+/*
+ * vservices/ioctl.h - Interface to service character devices
+ *
+ * Copyright (c) 2016, Cog Systems Pty Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_PUBLIC_VSERVICES_IOCTL_H__
+#define __LINUX_PUBLIC_VSERVICES_IOCTL_H__
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+
+/* ioctls that work on any opened service device */
+#define IOCTL_VS_RESET_SERVICE _IO('4', 0)
+#define IOCTL_VS_GET_NAME _IOR('4', 1, char[16])
+#define IOCTL_VS_GET_PROTOCOL _IOR('4', 2, char[32])
+
+/*
+ * Claim a device for user I/O (if no kernel driver is attached). The claim
+ * persists until the char device is closed.
+ */
+struct vs_ioctl_bind {
+ __u32 send_quota;
+ __u32 recv_quota;
+ __u32 send_notify_bits;
+ __u32 recv_notify_bits;
+ size_t msg_size;
+};
+#define IOCTL_VS_BIND_CLIENT _IOR('4', 3, struct vs_ioctl_bind)
+#define IOCTL_VS_BIND_SERVER _IOWR('4', 4, struct vs_ioctl_bind)
+
+/* send and receive messages and notifications */
+#define IOCTL_VS_NOTIFY _IOW('4', 5, __u32)
+struct vs_ioctl_iovec {
+ union {
+ __u32 iovcnt; /* input */
+ __u32 notify_bits; /* output (recv only) */
+ };
+ struct iovec *iov;
+};
+#define IOCTL_VS_SEND _IOW('4', 6, struct vs_ioctl_iovec)
+#define IOCTL_VS_RECV _IOWR('4', 7, struct vs_ioctl_iovec)
+
+#endif /* __LINUX_PUBLIC_VSERVICES_IOCTL_H__ */
diff --git a/include/vservices/protocol/Kbuild b/include/vservices/protocol/Kbuild
new file mode 100644
index 0000000..374d9b6
--- /dev/null
+++ b/include/vservices/protocol/Kbuild
@@ -0,0 +1,12 @@
+#
+# Find all of the protocol directory names, and get the basename followed
+# by a trailing slash.
+#
+protocols=$(shell find include/vservices/protocol/ -mindepth 1 -type d -exec basename {} \;)
+protocol_dirs=$(foreach p, $(protocols), $(p)/)
+
+#
+# Export the headers for all protocols. The kbuild file in each protocol
+# directory specifies exactly which headers to export.
+#
+header-y += $(protocol_dirs)
diff --git a/include/vservices/protocol/core.h b/include/vservices/protocol/core.h
new file mode 100644
index 0000000..3a86af5
--- /dev/null
+++ b/include/vservices/protocol/core.h
@@ -0,0 +1,145 @@
+/*
+ * include/vservices/protocol/core.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * These are the common generated definitions for the core protocol drivers;
+ * specifically the message IDs and the protocol state representation.
+ *
+ * This is currently hand-generated, but will eventually be autogenerated,
+ * from the protocol specifications in core.vs. Please keep it consistent
+ * with that file.
+ */
+
+#define VSERVICE_CORE_PROTOCOL_NAME "com.ok-labs.core"
+#define VSERVICE_CORE_PARAM_SIZE_SERVICE_INFO__PROTOCOL_NAME 32
+#define VSERVICE_CORE_PARAM_SIZE_SERVICE_INFO__SERVICE_NAME 16
+
+/*
+ * Identifiers for in-band messages.
+ *
+ * This definition applies in both directions, because there is no practical
+ * limit on message IDs (services are unlikely to define 2^16 distinct message
+ * names).
+ */
+typedef enum {
+ /** simple_protocol core **/
+ /* message out startup */
+ VSERVICE_CORE_MSG_STARTUP,
+
+ /* message out shutdown */
+ VSERVICE_CORE_MSG_SHUTDOWN,
+
+ /* command in sync connect */
+ VSERVICE_CORE_REQ_CONNECT,
+ VSERVICE_CORE_ACK_CONNECT,
+ VSERVICE_CORE_NACK_CONNECT,
+
+ /* command in sync disconnect */
+ VSERVICE_CORE_REQ_DISCONNECT,
+ VSERVICE_CORE_ACK_DISCONNECT,
+ VSERVICE_CORE_NACK_DISCONNECT,
+
+ /* command in service_count */
+ VSERVICE_CORE_REQ_SERVICE_COUNT,
+ VSERVICE_CORE_ACK_SERVICE_COUNT,
+ VSERVICE_CORE_NACK_SERVICE_COUNT,
+
+ /* command in queued service_info */
+ VSERVICE_CORE_REQ_SERVICE_INFO,
+ VSERVICE_CORE_ACK_SERVICE_INFO,
+ VSERVICE_CORE_NACK_SERVICE_INFO,
+
+ /* message inout service_reset */
+ VSERVICE_CORE_MSG_SERVICE_RESET,
+
+ /* message inout service_ready */
+ VSERVICE_CORE_MSG_SERVICE_READY,
+
+ /* message out notification bits */
+ VSERVICE_CORE_MSG_NOTIFICATION_BITS_INFO,
+
+} vservice_core_message_id_t;
+
+/*
+ * Notification bits are defined separately for each direction because there
+ * is relatively limited space to allocate them from (specifically, the bits in
+ * a machine word). It is unlikely but possible for a protocol to reach this
+ * limit.
+ */
+
+/* Bits in the in (client -> server) notification bitmask. */
+typedef enum {
+ /** simple_protocol core **/
+ /* No in notifications */
+
+ VSERVICE_CORE_NBIT_IN__COUNT = 0,
+} vservice_core_nbit_in_t;
+
+/* Masks for the in notification bits */
+/* No in notifications */
+
+/* Bits in the out (server -> client) notification bitmask. */
+typedef enum {
+ /** simple_protocol core **/
+ /* notification out reenumerate */
+ VSERVICE_CORE_NBIT_OUT_REENUMERATE = 0,
+
+ VSERVICE_CORE_NBIT_OUT__COUNT,
+} vservice_core_nbit_out_t;
+
+/* Masks for the out notification bits */
+#define VSERVICE_CORE_NMASK_OUT_REENUMERATE \
+ (1 << VSERVICE_CORE_NBIT_OUT_REENUMERATE)
+
+/* Valid states of the interface's generated state machine. */
+typedef enum {
+ /* state offline */
+ VSERVICE_CORE_STATE_OFFLINE = 0,
+
+ /* state disconnected */
+ VSERVICE_CORE_STATE_DISCONNECTED,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT,
+
+ /* state connected */
+ VSERVICE_CORE_STATE_CONNECTED,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT,
+
+ /* reset offline */
+ VSERVICE_CORE_STATE__RESET = VSERVICE_CORE_STATE_OFFLINE,
+} vservice_core_statenum_t;
+
+typedef struct {
+ vservice_core_statenum_t statenum;
+ bool pending_service_count;
+ unsigned pending_service_info;
+} vservice_core_state_t;
+
+#define VSERVICE_CORE_RESET_STATE (vservice_core_state_t) { \
+ .statenum = VSERVICE_CORE_STATE__RESET, \
+ .pending_service_count = false, \
+ .pending_service_info = 0 }
+
+#define VSERVICE_CORE_STATE_IS_OFFLINE(state) ( \
+ ((state).statenum == VSERVICE_CORE_STATE_OFFLINE))
+#define VSERVICE_CORE_STATE_IS_DISCONNECTED(state) ( \
+ ((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED) || \
+ ((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED__CONNECT))
+#define VSERVICE_CORE_STATE_IS_CONNECTED(state) ( \
+ ((state).statenum == VSERVICE_CORE_STATE_CONNECTED) || \
+ ((state).statenum == VSERVICE_CORE_STATE_CONNECTED__DISCONNECT))
+
+#define VSERVICE_CORE_STATE_VALID(state) \
+ VSERVICE_CORE_STATE_IS_OFFLINE(state) ? ( \
+ ((state).pending_service_count == false) && \
+ ((state).pending_service_info == 0)) : \
+ VSERVICE_CORE_STATE_IS_DISCONNECTED(state) ? ( \
+ ((state).pending_service_count == false) && \
+ ((state).pending_service_info == 0)) : \
+ VSERVICE_CORE_STATE_IS_CONNECTED(state) ? true : \
+ false)
diff --git a/include/vservices/protocol/core/Kbuild b/include/vservices/protocol/core/Kbuild
new file mode 100644
index 0000000..ec3cbe8
--- /dev/null
+++ b/include/vservices/protocol/core/Kbuild
@@ -0,0 +1 @@
+header-y += types.h
diff --git a/include/vservices/protocol/core/client.h b/include/vservices/protocol/core/client.h
new file mode 100644
index 0000000..3d52999
--- /dev/null
+++ b/include/vservices/protocol/core/client.h
@@ -0,0 +1,155 @@
+
+/*
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ */
+
+#if !defined(__VSERVICES_CLIENT_CORE__)
+#define __VSERVICES_CLIENT_CORE__
+
+struct vs_service_device;
+struct vs_client_core_state;
+
+struct vs_client_core {
+
+ /*
+ * If set to false then the receive message handlers are run from
+ * workqueue context and are allowed to sleep. If set to true the
+ * message handlers are run from tasklet context and may not sleep.
+ */
+ bool rx_atomic;
+
+ /*
+ * If this is set to true along with rx_atomic, the driver is allowed
+ * to send messages from softirq contexts other than the receive
+ * message handlers, after calling vs_service_state_lock_bh. Otherwise,
+ * messages may only be sent from the receive message handlers, or
+ * from task context after calling vs_service_state_lock. This must
+ * not be set to true if rx_atomic is set to false.
+ */
+ bool tx_atomic;
+ /** session setup **/
+ struct vs_client_core_state *(*alloc) (struct vs_service_device *
+ service);
+ void (*release) (struct vs_client_core_state * _state);
+
+ struct vs_service_driver *driver;
+
+ /** Core service base interface **/
+ void (*start) (struct vs_client_core_state * _state);
+ void (*reset) (struct vs_client_core_state * _state);
+ /** Send/receive state callbacks **/
+ int (*tx_ready) (struct vs_client_core_state * _state);
+
+ struct {
+ int (*state_change) (struct vs_client_core_state * _state,
+ vservice_core_statenum_t old,
+ vservice_core_statenum_t new);
+
+ int (*ack_connect) (struct vs_client_core_state * _state);
+ int (*nack_connect) (struct vs_client_core_state * _state);
+
+ int (*ack_disconnect) (struct vs_client_core_state * _state);
+ int (*nack_disconnect) (struct vs_client_core_state * _state);
+
+ int (*msg_startup) (struct vs_client_core_state * _state,
+ uint32_t core_in_quota,
+ uint32_t core_out_quota);
+
+ int (*msg_shutdown) (struct vs_client_core_state * _state);
+
+ int (*msg_service_created) (struct vs_client_core_state *
+ _state, uint32_t service_id,
+ struct vs_string service_name,
+ struct vs_string protocol_name,
+ struct vs_mbuf * _mbuf);
+
+ int (*msg_service_removed) (struct vs_client_core_state *
+ _state, uint32_t service_id);
+
+ int (*msg_server_ready) (struct vs_client_core_state * _state,
+ uint32_t service_id, uint32_t in_quota,
+ uint32_t out_quota,
+ uint32_t in_bit_offset,
+ uint32_t in_num_bits,
+ uint32_t out_bit_offset,
+ uint32_t out_num_bits);
+
+ int (*msg_service_reset) (struct vs_client_core_state * _state,
+ uint32_t service_id);
+
+ } core;
+};
+
+struct vs_client_core_state {
+ vservice_core_protocol_state_t state;
+ struct vs_service_device *service;
+ bool released;
+};
+
+extern int vs_client_core_reopen(struct vs_client_core_state *_state);
+
+extern int vs_client_core_close(struct vs_client_core_state *_state);
+
+ /** interface core **/
+/* command sync connect */
+extern int vs_client_core_core_req_connect(struct vs_client_core_state *_state,
+ gfp_t flags);
+
+ /* command sync disconnect */
+extern int vs_client_core_core_req_disconnect(struct vs_client_core_state
+ *_state, gfp_t flags);
+
+ /* message startup */
+/* message shutdown */
+/* message service_created */
+extern int vs_client_core_core_getbufs_service_created(struct
+ vs_client_core_state
+ *_state,
+ struct vs_string
+ *service_name,
+ struct vs_string
+ *protocol_name,
+ struct vs_mbuf *_mbuf);
+extern int vs_client_core_core_free_service_created(struct vs_client_core_state
+ *_state,
+ struct vs_string
+ *service_name,
+ struct vs_string
+ *protocol_name,
+ struct vs_mbuf *_mbuf);
+ /* message service_removed */
+/* message server_ready */
+/* message service_reset */
+extern int vs_client_core_core_send_service_reset(struct vs_client_core_state
+ *_state, uint32_t service_id,
+ gfp_t flags);
+
+/** Module registration **/
+
+struct module;
+
+extern int __vservice_core_client_register(struct vs_client_core *client,
+ const char *name,
+ struct module *owner);
+
+static inline int vservice_core_client_register(struct vs_client_core *client,
+ const char *name)
+{
+#ifdef MODULE
+ extern struct module __this_module;
+ struct module *this_module = &__this_module;
+#else
+ struct module *this_module = NULL;
+#endif
+
+ return __vservice_core_client_register(client, name, this_module);
+}
+
+extern int vservice_core_client_unregister(struct vs_client_core *client);
+
+#endif /* ! __VSERVICES_CLIENT_CORE__ */
diff --git a/include/vservices/protocol/core/common.h b/include/vservices/protocol/core/common.h
new file mode 100644
index 0000000..b496416
--- /dev/null
+++ b/include/vservices/protocol/core/common.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ */
+
+#if !defined(__VSERVICES_CORE_PROTOCOL_H__)
+#define __VSERVICES_CORE_PROTOCOL_H__
+
+#define VSERVICE_CORE_PROTOCOL_NAME "com.ok-labs.core"
+typedef enum {
+ VSERVICE_CORE_CORE_REQ_CONNECT,
+ VSERVICE_CORE_CORE_ACK_CONNECT,
+ VSERVICE_CORE_CORE_NACK_CONNECT,
+ VSERVICE_CORE_CORE_REQ_DISCONNECT,
+ VSERVICE_CORE_CORE_ACK_DISCONNECT,
+ VSERVICE_CORE_CORE_NACK_DISCONNECT,
+ VSERVICE_CORE_CORE_MSG_STARTUP,
+ VSERVICE_CORE_CORE_MSG_SHUTDOWN,
+ VSERVICE_CORE_CORE_MSG_SERVICE_CREATED,
+ VSERVICE_CORE_CORE_MSG_SERVICE_REMOVED,
+ VSERVICE_CORE_CORE_MSG_SERVER_READY,
+ VSERVICE_CORE_CORE_MSG_SERVICE_RESET,
+} vservice_core_message_id_t;
+typedef enum {
+ VSERVICE_CORE_NBIT_IN__COUNT
+} vservice_core_nbit_in_t;
+
+typedef enum {
+ VSERVICE_CORE_NBIT_OUT__COUNT
+} vservice_core_nbit_out_t;
+
+/* Notification mask macros */
+#endif /* ! __VSERVICES_CORE_PROTOCOL_H__ */
diff --git a/include/vservices/protocol/core/server.h b/include/vservices/protocol/core/server.h
new file mode 100644
index 0000000..959b8c3
--- /dev/null
+++ b/include/vservices/protocol/core/server.h
@@ -0,0 +1,171 @@
+
+/*
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ */
+
+#if !defined(VSERVICES_SERVER_CORE)
+#define VSERVICES_SERVER_CORE
+
+struct vs_service_device;
+struct vs_server_core_state;
+
+struct vs_server_core {
+
+ /*
+ * If set to false then the receive message handlers are run from
+ * workqueue context and are allowed to sleep. If set to true the
+ * message handlers are run from tasklet context and may not sleep.
+ */
+ bool rx_atomic;
+
+ /*
+ * If this is set to true along with rx_atomic, the driver is allowed
+ * to send messages from softirq contexts other than the receive
+ * message handlers, after calling vs_service_state_lock_bh. Otherwise,
+ * messages may only be sent from the receive message handlers, or
+ * from task context after calling vs_service_state_lock. This must
+ * not be set to true if rx_atomic is set to false.
+ */
+ bool tx_atomic;
+
+ /*
+ * These are the driver's recommended message quotas. They are used
+ * by the core service to select message quotas for services with no
+ * explicitly configured quotas.
+ */
+ u32 in_quota_best;
+ u32 out_quota_best;
+ /** session setup **/
+ struct vs_server_core_state *(*alloc) (struct vs_service_device *
+ service);
+ void (*release) (struct vs_server_core_state * _state);
+
+ struct vs_service_driver *driver;
+
+ /** Core service base interface **/
+ void (*start) (struct vs_server_core_state * _state);
+ void (*reset) (struct vs_server_core_state * _state);
+ /** Send/receive state callbacks **/
+ int (*tx_ready) (struct vs_server_core_state * _state);
+
+ struct {
+ int (*state_change) (struct vs_server_core_state * _state,
+ vservice_core_statenum_t old,
+ vservice_core_statenum_t new);
+
+ int (*req_connect) (struct vs_server_core_state * _state);
+
+ int (*req_disconnect) (struct vs_server_core_state * _state);
+
+ int (*msg_service_reset) (struct vs_server_core_state * _state,
+ uint32_t service_id);
+
+ } core;
+};
+
+struct vs_server_core_state {
+ vservice_core_protocol_state_t state;
+ struct vs_service_device *service;
+ bool released;
+};
+
+/** Complete calls for server core functions **/
+
+ /** interface core **/
+/* command sync connect */
+extern int vs_server_core_core_send_ack_connect(struct vs_server_core_state
+ *_state, gfp_t flags);
+extern int vs_server_core_core_send_nack_connect(struct vs_server_core_state
+ *_state, gfp_t flags);
+ /* command sync disconnect */
+extern int vs_server_core_core_send_ack_disconnect(struct vs_server_core_state
+ *_state, gfp_t flags);
+extern int vs_server_core_core_send_nack_disconnect(struct vs_server_core_state
+ *_state, gfp_t flags);
+ /* message startup */
+extern int vs_server_core_core_send_startup(struct vs_server_core_state *_state,
+ uint32_t core_in_quota,
+ uint32_t core_out_quota,
+ gfp_t flags);
+
+ /* message shutdown */
+extern int vs_server_core_core_send_shutdown(struct vs_server_core_state
+ *_state, gfp_t flags);
+
+ /* message service_created */
+extern struct vs_mbuf *vs_server_core_core_alloc_service_created(struct
+ vs_server_core_state
+ *_state,
+ struct
+ vs_string
+ *service_name,
+ struct
+ vs_string
+ *protocol_name,
+ gfp_t flags);
+extern int vs_server_core_core_free_service_created(struct vs_server_core_state
+ *_state,
+ struct vs_string
+ *service_name,
+ struct vs_string
+ *protocol_name,
+ struct vs_mbuf *_mbuf);
+extern int vs_server_core_core_send_service_created(struct vs_server_core_state
+ *_state,
+ uint32_t service_id,
+ struct vs_string
+ service_name,
+ struct vs_string
+ protocol_name,
+ struct vs_mbuf *_mbuf);
+
+ /* message service_removed */
+extern int vs_server_core_core_send_service_removed(struct vs_server_core_state
+ *_state,
+ uint32_t service_id,
+ gfp_t flags);
+
+ /* message server_ready */
+extern int vs_server_core_core_send_server_ready(struct vs_server_core_state
+ *_state, uint32_t service_id,
+ uint32_t in_quota,
+ uint32_t out_quota,
+ uint32_t in_bit_offset,
+ uint32_t in_num_bits,
+ uint32_t out_bit_offset,
+ uint32_t out_num_bits,
+ gfp_t flags);
+
+ /* message service_reset */
+extern int vs_server_core_core_send_service_reset(struct vs_server_core_state
+ *_state, uint32_t service_id,
+ gfp_t flags);
+
+/** Module registration **/
+
+struct module;
+
+extern int __vservice_core_server_register(struct vs_server_core *server,
+ const char *name,
+ struct module *owner);
+
+static inline int vservice_core_server_register(struct vs_server_core *server,
+ const char *name)
+{
+#ifdef MODULE
+ extern struct module __this_module;
+ struct module *this_module = &__this_module;
+#else
+ struct module *this_module = NULL;
+#endif
+
+ return __vservice_core_server_register(server, name, this_module);
+}
+
+extern int vservice_core_server_unregister(struct vs_server_core *server);
+#endif /* ! VSERVICES_SERVER_CORE */
diff --git a/include/vservices/protocol/core/types.h b/include/vservices/protocol/core/types.h
new file mode 100644
index 0000000..2d6928d
--- /dev/null
+++ b/include/vservices/protocol/core/types.h
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ */
+
+#if !defined(VSERVICES_CORE_TYPES_H)
+#define VSERVICES_CORE_TYPES_H
+
+#define VSERVICE_CORE_SERVICE_NAME_SIZE (uint32_t)16
+
+#define VSERVICE_CORE_PROTOCOL_NAME_SIZE (uint32_t)32
+
+typedef enum {
+/* state offline */
+ VSERVICE_CORE_STATE_OFFLINE = 0,
+ VSERVICE_CORE_STATE_OFFLINE__CONNECT,
+ VSERVICE_CORE_STATE_OFFLINE__DISCONNECT,
+
+/* state disconnected */
+ VSERVICE_CORE_STATE_DISCONNECTED,
+ VSERVICE_CORE_STATE_DISCONNECTED__CONNECT,
+ VSERVICE_CORE_STATE_DISCONNECTED__DISCONNECT,
+
+/* state connected */
+ VSERVICE_CORE_STATE_CONNECTED,
+ VSERVICE_CORE_STATE_CONNECTED__CONNECT,
+ VSERVICE_CORE_STATE_CONNECTED__DISCONNECT,
+
+ VSERVICE_CORE__RESET = VSERVICE_CORE_STATE_OFFLINE
+} vservice_core_statenum_t;
+
+typedef struct {
+ vservice_core_statenum_t statenum;
+} vservice_core_state_t;
+
+#define VSERVICE_CORE_RESET_STATE (vservice_core_state_t) { \
+.statenum = VSERVICE_CORE__RESET}
+
+#define VSERVICE_CORE_STATE_IS_OFFLINE(state) (\
+((state).statenum == VSERVICE_CORE_STATE_OFFLINE) || \
+((state).statenum == VSERVICE_CORE_STATE_OFFLINE__CONNECT) || \
+((state).statenum == VSERVICE_CORE_STATE_OFFLINE__DISCONNECT))
+
+#define VSERVICE_CORE_STATE_IS_DISCONNECTED(state) (\
+((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED) || \
+((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED__CONNECT) || \
+((state).statenum == VSERVICE_CORE_STATE_DISCONNECTED__DISCONNECT))
+
+#define VSERVICE_CORE_STATE_IS_CONNECTED(state) (\
+((state).statenum == VSERVICE_CORE_STATE_CONNECTED) || \
+((state).statenum == VSERVICE_CORE_STATE_CONNECTED__CONNECT) || \
+((state).statenum == VSERVICE_CORE_STATE_CONNECTED__DISCONNECT))
+
+#define VSERVICE_CORE_STATE_VALID(state) ( \
+VSERVICE_CORE_STATE_IS_OFFLINE(state) ? true : \
+VSERVICE_CORE_STATE_IS_DISCONNECTED(state) ? true : \
+VSERVICE_CORE_STATE_IS_CONNECTED(state) ? true : \
+false)
+
+static inline const char *vservice_core_get_state_string(vservice_core_state_t
+ state)
+{
+ static const char *names[] =
+ { "offline", "offline__connect", "offline__disconnect",
+ "disconnected", "disconnected__connect",
+ "disconnected__disconnect",
+ "connected", "connected__connect", "connected__disconnect"
+ };
+ if (!VSERVICE_CORE_STATE_VALID(state)) {
+ return "INVALID";
+ }
+ return names[state.statenum];
+}
+
+typedef struct {
+
+ vservice_core_state_t core;
+} vservice_core_protocol_state_t;
+
+#define VSERVICE_CORE_PROTOCOL_RESET_STATE (vservice_core_protocol_state_t) {\
+.core = VSERVICE_CORE_RESET_STATE }
+#endif /* ! VSERVICES_CORE_TYPES_H */
diff --git a/include/vservices/service.h b/include/vservices/service.h
new file mode 100644
index 0000000..af232b6
--- /dev/null
+++ b/include/vservices/service.h
@@ -0,0 +1,674 @@
+/*
+ * include/vservices/service.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This file defines the driver and device types for vServices client and
+ * server drivers. These are generally defined by generated protocol-layer
+ * code. However, they can also be defined directly by applications that
+ * don't require protocol generation.
+ */
+
+#ifndef _VSERVICE_SERVICE_H_
+#define _VSERVICE_SERVICE_H_
+
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 38)
+#include <asm/atomic.h>
+#else
+#include <linux/atomic.h>
+#endif
+
+#include <vservices/transport.h>
+#include <vservices/session.h>
+#include <vservices/types.h>
+
+struct vs_mbuf;
+
+/**
+ * struct vs_service_driver - Virtual service driver structure
+ * @protocol: Protocol name for this driver
+ * @is_server: True if this is a server driver, false if it is a client driver
+ * @rx_atomic: If set to false then the receive message handlers are run from
+ * workqueue context and are allowed to sleep. If set to true
+ * the message handlers are run from tasklet context and may not
+ * sleep. For this purpose, tx_ready is considered a receive
+ * message handler.
+ * @tx_atomic: If this is set to true along with rx_atomic, the driver is
+ * allowed to send messages from softirq contexts other than the receive
+ * message handlers, after calling vs_service_state_lock_bh. Otherwise,
+ * messages may only be sent from the receive message handlers, or from
+ * task context after calling vs_service_state_lock.
+ * @probe: Probe function for this service
+ * @remove: Remove function for this service
+ * --- Callbacks ---
+ * @receive: Message handler function for this service
+ * @notify: Incoming notification handler function for this service
+ * @start: Callback which is run when this service is started
+ * @reset: Callback which is run when this service is reset
+ * @tx_ready: Callback which is run when the service has dropped below its
+ * send quota
+ * --- Resource requirements (valid for server only) ---
+ * @in_quota_min: minimum number of input messages for protocol functionality
+ * @in_quota_best: suggested number of input messages
+ * @out_quota_min: minimum number of output messages for protocol functionality
+ * @out_quota_best: suggested number of output messages
+ * @in_notify_count: number of input notification bits used
+ * @out_notify_count: number of output notification bits used
+ * --- Internal ---
+ * @driver: Linux device model driver structure
+ *
+ * The callback functions for a virtual service driver are all called from
+ * the virtual service device's work queue.
+ */
+struct vs_service_driver {
+ const char *protocol;
+ bool is_server;
+ bool rx_atomic, tx_atomic;
+
+ int (*probe)(struct vs_service_device *service);
+ int (*remove)(struct vs_service_device *service);
+
+ int (*receive)(struct vs_service_device *service,
+ struct vs_mbuf *mbuf);
+ void (*notify)(struct vs_service_device *service, u32 flags);
+
+ void (*start)(struct vs_service_device *service);
+ void (*reset)(struct vs_service_device *service);
+
+ int (*tx_ready)(struct vs_service_device *service);
+
+ unsigned in_quota_min;
+ unsigned in_quota_best;
+ unsigned out_quota_min;
+ unsigned out_quota_best;
+ unsigned in_notify_count;
+ unsigned out_notify_count;
+
+ struct device_driver driver;
+};
+
+#define to_vs_service_driver(d) \
+ container_of(d, struct vs_service_driver, driver)
+
+/* The vServices server/client bus types */
+extern struct bus_type vs_client_bus_type;
+extern struct bus_type vs_server_bus_type;
+
+/**
+ * struct vs_service_stats - Virtual service statistics
+ * @over_quota_time: Internal counter for tracking over quota time.
+ * @sent_mbufs: Total number of message buffers sent.
+ * @sent_bytes: Total bytes sent.
+ * @send_failures: Total number of send failures.
+ * @recv_mbufs: Total number of message buffers received.
+ * @recv_bytes: Total number of bytes recevied.
+ * @recv_failures: Total number of receive failures.
+ * @nr_over_quota: Number of times an mbuf allocation has failed because the
+ * service is over quota.
+ * @nr_tx_ready: Number of times the service has run its tx_ready handler
+ * @over_quota_time_total: The total amount of time in milli-seconds that the
+ * service has spent over quota. Measured as the time
+ * between exceeding quota in mbuf allocation and
+ * running the tx_ready handler.
+ * @over_quota_time_avg: The average amount of time in milli-seconds that the
+ * service is spending in the over quota state.
+ */
+struct vs_service_stats {
+ unsigned long over_quota_time;
+
+ atomic_t sent_mbufs;
+ atomic_t sent_bytes;
+ atomic_t send_failures;
+ atomic_t recv_mbufs;
+ atomic_t recv_bytes;
+ atomic_t recv_failures;
+ atomic_t nr_over_quota;
+ atomic_t nr_tx_ready;
+ atomic_t over_quota_time_total;
+ atomic_t over_quota_time_avg;
+};
+
+/**
+ * struct vs_service_device - Virtual service device
+ * @id: Unique ID (to the session) for this service
+ * @name: Service name
+ * @sysfs_name: The sysfs name for the service
+ * @protocol: Service protocol name
+ * @is_server: True if this device is server, false if it is a client
+ * @owner: service responsible for managing this service. This must be
+ * on the same session, and is NULL iff this is the core service.
+ * It must not be a service whose driver has tx_atomic set.
+ * @lock_subclass: the number of generations of owners between this service
+ * and the core service; 0 for the core service, 1 for anything directly
+ * created by it, and so on. This is only used for verifying lock
+ * ordering (when lockdep is enabled), hence the name.
+ * @ready_lock: mutex protecting readiness, disable_count and driver_probed.
+ * This depends on the state_mutex of the service's owner, if any. Acquire
+ * it using mutex_lock_nested(ready_lock, lock_subclass).
+ * @readiness: Service's readiness state, owned by session layer.
+ * @disable_count: Number of times the service has been disabled without
+ * a matching enable.
+ * @driver_probed: True if a driver has been probed (and not removed)
+ * @work_queue: Work queue for this service's task-context work.
+ * @rx_tasklet: Tasklet for handling incoming messages. This is only used
+ * if the service driver has rx_atomic set to true. Otherwise
+ * incoming messages are handled on the workqueue by rx_work.
+ * @rx_work: Work structure for handling incoming messages. This is only
+ * used if the service driver has rx_atomic set to false.
+ * @rx_lock: Spinlock which protects access to rx_queue and tx_ready
+ * @rx_queue: Queue of incoming messages
+ * @tx_ready: Flag indicating that a tx_ready event is pending
+ * @tx_batching: Flag indicating that outgoing messages are being batched
+ * @state_spinlock: spinlock used to protect the service state if the
+ * service driver has tx_atomic (and rx_atomic) set to true. This
+ * depends on the service's ready_lock. Acquire it only by
+ * calling vs_service_state_lock_bh().
+ * @state_mutex: mutex used to protect the service state if the service
+ * driver has tx_atomic set to false. This depends on the service's
+ * ready_lock, and if rx_atomic is true, the rx_tasklet must be
+ * disabled while it is held. Acquire it only by calling
+ * vs_service_state_lock().
+ * @state_spinlock_used: Flag to check if the state spinlock has been acquired.
+ * @state_mutex_used: Flag to check if the state mutex has been acquired.
+ * @reset_work: Work to reset the service after a driver fails
+ * @pending_reset: Set if reset_work has been queued and not completed.
+ * @ready_work: Work to make service ready after a throttling delay
+ * @cooloff_work: Work for cooling off reset throttling after the reset
+ * throttling limit was hit
+ * @cleanup_work: Work for cleaning up and freeing the service structure
+ * @last_reset: Time in jiffies at which this service last reset
+ * @last_reset_request: Time in jiffies the last reset request for this
+ * service occurred at
+ * @last_ready: Time in jiffies at which this service last became ready
+ * @reset_delay: Time in jiffies that the next throttled reset will be
+ * delayed for. A value of zero means that reset throttling is not in
+ * effect.
+ * @is_over_quota: Internal flag for whether the service is over quota. This
+ * flag is only used for stats accounting.
+ * @quota_wq: waitqueue that is woken whenever the available send quota
+ * increases.
+ * @notify_send_bits: The number of bits allocated for outgoing notifications.
+ * @notify_send_offset: The first bit allocated for outgoing notifications.
+ * @notify_recv_bits: The number of bits allocated for incoming notifications.
+ * @notify_recv_offset: The first bit allocated for incoming notifications.
+ * @send_quota: The maximum number of outgoing messages.
+ * @recv_quota: The maximum number of incoming messages.
+ * @in_quota_set: For servers, the number of client->server messages
+ * requested during system configuration (sysfs or environment).
+ * @out_quota_set: For servers, the number of server->client messages
+ * requested during system configuration (sysfs or environment).
+ * @dev: Linux device model device structure
+ * @stats: Service statistics
+ */
+struct vs_service_device {
+ vs_service_id_t id;
+ char *name;
+ char *sysfs_name;
+ char *protocol;
+ bool is_server;
+
+ struct vs_service_device *owner;
+ unsigned lock_subclass;
+
+ struct mutex ready_lock;
+ unsigned readiness;
+ int disable_count;
+ bool driver_probed;
+
+ struct workqueue_struct *work_queue;
+
+ struct tasklet_struct rx_tasklet;
+ struct work_struct rx_work;
+
+ spinlock_t rx_lock;
+ struct list_head rx_queue;
+ bool tx_ready, tx_batching;
+
+ spinlock_t state_spinlock;
+ struct mutex state_mutex;
+
+ struct work_struct reset_work;
+ bool pending_reset;
+ struct delayed_work ready_work;
+ struct delayed_work cooloff_work;
+ struct work_struct cleanup_work;
+
+ unsigned long last_reset;
+ unsigned long last_reset_request;
+ unsigned long last_ready;
+ unsigned long reset_delay;
+
+ atomic_t is_over_quota;
+ wait_queue_head_t quota_wq;
+
+ unsigned notify_send_bits;
+ unsigned notify_send_offset;
+ unsigned notify_recv_bits;
+ unsigned notify_recv_offset;
+ unsigned send_quota;
+ unsigned recv_quota;
+
+ unsigned in_quota_set;
+ unsigned out_quota_set;
+
+ void *transport_priv;
+
+ struct device dev;
+ struct vs_service_stats stats;
+
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ bool state_spinlock_used;
+ bool state_mutex_used;
+#endif
+};
+
+#define to_vs_service_device(d) container_of(d, struct vs_service_device, dev)
+
+/**
+ * vs_service_get_session - Return the session for a service
+ * @service: Service to get the session for
+ */
+static inline struct vs_session_device *
+vs_service_get_session(struct vs_service_device *service)
+{
+ return to_vs_session_device(service->dev.parent);
+}
+
+/**
+ * vs_service_send - Send a message from a service
+ * @service: Service to send the message from
+ * @mbuf: Message buffer to send
+ */
+static inline int
+vs_service_send(struct vs_service_device *service, struct vs_mbuf *mbuf)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ const struct vs_transport_vtable *vt = session->transport->vt;
+ const unsigned long flags =
+ service->tx_batching ? VS_TRANSPORT_SEND_FLAGS_MORE : 0;
+ size_t msg_size = vt->mbuf_size(mbuf);
+ int err;
+
+ err = vt->send(session->transport, service, mbuf, flags);
+ if (!err) {
+ atomic_inc(&service->stats.sent_mbufs);
+ atomic_add(msg_size, &service->stats.sent_bytes);
+ } else {
+ atomic_inc(&service->stats.send_failures);
+ }
+
+ return err;
+}
+
+/**
+ * vs_service_alloc_mbuf - Allocate a message buffer for a service
+ * @service: Service to allocate the buffer for
+ * @size: Size of the data buffer to allocate
+ * @flags: Flags to pass to the buffer allocation
+ */
+static inline struct vs_mbuf *
+vs_service_alloc_mbuf(struct vs_service_device *service, size_t size,
+ gfp_t flags)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+ struct vs_mbuf *mbuf;
+
+ mbuf = session->transport->vt->alloc_mbuf(session->transport,
+ service, size, flags);
+ if (IS_ERR(mbuf) && PTR_ERR(mbuf) == -ENOBUFS) {
+ /* Over quota accounting */
+ if (atomic_cmpxchg(&service->is_over_quota, 0, 1) == 0) {
+ service->stats.over_quota_time = jiffies;
+ atomic_inc(&service->stats.nr_over_quota);
+ }
+ }
+
+ /*
+ * The transport drivers should return either a valid message buffer
+ * pointer or an ERR_PTR value. Warn here if a transport driver is
+ * returning NULL on message buffer allocation failure.
+ */
+ if (WARN_ON_ONCE(!mbuf))
+ return ERR_PTR(-ENOMEM);
+
+ return mbuf;
+}
+
+/**
+ * vs_service_free_mbuf - Deallocate a message buffer for a service
+ * @service: Service the message buffer was allocated for
+ * @mbuf: Message buffer to deallocate
+ */
+static inline void
+vs_service_free_mbuf(struct vs_service_device *service, struct vs_mbuf *mbuf)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ session->transport->vt->free_mbuf(session->transport, service, mbuf);
+}
+
+/**
+ * vs_service_notify - Send a notification from a service
+ * @service: Service to send the notification from
+ * @flags: Notification bits to send
+ */
+static inline int
+vs_service_notify(struct vs_service_device *service, u32 flags)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ return session->transport->vt->notify(session->transport,
+ service, flags);
+}
+
+/**
+ * vs_service_has_atomic_rx - Return whether or not a service's receive
+ * message handler runs in atomic context. This function should only be
+ * called for services which are bound to a driver.
+ *
+ * @service: Service to check
+ */
+static inline bool
+vs_service_has_atomic_rx(struct vs_service_device *service)
+{
+ if (WARN_ON(!service->dev.driver))
+ return false;
+
+ return to_vs_service_driver(service->dev.driver)->rx_atomic;
+}
+
+/**
+ * vs_session_max_mbuf_size - Return the maximum allocation size of a message
+ * buffer.
+ * @service: The service to check
+ */
+static inline size_t
+vs_service_max_mbuf_size(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ return session->transport->vt->max_mbuf_size(session->transport);
+}
+
+/**
+ * vs_service_send_mbufs_available - Return the number of mbufs which can be
+ * allocated for sending before going over quota.
+ * @service: The service to check
+ */
+static inline ssize_t
+vs_service_send_mbufs_available(struct vs_service_device *service)
+{
+ struct vs_session_device *session = vs_service_get_session(service);
+
+ return session->transport->vt->service_send_avail(session->transport,
+ service);
+}
+
+/**
+ * vs_service_has_atomic_tx - Return whether or not a service is allowed to
+ * transmit from atomic context (other than its receive message handler).
+ * This function should only be called for services which are bound to a
+ * driver.
+ *
+ * @service: Service to check
+ */
+static inline bool
+vs_service_has_atomic_tx(struct vs_service_device *service)
+{
+ if (WARN_ON(!service->dev.driver))
+ return false;
+
+ return to_vs_service_driver(service->dev.driver)->tx_atomic;
+}
+
+/**
+ * vs_service_state_lock - Acquire a lock allowing service state operations
+ * from external task contexts.
+ *
+ * @service: Service to lock.
+ *
+ * This must be used to protect any service state accesses that occur in task
+ * contexts outside of a callback from the vservices protocol layer. It must
+ * not be called from a protocol layer callback, nor from atomic context.
+ *
+ * If this service's state is also accessed from softirq contexts other than
+ * vservices protocol layer callbacks, use vs_service_state_lock_bh instead,
+ * and set the driver's tx_atomic flag.
+ *
+ * If this is called from outside the service's workqueue, the calling driver
+ * must provide its own guarantee that it has not been detached from the
+ * service. If that is not possible, use vs_state_lock_safe().
+ */
+static inline void
+vs_service_state_lock(struct vs_service_device *service)
+__acquires(service)
+{
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ WARN_ON_ONCE(vs_service_has_atomic_tx(service));
+#endif
+
+ mutex_lock_nested(&service->state_mutex, service->lock_subclass);
+
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ if (WARN_ON_ONCE(service->state_spinlock_used))
+ dev_err(&service->dev, "Service is using both the state spinlock and mutex - Fix your driver\n");
+ service->state_mutex_used = true;
+#endif
+
+ if (vs_service_has_atomic_rx(service))
+ tasklet_disable(&service->rx_tasklet);
+
+ __acquire(service);
+}
+
+/**
+ * vs_service_state_unlock - Release the lock acquired by vs_service_state_lock.
+ *
+ * @service: Service to unlock.
+ */
+static inline void
+vs_service_state_unlock(struct vs_service_device *service)
+__releases(service)
+{
+ __release(service);
+
+ mutex_unlock(&service->state_mutex);
+
+ if (vs_service_has_atomic_rx(service)) {
+ tasklet_enable(&service->rx_tasklet);
+
+ /* Kick the tasklet if there is RX work to do */
+ if (!list_empty(&service->rx_queue))
+ tasklet_schedule(&service->rx_tasklet);
+ }
+}
+
+/**
+ * vs_service_state_lock_bh - Acquire a lock allowing service state operations
+ * from external task or softirq contexts.
+ *
+ * @service: Service to lock.
+ *
+ * This is an alternative to vs_service_state_lock for drivers that receive
+ * messages in atomic context (i.e. have their rx_atomic flag set), *and* must
+ * transmit messages from softirq contexts other than their own message
+ * receive and tx_ready callbacks. Such drivers must set their tx_atomic
+ * flag, so generated protocol drivers perform correct locking.
+ *
+ * This should replace all calls to vs_service_state_lock for services that
+ * need it. Do not use both locking functions in one service driver.
+ *
+ * The calling driver must provide its own guarantee that it has not been
+ * detached from the service. If that is not possible, use
+ * vs_state_lock_safe_bh().
+ */
+static inline void
+vs_service_state_lock_bh(struct vs_service_device *service)
+__acquires(service)
+__acquires(&service->state_spinlock)
+{
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ WARN_ON_ONCE(!vs_service_has_atomic_rx(service));
+ WARN_ON_ONCE(!vs_service_has_atomic_tx(service));
+#endif
+
+#ifdef CONFIG_SMP
+ /* Not necessary on UP because it's implied by spin_lock_bh(). */
+ tasklet_disable(&service->rx_tasklet);
+#endif
+
+ spin_lock_bh(&service->state_spinlock);
+
+#ifdef CONFIG_VSERVICES_LOCK_DEBUG
+ if (WARN_ON_ONCE(service->state_mutex_used))
+ dev_err(&service->dev, "Service is using both the state spinlock and mutex - Fix your driver\n");
+ service->state_spinlock_used = true;
+#endif
+
+ __acquire(service);
+}
+
+/**
+ * vs_service_state_unlock_bh - Release the lock acquired by
+ * vs_service_state_lock_bh.
+ *
+ * @service: Service to unlock.
+ */
+static inline void
+vs_service_state_unlock_bh(struct vs_service_device *service)
+__releases(service)
+__releases(&service->state_spinlock)
+{
+ __release(service);
+
+ spin_unlock_bh(&service->state_spinlock);
+
+#ifdef CONFIG_SMP
+ tasklet_enable(&service->rx_tasklet);
+#endif
+}
+
+/* Convenience macros for locking a state structure rather than a service. */
+#define vs_state_lock(state) vs_service_state_lock((state)->service)
+#define vs_state_unlock(state) vs_service_state_unlock((state)->service)
+#define vs_state_lock_bh(state) vs_service_state_lock_bh((state)->service)
+#define vs_state_unlock_bh(state) vs_service_state_unlock_bh((state)->service)
+
+/**
+ * vs_state_lock_safe[_bh] - Aqcuire a lock for a state structure's service,
+ * when the service may have been detached from the state.
+ *
+ * This is useful for blocking operations that can't easily be terminated
+ * before returning from the service reset handler, such as file I/O. To use
+ * this, the state structure should be reference-counted rather than freed in
+ * the release callback, and the driver should retain its own reference to the
+ * service until the state structure is freed.
+ *
+ * This macro acquires the lock and returns true if the state has not been
+ * detached from the service. Otherwise, it returns false.
+ *
+ * Note that the _bh variant cannot be used from atomic context, because it
+ * acquires a mutex.
+ */
+#define __vs_state_lock_safe(_state, _lock, _unlock) ({ \
+ bool __ok = true; \
+ typeof(_state) __state = (_state); \
+ struct vs_service_device *__service = __state->service; \
+ mutex_lock_nested(&__service->ready_lock, \
+ __service->lock_subclass); \
+ __ok = !ACCESS_ONCE(__state->released); \
+ if (__ok) { \
+ _lock(__state); \
+ __ok = !ACCESS_ONCE(__state->released); \
+ if (!__ok) \
+ _unlock(__state); \
+ } \
+ mutex_unlock(&__service->ready_lock); \
+ __ok; \
+})
+#define vs_state_lock_safe(_state) \
+ __vs_state_lock_safe((_state), vs_state_lock, vs_state_unlock)
+#define vs_state_lock_safe_bh(_state) \
+ __vs_state_lock_safe((_state), vs_state_lock_bh, vs_state_unlock_bh)
+
+/**
+ * vs_get_service - Get a reference to a service.
+ * @service: Service to get a reference to.
+ */
+static inline struct vs_service_device *
+vs_get_service(struct vs_service_device *service)
+{
+ if (service)
+ get_device(&service->dev);
+ return service;
+}
+
+/**
+ * vs_put_service - Put a reference to a service.
+ * @service: The service to put the reference to.
+ */
+static inline void
+vs_put_service(struct vs_service_device *service)
+{
+ put_device(&service->dev);
+}
+
+extern int vs_service_reset(struct vs_service_device *service,
+ struct vs_service_device *caller);
+extern void vs_service_reset_nosync(struct vs_service_device *service);
+
+/**
+ * vs_service_send_batch_start - Start a batch of outgoing messages
+ * @service: The service that is starting a batch
+ * @flush: Finish any previously started batch (if false, then duplicate
+ * calls to this function have no effect)
+ */
+static inline void
+vs_service_send_batch_start(struct vs_service_device *service, bool flush)
+{
+ if (flush && service->tx_batching) {
+ struct vs_session_device *session =
+ vs_service_get_session(service);
+ const struct vs_transport_vtable *vt = session->transport->vt;
+ if (vt->flush)
+ vt->flush(session->transport, service);
+ } else {
+ service->tx_batching = true;
+ }
+}
+
+/**
+ * vs_service_send_batch_end - End a batch of outgoing messages
+ * @service: The service that is ending a batch
+ * @flush: Start sending the batch immediately (if false, the batch will
+ * be flushed when the next message is sent)
+ */
+static inline void
+vs_service_send_batch_end(struct vs_service_device *service, bool flush)
+{
+ service->tx_batching = false;
+ if (flush) {
+ struct vs_session_device *session =
+ vs_service_get_session(service);
+ const struct vs_transport_vtable *vt = session->transport->vt;
+ if (vt->flush)
+ vt->flush(session->transport, service);
+ }
+}
+
+
+#endif /* _VSERVICE_SERVICE_H_ */
diff --git a/include/vservices/session.h b/include/vservices/session.h
new file mode 100644
index 0000000..b9dc775
--- /dev/null
+++ b/include/vservices/session.h
@@ -0,0 +1,161 @@
+/*
+ * include/vservices/session.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This file defines the device type for a vServices session attached to a
+ * transport. This should only be used by transport drivers, the vServices
+ * session code, and the inline transport-access functions defined in
+ * vservices/service.h.
+ *
+ * Drivers for these devices are defined internally by the vServices
+ * framework. Other drivers should not attach to these devices.
+ */
+
+#ifndef _VSERVICES_SESSION_H_
+#define _VSERVICES_SESSION_H_
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+
+#include <vservices/types.h>
+
+struct vs_service_device;
+struct vs_mbuf;
+
+struct notifier_block;
+
+/**
+ * enum vs_notify_event_t - vService notifier events
+ *
+ * @VS_SESSION_NOTIFY_ADD: vService session added. Argument is a pointer to
+ * the vs_session_device. This notification is sent after the session has been
+ * added.
+ *
+ * @VS_SESSION_NOTIFY_REMOVE: vService session about to be removed. Argument is
+ * a pointer to the vs_session_device. This notification is sent before the
+ * session is removed.
+ */
+enum vs_notify_event_t {
+ VS_SESSION_NOTIFY_ADD,
+ VS_SESSION_NOTIFY_REMOVE,
+};
+
+/**
+ * struct vs_session_device - Session device
+ * @name: The unique human-readable name of this session.
+ * @is_server: True if this session is a server, false if client
+ * @transport: The transport device for this session
+ * @session_num: Unique ID for this session. Used for sysfs
+ * @session_lock: Mutex which protects any change to service presence or
+ * readiness
+ * @core_service: The core service, if one has ever been registered. Once set,
+ * this must remain valid and unchanged until the session driver is
+ * removed. Writes are protected by the service_ids_lock.
+ * @services: Dynamic array of the services on this session. Protected by
+ * service_ids_lock.
+ * @alloc_service_ids: Size of the session services array
+ * @service_ids_lock: Mutex protecting service array updates
+ * @activation_work: work structure for handling session activation & reset
+ * @activation_state: true if transport is currently active
+ * @fatal_error_work: work structure for handling fatal session failures
+ * @debug_mask: Debug level mask
+ * @list: Entry in the global session list
+ * @sysfs_entry: Kobject pointer pointing to session device in sysfs under
+ * sys/vservices
+ * @dev: Device structure for the Linux device model
+ */
+struct vs_session_device {
+ char *name;
+ bool is_server;
+ struct vs_transport *transport;
+ int session_num;
+
+ struct mutex session_lock;
+
+ /*
+ * The service_idr maintains the list of currently allocated services
+ * on a session, and allows for recycling of service ids. The lock also
+ * protects core_service.
+ */
+ struct idr service_idr;
+ struct mutex service_idr_lock;
+ struct vs_service_device *core_service;
+
+ struct work_struct activation_work;
+ atomic_t activation_state;
+
+ struct work_struct fatal_error_work;
+
+ unsigned long debug_mask;
+
+ struct list_head list;
+ struct kobject *sysfs_entry;
+
+ struct device dev;
+};
+
+#define to_vs_session_device(d) \
+ container_of(d, struct vs_session_device, dev)
+
+extern struct vs_session_device *
+vs_session_register(struct vs_transport *transport, struct device *parent,
+ bool server, const char *transport_name);
+extern void vs_session_start(struct vs_session_device *session);
+extern void vs_session_unregister(struct vs_session_device *session);
+
+extern int vs_session_handle_message(struct vs_session_device *session,
+ struct vs_mbuf *mbuf, vs_service_id_t service_id);
+
+extern void vs_session_quota_available(struct vs_session_device *session,
+ vs_service_id_t service_id, unsigned count,
+ bool send_tx_ready);
+
+extern void vs_session_handle_notify(struct vs_session_device *session,
+ unsigned long flags, vs_service_id_t service_id);
+
+extern void vs_session_handle_reset(struct vs_session_device *session);
+extern void vs_session_handle_activate(struct vs_session_device *session);
+
+extern struct vs_service_device *
+vs_server_create_service(struct vs_session_device *session,
+ struct vs_service_device *parent, const char *name,
+ const char *protocol, const void *plat_data);
+extern int vs_server_destroy_service(struct vs_service_device *service,
+ struct vs_service_device *parent);
+
+extern void vs_session_register_notify(struct notifier_block *nb);
+extern void vs_session_unregister_notify(struct notifier_block *nb);
+
+extern int vs_session_unbind_driver(struct vs_service_device *service);
+
+extern void vs_session_for_each_service(struct vs_session_device *session,
+ void (*func)(struct vs_service_device *, void *), void *data);
+
+extern struct mutex vs_session_lock;
+extern int vs_session_for_each_locked(
+ int (*fn)(struct vs_session_device *session, void *data),
+ void *data);
+
+static inline int vs_session_for_each(
+ int (*fn)(struct vs_session_device *session, void *data),
+ void *data)
+{
+ int r;
+ mutex_lock(&vs_session_lock);
+ r = vs_session_for_each_locked(fn, data);
+ mutex_unlock(&vs_session_lock);
+ return r;
+}
+
+#endif /* _VSERVICES_SESSION_H_ */
diff --git a/include/vservices/transport.h b/include/vservices/transport.h
new file mode 100644
index 0000000..6251ce1
--- /dev/null
+++ b/include/vservices/transport.h
@@ -0,0 +1,150 @@
+/*
+ * include/vservices/transport.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This file contains the transport vtable structure. This is made public so
+ * that the application drivers can call the vtable functions directly (via
+ * the inlined wrappers in service.h) rather than indirectly via a function
+ * call.
+ *
+ */
+
+#ifndef _VSERVICES_TRANSPORT_H_
+#define _VSERVICES_TRANSPORT_H_
+
+#include <linux/types.h>
+
+#include <vservices/types.h>
+
+struct vs_transport;
+struct vs_mbuf;
+struct vs_service_device;
+
+/**
+ * struct vs_transport_vtable - Transport driver operations. Transport drivers
+ * must provide implementations for all operations in this table.
+ * --- Message buffer allocation ---
+ * @alloc_mbuf: Allocate an mbuf of the given size for the given service
+ * @free_mbuf: Deallocate an mbuf
+ * @mbuf_size: Return the size in bytes of a message buffer. The size returned
+ * should be the total number of bytes including any headers.
+ * @max_mbuf_size: Return the maximum allowable message buffer allocation size.
+ * --- Message sending ---
+ * @send: Queue an mbuf for sending
+ * @flush: Start the transfer for the current message batch, if any
+ * @notify: Send a notification
+ * --- Transport-level reset handling ---
+ * @reset: Reset the transport layer
+ * @ready: Ready the transport layer
+ * --- Service management ---
+ * @service_add: A new service has been added to this transport's session
+ * @service_remove: A service has been removed from this transport's session
+ * @service_start: A service on this transport's session has had its resource
+ * allocations set and is about to start. This is always interleaved with
+ * service_reset, with one specific exception: the core service client,
+ * which has its quotas initially hard-coded to 0 send / 1 recv and
+ * adjusted when the initial startup message arrives.
+ * @service_reset: A service on this transport's session has just been reset,
+ * and any resources allocated to it should be cleaned up to prepare
+ * for later reallocation.
+ * @service_send_avail: The number of message buffers that this service is
+ * able to send before going over quota.
+ * --- Query transport capabilities ---
+ * @get_notify_bits: Fetch the number of sent and received notification bits
+ * supported by this transport. Note that this can be any positive value
+ * up to UINT_MAX.
+ * @get_quota_limits: Fetch the total send and receive message buffer quotas
+ * supported by this transport. Note that this can be any positive value
+ * up to UINT_MAX.
+ */
+struct vs_transport_vtable {
+ /* Message buffer allocation */
+ struct vs_mbuf *(*alloc_mbuf)(struct vs_transport *transport,
+ struct vs_service_device *service, size_t size,
+ gfp_t gfp_flags);
+ void (*free_mbuf)(struct vs_transport *transport,
+ struct vs_service_device *service,
+ struct vs_mbuf *mbuf);
+ size_t (*mbuf_size)(struct vs_mbuf *mbuf);
+ size_t (*max_mbuf_size)(struct vs_transport *transport);
+
+ /* Sending messages */
+ int (*send)(struct vs_transport *transport,
+ struct vs_service_device *service,
+ struct vs_mbuf *mbuf, unsigned long flags);
+ int (*flush)(struct vs_transport *transport,
+ struct vs_service_device *service);
+ int (*notify)(struct vs_transport *transport,
+ struct vs_service_device *service,
+ unsigned long bits);
+
+ /* Raising and clearing transport-level reset */
+ void (*reset)(struct vs_transport *transport);
+ void (*ready)(struct vs_transport *transport);
+
+ /* Service management */
+ int (*service_add)(struct vs_transport *transport,
+ struct vs_service_device *service);
+ void (*service_remove)(struct vs_transport *transport,
+ struct vs_service_device *service);
+
+ int (*service_start)(struct vs_transport *transport,
+ struct vs_service_device *service);
+ int (*service_reset)(struct vs_transport *transport,
+ struct vs_service_device *service);
+
+ ssize_t (*service_send_avail)(struct vs_transport *transport,
+ struct vs_service_device *service);
+
+ /* Query transport capabilities */
+ void (*get_notify_bits)(struct vs_transport *transport,
+ unsigned *send_notify_bits, unsigned *recv_notify_bits);
+ void (*get_quota_limits)(struct vs_transport *transport,
+ unsigned *send_quota, unsigned *recv_quota);
+};
+
+/* Flags for .send */
+#define VS_TRANSPORT_SEND_FLAGS_MORE 0x1
+
+/**
+ * struct vs_transport - A structure representing a transport
+ * @type: type of transport i.e. microvisror/loopback etc
+ * @vt: Transport operations table
+ * @notify_info: Array of incoming notification settings
+ * @notify_info_size: Size of the incoming notification array
+ */
+struct vs_transport {
+ const char *type;
+ const struct vs_transport_vtable *vt;
+ struct vs_notify_info *notify_info;
+ int notify_info_size;
+};
+
+/**
+ * struct vs_mbuf - Message buffer. This is always allocated and released by the
+ * transport callbacks defined above, so it may be embedded in a
+ * transport-specific structure containing additional state.
+ * @data: Message data buffer
+ * @size: Size of the data buffer in bytes
+ * @is_recv: True if this mbuf was received from the other end of the
+ * transport. False if it was allocated by this end for sending.
+ * @priv: Private value that will not be touched by the framework
+ * @queue: list_head for entry in lists. The session layer uses this queue
+ * for receiving messages. The transport driver may use this queue for its
+ * own purposes when sending messages.
+ */
+struct vs_mbuf {
+ void *data;
+ size_t size;
+ bool is_recv;
+ void *priv;
+ struct list_head queue;
+};
+
+#endif /* _VSERVICES_TRANSPORT_H_ */
diff --git a/include/vservices/types.h b/include/vservices/types.h
new file mode 100644
index 0000000..306156e
--- /dev/null
+++ b/include/vservices/types.h
@@ -0,0 +1,41 @@
+/*
+ * include/vservices/types.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _VSERVICE_TYPES_H
+#define _VSERVICE_TYPES_H
+
+#include <linux/types.h>
+
+typedef u16 vs_service_id_t;
+typedef u16 vs_message_id_t;
+
+/*
+ * An opaque handle to a queued asynchronous command. This is used internally
+ * by the generated interface code, to identify which of the pending commands
+ * is being replied to. It is provided as a parameter to non-blocking handler
+ * callbacks for queued asynchronous requests, and must be stored by the server
+ * and passed to the corresponding reply call.
+ */
+typedef struct vservice_queued_request vservice_queued_request_t;
+
+/*
+ * Following enum is to be used by server for informing about successful or
+ * unsuccessful open callback by using VS_SERVER_RESP_SUCCESS or
+ * VS_SERVER_RESP_FAILURE resepectively. Server can choose to complete request
+ * explicitely in this case it should return VS_SERVER_RESP_EXPLICIT_COMPLETE.
+ */
+typedef enum vs_server_response_type {
+ VS_SERVER_RESP_SUCCESS,
+ VS_SERVER_RESP_FAILURE,
+ VS_SERVER_RESP_EXPLICIT_COMPLETE
+} vs_server_response_type_t;
+
+#endif /*_VSERVICE_TYPES_H */
diff --git a/include/vservices/wait.h b/include/vservices/wait.h
new file mode 100644
index 0000000..544937d
--- /dev/null
+++ b/include/vservices/wait.h
@@ -0,0 +1,455 @@
+/*
+ * include/vservices/wait.h
+ *
+ * Copyright (c) 2012-2018 General Dynamics
+ * Copyright (c) 2014 Open Kernel Labs, Inc.
+ *
+ * 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.
+ *
+ * Generic wait event helpers for Virtual Service drivers.
+ */
+
+#ifndef _VSERVICE_SERVICE_WAIT_H
+#define _VSERVICE_SERVICE_WAIT_H
+
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+#include <vservices/service.h>
+
+/* Older kernels don't have lockdep_assert_held_once(). */
+#ifndef lockdep_assert_held_once
+#ifdef CONFIG_LOCKDEP
+#define lockdep_assert_held_once(l) do { \
+ WARN_ON_ONCE(debug_locks && !lockdep_is_held(l)); \
+ } while (0)
+#else
+#define lockdep_assert_held_once(l) do { } while (0)
+#endif
+#endif
+
+/* Legacy wait macro; needs rewriting to use vs_state_lock_safe(). */
+/* FIXME: Redmine ticket #229 - philip. */
+/**
+ * __vs_service_wait_event - Wait for a condition to become true for a
+ * Virtual Service.
+ *
+ * @_service: The service to wait for the condition to be true for.
+ * @_wq: Waitqueue to wait on.
+ * @_condition: Condition to wait for.
+ *
+ * Returns: This function returns 0 if the condition is true, or a -ERESTARTSYS
+ * if the wait loop wait interrupted. If _state is TASK_UNINTERRUPTIBLE
+ * then this function will always return 0.
+ *
+ * This function must be called with the service's state lock held. The wait
+ * is performed without the state lock held, but the condition is re-checked
+ * after reacquiring the state lock. This property allows this function to
+ * check the state of the service's protocol in a thread safe manner.
+ *
+ * The caller is responsible for ensuring that it has not been detached from
+ * the given service.
+ *
+ * It is nearly always wrong to call this on the service workqueue, since
+ * the workqueue is single-threaded and the state can only change when a
+ * handler function is called on it.
+ */
+#define __vs_service_wait_event(_service, _wq, _cond, _state) \
+ ({ \
+ DEFINE_WAIT(__wait); \
+ int __ret = 0; \
+ \
+ lockdep_assert_held_once(&(_service)->state_mutex); \
+ do { \
+ prepare_to_wait(&(_wq), &__wait, (_state)); \
+ \
+ if (_cond) \
+ break; \
+ \
+ if ((_state) == TASK_INTERRUPTIBLE && \
+ signal_pending(current)) { \
+ __ret = -ERESTARTSYS; \
+ break; \
+ } \
+ \
+ vs_service_state_unlock(_service); \
+ schedule(); \
+ vs_service_state_lock(_service); \
+ } while (!(_cond)); \
+ \
+ finish_wait(&(_wq), &__wait); \
+ __ret; \
+ })
+
+/* Legacy wait macros; need rewriting to use __vs_wait_state(). */
+/* FIXME: Redmine ticket #229 - philip. */
+#define vs_service_wait_event(_service, _wq, _cond) \
+ __vs_service_wait_event(_service, _wq, _cond, TASK_INTERRUPTIBLE)
+#define vs_service_wait_event_nointr(_service, _wq, _cond) \
+ __vs_service_wait_event(_service, _wq, _cond, TASK_UNINTERRUPTIBLE)
+
+/**
+ * __vs_wait_state - block until a condition becomes true on a service state.
+ *
+ * @_state: The protocol state to wait on.
+ * @_cond: Condition to wait for.
+ * @_intr: If true, perform an interruptible wait; the wait may then fail
+ * with -ERESTARTSYS.
+ * @_timeout: A timeout in jiffies, or negative for no timeout. If the
+ * timeout expires, the wait will fail with -ETIMEDOUT.
+ * @_bh: The token _bh if this service uses tx_atomic (sends from a
+ * non-framework tasklet); otherwise nothing.
+ *
+ * Return: Return a pointer to a message buffer on successful allocation,
+ * or an error code in ERR_PTR form.
+ *
+ * This macro blocks waiting until a particular condition becomes true on a
+ * service state. The service must be running; if not, or if it ceases to be
+ * running during the wait, -ECANCELED will be returned.
+ *
+ * This is not an exclusive wait. If an exclusive wait is desired it is
+ * usually better to use the waiting alloc or send functions.
+ *
+ * This macro must be called with a reference to the service held, and with
+ * the service's state lock held. The state lock will be dropped by waiting
+ * but reacquired before returning, unless -ENOLINK is returned, in which case
+ * the service driver has been unbound and the lock cannot be reacquired.
+ */
+#define __vs_wait_state(_state, _cond, _intr, _timeout, _bh) \
+ ({ \
+ DEFINE_WAIT(__wait); \
+ int __ret; \
+ int __jiffies __maybe_unused = (_timeout); \
+ struct vs_service_device *__service = (_state)->service;\
+ \
+ while (1) { \
+ prepare_to_wait(&__service->quota_wq, &__wait, \
+ _intr ? TASK_INTERRUPTIBLE : \
+ TASK_UNINTERRUPTIBLE); \
+ \
+ if (!VSERVICE_BASE_STATE_IS_RUNNING( \
+ (_state)->state.base)) { \
+ __ret = -ECANCELED; \
+ break; \
+ } \
+ \
+ if (_cond) { \
+ __ret = 0; \
+ break; \
+ } \
+ \
+ if (_intr && signal_pending(current)) { \
+ __ret = -ERESTARTSYS; \
+ break; \
+ } \
+ \
+ vs_state_unlock##_bh(_state); \
+ \
+ if (_timeout >= 0) { \
+ __jiffies = schedule_timeout(__jiffies);\
+ if (!__jiffies) { \
+ __ret = -ETIMEDOUT; \
+ break; \
+ } \
+ } else { \
+ schedule(); \
+ } \
+ \
+ if (!vs_state_lock_safe##_bh(_state)) { \
+ __ret = -ENOLINK; \
+ break; \
+ } \
+ } \
+ \
+ finish_wait(&__service->quota_wq, &__wait); \
+ __ret; \
+ })
+
+/* Specialisations of __vs_wait_state for common uses. */
+#define vs_wait_state(_state, _cond) \
+ __vs_wait_state(_state, _cond, true, -1,)
+#define vs_wait_state_timeout(_state, _cond, _timeout) \
+ __vs_wait_state(_state, _cond, true, _timeout,)
+#define vs_wait_state_nointr(_state, _cond) \
+ __vs_wait_state(_state, _cond, false, -1,)
+#define vs_wait_state_nointr_timeout(_state, _cond, _timeout) \
+ __vs_wait_state(_state, _cond, false, _timeout,)
+#define vs_wait_state_bh(_state, _cond) \
+ __vs_wait_state(_state, _cond, true, -1, _bh)
+#define vs_wait_state_timeout_bh(_state, _cond, _timeout) \
+ __vs_wait_state(_state, _cond, true, _timeout, _bh)
+#define vs_wait_state_nointr_bh(_state, _cond) \
+ __vs_wait_state(_state, _cond, false, -1, _bh)
+#define vs_wait_state_nointr_timeout_bh(_state, _cond, _timeout) \
+ __vs_wait_state(_state, _cond, false, _timeout, _bh)
+
+/**
+ * __vs_wait_alloc - block until quota is available, then allocate a buffer.
+ *
+ * @_state: The protocol state to allocate a message for.
+ * @_alloc_func: The message buffer allocation function to run. This is the
+ * full function invocation, not a pointer to the function.
+ * @_cond: Additional condition which must remain true, or else the wait
+ * will fail with -ECANCELED. This is typically used to check the
+ * service's protocol state. Note that this condition will only
+ * be checked after sleeping; it is assumed to be true when the
+ * macro is first called.
+ * @_unlock: If true, drop the service state lock before sleeping. The wait
+ * may then fail with -ENOLINK if the driver is detached from the
+ * service, in which case the lock is dropped.
+ * @_intr: If true, perform an interruptible wait; the wait may then fail
+ * with -ERESTARTSYS.
+ * @_timeout: A timeout in jiffies, or negative for no timeout. If the
+ * timeout expires, the wait will fail with -ETIMEDOUT.
+ * @_bh: The token _bh if this service uses tx_atomic (sends from a
+ * non-framework tasklet); otherwise nothing.
+ *
+ * Return: Return a pointer to a message buffer on successful allocation,
+ * or an error code in ERR_PTR form.
+ *
+ * This macro calls a specified message allocation function, and blocks
+ * if it returns -ENOBUFS, waiting until quota is available on the service
+ * before retrying. It aborts the wait if the service resets, or if the
+ * optionally specified condition becomes false. Note that a reset followed
+ * quickly by an activate might not trigger a failure; if that is significant
+ * for your driver, use the optional condition to detect it.
+ *
+ * This macro must be called with a reference to the service held, and with
+ * the service's state lock held. The reference and state lock will still be
+ * held on return, unless -ENOLINK is returned, in which case the lock has been
+ * dropped and cannot be reacquired.
+ *
+ * This is always an exclusive wait. It is safe to call without separately
+ * waking the waitqueue afterwards; if the allocator function fails for any
+ * reason other than quota exhaustion then another waiter will be woken.
+ *
+ * Be wary of potential deadlocks when using this macro on the service
+ * workqueue. If both ends block their service workqueues waiting for quota,
+ * then no progress can be made. It is usually only correct to block the
+ * service workqueue on the server side.
+ */
+#define __vs_wait_alloc(_state, _alloc_func, _cond, _unlock, _intr, \
+ _timeout, _bh) \
+ ({ \
+ DEFINE_WAIT(__wait); \
+ struct vs_mbuf *__mbuf = NULL; \
+ int __jiffies __maybe_unused = (_timeout); \
+ struct vs_service_device *__service = (_state)->service;\
+ \
+ while (!vs_service_send_mbufs_available(__service)) { \
+ if (_intr && signal_pending(current)) { \
+ __mbuf = ERR_PTR(-ERESTARTSYS); \
+ break; \
+ } \
+ \
+ prepare_to_wait_exclusive( \
+ &__service->quota_wq, &__wait, \
+ _intr ? TASK_INTERRUPTIBLE : \
+ TASK_UNINTERRUPTIBLE); \
+ \
+ if (_unlock) \
+ vs_state_unlock##_bh(_state); \
+ \
+ if (_timeout >= 0) { \
+ __jiffies = schedule_timeout(__jiffies);\
+ if (!__jiffies) { \
+ __mbuf = ERR_PTR(-ETIMEDOUT); \
+ break; \
+ } \
+ } else { \
+ schedule(); \
+ } \
+ \
+ if (_unlock && !vs_state_lock_safe##_bh( \
+ _state)) { \
+ __mbuf = ERR_PTR(-ENOLINK); \
+ break; \
+ } \
+ \
+ if (!VSERVICE_BASE_STATE_IS_RUNNING( \
+ (_state)->state.base) || \
+ !(_cond)) { \
+ __mbuf = ERR_PTR(-ECANCELED); \
+ break; \
+ } \
+ } \
+ finish_wait(&__service->quota_wq, &__wait); \
+ \
+ if (__mbuf == NULL) \
+ __mbuf = (_alloc_func); \
+ if (IS_ERR(__mbuf) && (PTR_ERR(__mbuf) != -ENOBUFS)) \
+ wake_up(&__service->quota_wq); \
+ __mbuf; \
+ })
+
+/* Specialisations of __vs_wait_alloc for common uses. */
+#define vs_wait_alloc(_state, _cond, _alloc_func) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1,)
+#define vs_wait_alloc_timeout(_state, _cond, _alloc_func, _timeout) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, true, _timeout,)
+#define vs_wait_alloc_nointr(_state, _cond, _alloc_func) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1,)
+#define vs_wait_alloc_nointr_timeout(_state, _cond, _alloc_func, _timeout) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, false, _timeout,)
+#define vs_wait_alloc_bh(_state, _cond, _alloc_func) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1, _bh)
+#define vs_wait_alloc_timeout_bh(_state, _cond, _alloc_func, _timeout) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, true, _timeout, _bh)
+#define vs_wait_alloc_nointr_bh(_state, _cond, _alloc_func) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1, _bh)
+#define vs_wait_alloc_nointr_timeout_bh(_state, _cond, _alloc_func, _timeout) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, false, _timeout, _bh)
+#define vs_wait_alloc_locked(_state, _alloc_func) \
+ __vs_wait_alloc(_state, _alloc_func, true, false, true, -1,)
+
+/* Legacy wait macros, to be removed and replaced with those above. */
+/* FIXME: Redmine ticket #229 - philip. */
+#define vs_service_waiting_alloc(_state, _alloc_func) \
+ __vs_wait_alloc(_state, _alloc_func, true, false, true, -1,)
+#define vs_service_waiting_alloc_cond_locked(_state, _alloc_func, _cond) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, true, -1,)
+#define vs_service_waiting_alloc_cond_locked_nointr(_state, _alloc_func, _cond) \
+ __vs_wait_alloc(_state, _alloc_func, _cond, true, false, -1,)
+
+/**
+ * __vs_wait_send - block until quota is available, then send a message.
+ *
+ * @_state: The protocol state to send a message for.
+ * @_cond: Additional condition which must remain true, or else the wait
+ * will fail with -ECANCELED. This is typically used to check the
+ * service's protocol state. Note that this condition will only
+ * be checked after sleeping; it is assumed to be true when the
+ * macro is first called.
+ * @_send_func: The message send function to run. This is the full function
+ * invocation, not a pointer to the function.
+ * @_unlock: If true, drop the service state lock before sleeping. The wait
+ * may then fail with -ENOLINK if the driver is detached from the
+ * service, in which case the lock is dropped.
+ * @_check_running: If true, the wait will return -ECANCELED if the service's
+ * base state is not active, or ceases to be active.
+ * @_intr: If true, perform an interruptible wait; the wait may then fail
+ * with -ERESTARTSYS.
+ * @_timeout: A timeout in jiffies, or negative for no timeout. If the
+ * timeout expires, the wait will fail with -ETIMEDOUT.
+ * @_bh: The token _bh if this service uses tx_atomic (sends from a
+ * non-framework tasklet); otherwise nothing.
+ *
+ * Return: If the send succeeds, then 0 is returned; otherwise an error
+ * code may be returned as described above.
+ *
+ * This macro calls a specified message send function, and blocks if it
+ * returns -ENOBUFS, waiting until quota is available on the service before
+ * retrying. It aborts the wait if it finds the service in reset, or if the
+ * optionally specified condition becomes false. Note that a reset followed
+ * quickly by an activate might not trigger a failure; if that is significant
+ * for your driver, use the optional condition to detect it.
+ *
+ * This macro must be called with a reference to the service held, and with
+ * the service's state lock held. The reference and state lock will still be
+ * held on return, unless -ENOLINK is returned, in which case the lock has been
+ * dropped and cannot be reacquired.
+ *
+ * This is always an exclusive wait. It is safe to call without separately
+ * waking the waitqueue afterwards; if the allocator function fails for any
+ * reason other than quota exhaustion then another waiter will be woken.
+ *
+ * Be wary of potential deadlocks when calling this function on the service
+ * workqueue. If both ends block their service workqueues waiting for quota,
+ * then no progress can be made. It is usually only correct to block the
+ * service workqueue on the server side.
+ */
+#define __vs_wait_send(_state, _cond, _send_func, _unlock, \
+ _check_running, _intr, _timeout, _bh) \
+ ({ \
+ DEFINE_WAIT(__wait); \
+ int __ret = 0; \
+ int __jiffies __maybe_unused = (_timeout); \
+ struct vs_service_device *__service = (_state)->service;\
+ \
+ while (!vs_service_send_mbufs_available(__service)) { \
+ if (_intr && signal_pending(current)) { \
+ __ret = -ERESTARTSYS; \
+ break; \
+ } \
+ \
+ prepare_to_wait_exclusive( \
+ &__service->quota_wq, &__wait, \
+ _intr ? TASK_INTERRUPTIBLE : \
+ TASK_UNINTERRUPTIBLE); \
+ \
+ if (_unlock) \
+ vs_state_unlock##_bh(_state); \
+ \
+ if (_timeout >= 0) { \
+ __jiffies = schedule_timeout(__jiffies);\
+ if (!__jiffies) { \
+ __ret = -ETIMEDOUT; \
+ break; \
+ } \
+ } else { \
+ schedule(); \
+ } \
+ \
+ if (_unlock && !vs_state_lock_safe##_bh( \
+ _state)) { \
+ __ret = -ENOLINK; \
+ break; \
+ } \
+ \
+ if ((_check_running && \
+ !VSERVICE_BASE_STATE_IS_RUNNING(\
+ (_state)->state.base)) || \
+ !(_cond)) { \
+ __ret = -ECANCELED; \
+ break; \
+ } \
+ } \
+ finish_wait(&__service->quota_wq, &__wait); \
+ \
+ if (!__ret) \
+ __ret = (_send_func); \
+ if ((__ret < 0) && (__ret != -ENOBUFS)) \
+ wake_up(&__service->quota_wq); \
+ __ret; \
+ })
+
+/* Specialisations of __vs_wait_send for common uses. */
+#define vs_wait_send(_state, _cond, _send_func) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, true, -1,)
+#define vs_wait_send_timeout(_state, _cond, _send_func, _timeout) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, true, _timeout,)
+#define vs_wait_send_nointr(_state, _cond, _send_func) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, false, -1,)
+#define vs_wait_send_nointr_timeout(_state, _cond, _send_func, _timeout) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, false, _timeout,)
+#define vs_wait_send_bh(_state, _cond, _send_func) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, true, -1, _bh)
+#define vs_wait_send_timeout_bh(_state, _cond, _send_func, _timeout) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, true, \
+ _timeout, _bh)
+#define vs_wait_send_nointr_bh(_state, _cond, _send_func) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, false, -1, _bh)
+#define vs_wait_send_nointr_timeout_bh(_state, _cond, _send_func, _timeout) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, false, \
+ _timeout, _bh)
+#define vs_wait_send_locked(_state, _send_func) \
+ __vs_wait_send(_state, true, _send_func, false, true, true, -1,)
+#define vs_wait_send_locked_nocheck(_state, _send_func) \
+ __vs_wait_send(_state, true, _send_func, false, false, true, -1,)
+
+/* Legacy wait macros, to be removed and replaced with those above. */
+/* FIXME: Redmine ticket #229 - philip. */
+#define vs_service_waiting_send(_state, _send_func) \
+ __vs_wait_send(_state, true, _send_func, true, true, true, -1,)
+#define vs_service_waiting_send_nointr(_state, _send_func) \
+ __vs_wait_send(_state, true, _send_func, true, true, false, -1,)
+#define vs_service_waiting_send_cond(_state, _cond, _send_func) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, true, -1,)
+#define vs_service_waiting_send_cond_nointr(_state, _cond, _send_func) \
+ __vs_wait_send(_state, _cond, _send_func, true, true, false, -1,)
+#define vs_service_waiting_send_nocheck(_state, _send_func) \
+ __vs_wait_send(_state, true, _send_func, true, false, true, -1,)
+
+#endif /* _VSERVICE_SERVICE_WAIT_H */
diff --git a/kernel/module.c b/kernel/module.c
index 8a84031..1277bdf 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -4035,7 +4035,7 @@
for (i = 0; i < kallsyms->num_symtab; i++)
if (strcmp(name, symname(kallsyms, i)) == 0 &&
- kallsyms->symtab[i].st_info != 'U')
+ kallsyms->symtab[i].st_shndx != SHN_UNDEF)
return kallsyms->symtab[i].st_value;
return 0;
}
@@ -4081,6 +4081,10 @@
if (mod->state == MODULE_STATE_UNFORMED)
continue;
for (i = 0; i < kallsyms->num_symtab; i++) {
+
+ if (kallsyms->symtab[i].st_shndx == SHN_UNDEF)
+ continue;
+
ret = fn(data, symname(kallsyms, i),
mod, kallsyms->symtab[i].st_value);
if (ret != 0)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 0bb5046..7944ae9 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -7350,7 +7350,7 @@
return cpumask_test_cpu(cpu, tsk_cpus_allowed(p)) &&
cpu_active(cpu) && !cpu_isolated(cpu) &&
capacity_orig_of(cpu) >= capacity_orig_of(rtg_target_cpu) &&
- task_fits_max(p, cpu);
+ task_fits_max(p, cpu) && !__cpu_overutilized(cpu, task_util(p));
}
#define SCHED_SELECT_PREV_CPU_NSEC 2000000
@@ -11166,7 +11166,8 @@
* - A task which has been woken up by try_to_wake_up() and
* waiting for actually being woken up by sched_ttwu_pending().
*/
- if (!se->sum_exec_runtime || p->state == TASK_WAKING)
+ if (!se->sum_exec_runtime ||
+ (p->state == TASK_WAKING && p->sched_remote_wakeup))
return true;
return false;
diff --git a/kernel/sched/tune.c b/kernel/sched/tune.c
index a8fab0c..92fcb92 100644
--- a/kernel/sched/tune.c
+++ b/kernel/sched/tune.c
@@ -791,7 +791,7 @@
u64 prefer_idle)
{
struct schedtune *st = css_st(css);
- st->prefer_idle = prefer_idle;
+ st->prefer_idle = !!prefer_idle;
return 0;
}
diff --git a/kernel/time/alarmtimer.c b/kernel/time/alarmtimer.c
index 78c0e04..aa25aac 100644
--- a/kernel/time/alarmtimer.c
+++ b/kernel/time/alarmtimer.c
@@ -815,7 +815,8 @@
/* Convert (if necessary) to absolute time */
if (flags != TIMER_ABSTIME) {
ktime_t now = alarm_bases[type].gettime();
- exp = ktime_add(now, exp);
+
+ exp = ktime_add_safe(now, exp);
}
if (alarmtimer_do_nsleep(&alarm, exp))
diff --git a/kernel/time/time.c b/kernel/time/time.c
index 15f3487..9e5ffd1 100644
--- a/kernel/time/time.c
+++ b/kernel/time/time.c
@@ -704,6 +704,16 @@
#endif
}
+u64 jiffies64_to_nsecs(u64 j)
+{
+#if !(NSEC_PER_SEC % HZ)
+ return (NSEC_PER_SEC / HZ) * j;
+# else
+ return div_u64(j * HZ_TO_NSEC_NUM, HZ_TO_NSEC_DEN);
+#endif
+}
+EXPORT_SYMBOL(jiffies64_to_nsecs);
+
/**
* nsecs_to_jiffies64 - Convert nsecs in u64 to jiffies64
*
diff --git a/kernel/time/timeconst.bc b/kernel/time/timeconst.bc
index c486889..f83bbb8 100644
--- a/kernel/time/timeconst.bc
+++ b/kernel/time/timeconst.bc
@@ -98,6 +98,12 @@
print "#define HZ_TO_USEC_DEN\t\t", hz/cd, "\n"
print "#define USEC_TO_HZ_NUM\t\t", hz/cd, "\n"
print "#define USEC_TO_HZ_DEN\t\t", 1000000/cd, "\n"
+
+ cd=gcd(hz,1000000000)
+ print "#define HZ_TO_NSEC_NUM\t\t", 1000000000/cd, "\n"
+ print "#define HZ_TO_NSEC_DEN\t\t", hz/cd, "\n"
+ print "#define NSEC_TO_HZ_NUM\t\t", hz/cd, "\n"
+ print "#define NSEC_TO_HZ_DEN\t\t", 1000000000/cd, "\n"
print "\n"
print "#endif /* KERNEL_TIMECONST_H */\n"
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index dc29b60..f316e90 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -1504,6 +1504,8 @@
tmp_iter_page = first_page;
do {
+ cond_resched();
+
to_remove_page = tmp_iter_page;
rb_inc_page(cpu_buffer, &tmp_iter_page);
diff --git a/lib/klist.c b/lib/klist.c
index 0507fa5..f6b5478 100644
--- a/lib/klist.c
+++ b/lib/klist.c
@@ -336,8 +336,9 @@
void (*put)(struct klist_node *) = i->i_klist->put;
struct klist_node *last = i->i_cur;
struct klist_node *prev;
+ unsigned long flags;
- spin_lock(&i->i_klist->k_lock);
+ spin_lock_irqsave(&i->i_klist->k_lock, flags);
if (last) {
prev = to_klist_node(last->n_node.prev);
@@ -356,7 +357,7 @@
prev = to_klist_node(prev->n_node.prev);
}
- spin_unlock(&i->i_klist->k_lock);
+ spin_unlock_irqrestore(&i->i_klist->k_lock, flags);
if (put && last)
put(last);
@@ -377,8 +378,9 @@
void (*put)(struct klist_node *) = i->i_klist->put;
struct klist_node *last = i->i_cur;
struct klist_node *next;
+ unsigned long flags;
- spin_lock(&i->i_klist->k_lock);
+ spin_lock_irqsave(&i->i_klist->k_lock, flags);
if (last) {
next = to_klist_node(last->n_node.next);
@@ -397,7 +399,7 @@
next = to_klist_node(next->n_node.next);
}
- spin_unlock(&i->i_klist->k_lock);
+ spin_unlock_irqrestore(&i->i_klist->k_lock, flags);
if (put && last)
put(last);
diff --git a/mm/madvise.c b/mm/madvise.c
index ee7ad9b..b753f02 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -83,7 +83,7 @@
new_flags |= VM_DONTDUMP;
break;
case MADV_DODUMP:
- if (new_flags & VM_SPECIAL) {
+ if (!is_vm_hugetlb_page(vma) && new_flags & VM_SPECIAL) {
error = -EINVAL;
goto out;
}
diff --git a/mm/shmem.c b/mm/shmem.c
index 61a39aa..290c5b8 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2160,6 +2160,8 @@
mpol_shared_policy_init(&info->policy, NULL);
break;
}
+
+ lockdep_annotate_inode_mutex_key(inode);
} else
shmem_free_inode(sb);
return inode;
diff --git a/mm/slub.c b/mm/slub.c
index 1a14283..b5c9fde 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -1806,7 +1806,7 @@
{
struct page *page, *page2;
void *object = NULL;
- int available = 0;
+ unsigned int available = 0;
int objects;
/*
@@ -4900,10 +4900,10 @@
static ssize_t cpu_partial_store(struct kmem_cache *s, const char *buf,
size_t length)
{
- unsigned long objects;
+ unsigned int objects;
int err;
- err = kstrtoul(buf, 10, &objects);
+ err = kstrtouint(buf, 10, &objects);
if (err)
return err;
if (objects && !kmem_cache_has_cpu_partial(s))
diff --git a/mm/vmstat.c b/mm/vmstat.c
index fdd884a..f5ef213 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c
@@ -1079,6 +1079,9 @@
#ifdef CONFIG_SMP
"nr_tlb_remote_flush",
"nr_tlb_remote_flush_received",
+#else
+ "", /* nr_tlb_remote_flush */
+ "", /* nr_tlb_remote_flush_received */
#endif /* CONFIG_SMP */
"nr_tlb_local_flush_all",
"nr_tlb_local_flush_one",
diff --git a/net/6lowpan/iphc.c b/net/6lowpan/iphc.c
index 79f1fa2..23654f1 100644
--- a/net/6lowpan/iphc.c
+++ b/net/6lowpan/iphc.c
@@ -745,6 +745,7 @@
hdr.hop_limit, &hdr.daddr);
skb_push(skb, sizeof(hdr));
+ skb_reset_mac_header(skb);
skb_reset_network_header(skb);
skb_copy_to_linear_data(skb, &hdr, sizeof(hdr));
diff --git a/net/bridge/netfilter/ebt_arpreply.c b/net/bridge/netfilter/ebt_arpreply.c
index 070cf13..f2660c1 100644
--- a/net/bridge/netfilter/ebt_arpreply.c
+++ b/net/bridge/netfilter/ebt_arpreply.c
@@ -67,6 +67,9 @@
if (e->ethproto != htons(ETH_P_ARP) ||
e->invflags & EBT_IPROTO)
return -EINVAL;
+ if (ebt_invalid_target(info->target))
+ return -EINVAL;
+
return 0;
}
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 340a3db..2cfbe3f 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c
@@ -1147,6 +1147,12 @@
lladdr = neigh->ha;
}
+ /* Update confirmed timestamp for neighbour entry after we
+ * received ARP packet even if it doesn't change IP to MAC binding.
+ */
+ if (new & NUD_CONNECTED)
+ neigh->confirmed = jiffies;
+
/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
@@ -1168,15 +1174,12 @@
}
}
- /* Update timestamps only once we know we will make a change to the
+ /* Update timestamp only once we know we will make a change to the
* neighbour entry. Otherwise we risk to move the locktime window with
* noop updates and ignore relevant ARP updates.
*/
- if (new != old || lladdr != neigh->ha) {
- if (new & NUD_CONNECTED)
- neigh->confirmed = jiffies;
+ if (new != old || lladdr != neigh->ha)
neigh->updated = jiffies;
- }
if (new != old) {
neigh_del_timer(neigh);
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 90c91a7..275ef13 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1315,6 +1315,7 @@
if (encap)
skb_reset_inner_headers(skb);
skb->network_header = (u8 *)iph - skb->head;
+ skb_reset_mac_len(skb);
} while ((skb = skb->next));
out:
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 885cc39..789bbcb 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -1740,6 +1740,28 @@
inet_compute_pseudo);
}
+/* wrapper for udp_queue_rcv_skb tacking care of csum conversion and
+ * return code conversion for ip layer consumption
+ */
+static int udp_unicast_rcv_skb(struct sock *sk, struct sk_buff *skb,
+ struct udphdr *uh)
+{
+ int ret;
+
+ if (inet_get_convert_csum(sk) && uh->check && !IS_UDPLITE(sk))
+ skb_checksum_try_convert(skb, IPPROTO_UDP, uh->check,
+ inet_compute_pseudo);
+
+ ret = udp_queue_rcv_skb(sk, skb);
+
+ /* a return value > 0 means to resubmit the input, but
+ * it wants the return to be -protocol, or 0
+ */
+ if (ret > 0)
+ return -ret;
+ return 0;
+}
+
/*
* All we need to do is get the socket, and then do a checksum.
*/
@@ -1786,14 +1808,9 @@
if (unlikely(sk->sk_rx_dst != dst))
udp_sk_rx_dst_set(sk, dst);
- ret = udp_queue_rcv_skb(sk, skb);
+ ret = udp_unicast_rcv_skb(sk, skb, uh);
sock_put(sk);
- /* a return value > 0 means to resubmit the input, but
- * it wants the return to be -protocol, or 0
- */
- if (ret > 0)
- return -ret;
- return 0;
+ return ret;
}
if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
@@ -1801,22 +1818,8 @@
saddr, daddr, udptable, proto);
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
- if (sk) {
- int ret;
-
- if (inet_get_convert_csum(sk) && uh->check && !IS_UDPLITE(sk))
- skb_checksum_try_convert(skb, IPPROTO_UDP, uh->check,
- inet_compute_pseudo);
-
- ret = udp_queue_rcv_skb(sk, skb);
-
- /* a return value > 0 means to resubmit the input, but
- * it wants the return to be -protocol, or 0
- */
- if (ret > 0)
- return -ret;
- return 0;
- }
+ if (sk)
+ return udp_unicast_rcv_skb(sk, skb, uh);
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto drop;
diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c
index 3cdf4dc..7c539de 100644
--- a/net/ipv6/ip6_offload.c
+++ b/net/ipv6/ip6_offload.c
@@ -113,6 +113,7 @@
payload_len = skb->len - nhoff - sizeof(*ipv6h);
ipv6h->payload_len = htons(payload_len);
skb->network_header = (u8 *)ipv6h - skb->head;
+ skb_reset_mac_len(skb);
if (udpfrag) {
int err = ip6_find_1stfragopt(skb, &prevhdr);
diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
index def627f..46f8e7c 100644
--- a/net/ipv6/ip6_output.c
+++ b/net/ipv6/ip6_output.c
@@ -210,12 +210,10 @@
kfree_skb(skb);
return -ENOBUFS;
}
+ if (skb->sk)
+ skb_set_owner_w(skb2, skb->sk);
consume_skb(skb);
skb = skb2;
- /* skb_set_owner_w() changes sk->sk_wmem_alloc atomically,
- * it is safe to call in our context (socket lock not held)
- */
- skb_set_owner_w(skb, (struct sock *)sk);
}
if (opt->opt_flen)
ipv6_push_frag_opts(skb, opt, &proto);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index e63fd12..6ef9d32 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -386,7 +386,7 @@
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
/* Keys without a station are used for TX only */
- if (key->sta && test_sta_flag(key->sta, WLAN_STA_MFP))
+ if (sta && test_sta_flag(sta, WLAN_STA_MFP))
key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
break;
case NL80211_IFTYPE_ADHOC:
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index a5acaf1..0c0695e 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -948,8 +948,8 @@
if (len < IEEE80211_DEAUTH_FRAME_LEN)
return;
- ibss_dbg(sdata, "RX DeAuth SA=%pM DA=%pM BSSID=%pM (reason: %d)\n",
- mgmt->sa, mgmt->da, mgmt->bssid, reason);
+ ibss_dbg(sdata, "RX DeAuth SA=%pM DA=%pM\n", mgmt->sa, mgmt->da);
+ ibss_dbg(sdata, "\tBSSID=%pM (reason: %d)\n", mgmt->bssid, reason);
sta_info_destroy_addr(sdata, mgmt->sa);
}
@@ -967,9 +967,9 @@
auth_alg = le16_to_cpu(mgmt->u.auth.auth_alg);
auth_transaction = le16_to_cpu(mgmt->u.auth.auth_transaction);
- ibss_dbg(sdata,
- "RX Auth SA=%pM DA=%pM BSSID=%pM (auth_transaction=%d)\n",
- mgmt->sa, mgmt->da, mgmt->bssid, auth_transaction);
+ ibss_dbg(sdata, "RX Auth SA=%pM DA=%pM\n", mgmt->sa, mgmt->da);
+ ibss_dbg(sdata, "\tBSSID=%pM (auth_transaction=%d)\n",
+ mgmt->bssid, auth_transaction);
if (auth_alg != WLAN_AUTH_OPEN || auth_transaction != 1)
return;
@@ -1176,10 +1176,10 @@
rx_timestamp = drv_get_tsf(local, sdata);
}
- ibss_dbg(sdata,
- "RX beacon SA=%pM BSSID=%pM TSF=0x%llx BCN=0x%llx diff=%lld @%lu\n",
+ ibss_dbg(sdata, "RX beacon SA=%pM BSSID=%pM TSF=0x%llx\n",
mgmt->sa, mgmt->bssid,
- (unsigned long long)rx_timestamp,
+ (unsigned long long)rx_timestamp);
+ ibss_dbg(sdata, "\tBCN=0x%llx diff=%lld @%lu\n",
(unsigned long long)beacon_timestamp,
(unsigned long long)(rx_timestamp - beacon_timestamp),
jiffies);
@@ -1538,9 +1538,9 @@
tx_last_beacon = drv_tx_last_beacon(local);
- ibss_dbg(sdata,
- "RX ProbeReq SA=%pM DA=%pM BSSID=%pM (tx_last_beacon=%d)\n",
- mgmt->sa, mgmt->da, mgmt->bssid, tx_last_beacon);
+ ibss_dbg(sdata, "RX ProbeReq SA=%pM DA=%pM\n", mgmt->sa, mgmt->da);
+ ibss_dbg(sdata, "\tBSSID=%pM (tx_last_beacon=%d)\n",
+ mgmt->bssid, tx_last_beacon);
if (!tx_last_beacon && is_multicast_ether_addr(mgmt->da))
return;
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 2bb6899..e3bbfb2 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -254,8 +254,27 @@
"%s called with hardware scan in progress\n", __func__);
rtnl_lock();
- list_for_each_entry(sdata, &local->interfaces, list)
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ /*
+ * XXX: there may be more work for other vif types and even
+ * for station mode: a good thing would be to run most of
+ * the iface type's dependent _stop (ieee80211_mg_stop,
+ * ieee80211_ibss_stop) etc...
+ * For now, fix only the specific bug that was seen: race
+ * between csa_connection_drop_work and us.
+ */
+ if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+ /*
+ * This worker is scheduled from the iface worker that
+ * runs on mac80211's workqueue, so we can't be
+ * scheduling this worker after the cancel right here.
+ * The exception is ieee80211_chswitch_done.
+ * Then we can have a race...
+ */
+ cancel_work_sync(&sdata->u.mgd.csa_connection_drop_work);
+ }
flush_delayed_work(&sdata->dec_tailroom_needed_wk);
+ }
ieee80211_scan_cancel(local);
/* make sure any new ROC will consider local->in_reconfig */
@@ -466,10 +485,7 @@
cpu_to_le32(IEEE80211_VHT_CAP_RXLDPC |
IEEE80211_VHT_CAP_SHORT_GI_80 |
IEEE80211_VHT_CAP_SHORT_GI_160 |
- IEEE80211_VHT_CAP_RXSTBC_1 |
- IEEE80211_VHT_CAP_RXSTBC_2 |
- IEEE80211_VHT_CAP_RXSTBC_3 |
- IEEE80211_VHT_CAP_RXSTBC_4 |
+ IEEE80211_VHT_CAP_RXSTBC_MASK |
IEEE80211_VHT_CAP_TXSTBC |
IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE |
IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE |
@@ -1164,6 +1180,7 @@
#if IS_ENABLED(CONFIG_IPV6)
unregister_inet6addr_notifier(&local->ifa6_notifier);
#endif
+ ieee80211_txq_teardown_flows(local);
rtnl_lock();
@@ -1191,7 +1208,6 @@
skb_queue_purge(&local->skb_queue);
skb_queue_purge(&local->skb_queue_unreliable);
skb_queue_purge(&local->skb_queue_tdls_chsw);
- ieee80211_txq_teardown_flows(local);
destroy_workqueue(local->workqueue);
wiphy_unregister(local->hw.wiphy);
diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c
index fed598a..b0acb29 100644
--- a/net/mac80211/mesh_hwmp.c
+++ b/net/mac80211/mesh_hwmp.c
@@ -563,6 +563,10 @@
forward = false;
reply = true;
target_metric = 0;
+
+ if (SN_GT(target_sn, ifmsh->sn))
+ ifmsh->sn = target_sn;
+
if (time_after(jiffies, ifmsh->last_sn_update +
net_traversal_jiffies(sdata)) ||
time_before(jiffies, ifmsh->last_sn_update)) {
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 973adf3..70d289d 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -989,6 +989,10 @@
*/
if (sdata->reserved_chanctx) {
+ struct ieee80211_supported_band *sband = NULL;
+ struct sta_info *mgd_sta = NULL;
+ enum ieee80211_sta_rx_bandwidth bw = IEEE80211_STA_RX_BW_20;
+
/*
* with multi-vif csa driver may call ieee80211_csa_finish()
* many times while waiting for other interfaces to use their
@@ -997,6 +1001,48 @@
if (sdata->reserved_ready)
goto out;
+ if (sdata->vif.bss_conf.chandef.width !=
+ sdata->csa_chandef.width) {
+ /*
+ * For managed interface, we need to also update the AP
+ * station bandwidth and align the rate scale algorithm
+ * on the bandwidth change. Here we only consider the
+ * bandwidth of the new channel definition (as channel
+ * switch flow does not have the full HT/VHT/HE
+ * information), assuming that if additional changes are
+ * required they would be done as part of the processing
+ * of the next beacon from the AP.
+ */
+ switch (sdata->csa_chandef.width) {
+ case NL80211_CHAN_WIDTH_20_NOHT:
+ case NL80211_CHAN_WIDTH_20:
+ default:
+ bw = IEEE80211_STA_RX_BW_20;
+ break;
+ case NL80211_CHAN_WIDTH_40:
+ bw = IEEE80211_STA_RX_BW_40;
+ break;
+ case NL80211_CHAN_WIDTH_80:
+ bw = IEEE80211_STA_RX_BW_80;
+ break;
+ case NL80211_CHAN_WIDTH_80P80:
+ case NL80211_CHAN_WIDTH_160:
+ bw = IEEE80211_STA_RX_BW_160;
+ break;
+ }
+
+ mgd_sta = sta_info_get(sdata, ifmgd->bssid);
+ sband =
+ local->hw.wiphy->bands[sdata->csa_chandef.chan->band];
+ }
+
+ if (sdata->vif.bss_conf.chandef.width >
+ sdata->csa_chandef.width) {
+ mgd_sta->sta.bandwidth = bw;
+ rate_control_rate_update(local, sband, mgd_sta,
+ IEEE80211_RC_BW_CHANGED);
+ }
+
ret = ieee80211_vif_use_reserved_context(sdata);
if (ret) {
sdata_info(sdata,
@@ -1007,6 +1053,13 @@
goto out;
}
+ if (sdata->vif.bss_conf.chandef.width <
+ sdata->csa_chandef.width) {
+ mgd_sta->sta.bandwidth = bw;
+ rate_control_rate_update(local, sband, mgd_sta,
+ IEEE80211_RC_BW_CHANGED);
+ }
+
goto out;
}
@@ -1229,6 +1282,16 @@
cbss->beacon_interval));
return;
drop_connection:
+ /*
+ * This is just so that the disconnect flow will know that
+ * we were trying to switch channel and failed. In case the
+ * mode is 1 (we are not allowed to Tx), we will know not to
+ * send a deauthentication frame. Those two fields will be
+ * reset when the disconnection worker runs.
+ */
+ sdata->vif.csa_active = true;
+ sdata->csa_block_tx = csa_ie.mode;
+
ieee80211_queue_work(&local->hw, &ifmgd->csa_connection_drop_work);
mutex_unlock(&local->chanctx_mtx);
mutex_unlock(&local->mtx);
@@ -2401,6 +2464,7 @@
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN];
+ bool tx;
sdata_lock(sdata);
if (!ifmgd->associated) {
@@ -2408,6 +2472,8 @@
return;
}
+ tx = !sdata->csa_block_tx;
+
/* AP is probably out of range (or not reachable for another reason) so
* remove the bss struct for that AP.
*/
@@ -2415,7 +2481,7 @@
ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH,
WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY,
- true, frame_buf);
+ tx, frame_buf);
mutex_lock(&local->mtx);
sdata->vif.csa_active = false;
ifmgd->csa_waiting_bcn = false;
@@ -2426,7 +2492,7 @@
}
mutex_unlock(&local->mtx);
- ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true,
+ ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), tx,
WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY);
sdata_unlock(sdata);
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 13ff407..c934189 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -1310,7 +1310,7 @@
u8 *op_class)
{
u8 vht_opclass;
- u16 freq = chandef->center_freq1;
+ u32 freq = chandef->center_freq1;
if (freq >= 2412 && freq <= 2472) {
if (chandef->width > NL80211_CHAN_WIDTH_40)
diff --git a/sound/aoa/core/gpio-feature.c b/sound/aoa/core/gpio-feature.c
index 7196008..6555742 100644
--- a/sound/aoa/core/gpio-feature.c
+++ b/sound/aoa/core/gpio-feature.c
@@ -88,8 +88,10 @@
}
reg = of_get_property(np, "reg", NULL);
- if (!reg)
+ if (!reg) {
+ of_node_put(np);
return NULL;
+ }
*gpioptr = *reg;
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c
index 3469ac14..d0dfa82 100644
--- a/sound/firewire/bebob/bebob.c
+++ b/sound/firewire/bebob/bebob.c
@@ -263,6 +263,8 @@
error:
mutex_unlock(&devices_mutex);
snd_bebob_stream_destroy_duplex(bebob);
+ kfree(bebob->maudio_special_quirk);
+ bebob->maudio_special_quirk = NULL;
snd_card_free(bebob->card);
dev_info(&bebob->unit->device,
"Sound card registration failed: %d\n", err);
diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c
index 07e5abd..6dbf047 100644
--- a/sound/firewire/bebob/bebob_maudio.c
+++ b/sound/firewire/bebob/bebob_maudio.c
@@ -96,17 +96,13 @@
struct fw_device *device = fw_parent_device(unit);
int err, rcode;
u64 date;
- __le32 cues[3] = {
- cpu_to_le32(MAUDIO_BOOTLOADER_CUE1),
- cpu_to_le32(MAUDIO_BOOTLOADER_CUE2),
- cpu_to_le32(MAUDIO_BOOTLOADER_CUE3)
- };
+ __le32 *cues;
/* check date of software used to build */
err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE,
&date, sizeof(u64));
if (err < 0)
- goto end;
+ return err;
/*
* firmware version 5058 or later has date later than "20070401", but
* 'date' is not null-terminated.
@@ -114,20 +110,28 @@
if (date < 0x3230303730343031LL) {
dev_err(&unit->device,
"Use firmware version 5058 or later\n");
- err = -ENOSYS;
- goto end;
+ return -ENXIO;
}
+ cues = kmalloc_array(3, sizeof(*cues), GFP_KERNEL);
+ if (!cues)
+ return -ENOMEM;
+
+ cues[0] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE1);
+ cues[1] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE2);
+ cues[2] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE3);
+
rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST,
device->node_id, device->generation,
device->max_speed, BEBOB_ADDR_REG_REQ,
- cues, sizeof(cues));
+ cues, 3 * sizeof(*cues));
+ kfree(cues);
if (rcode != RCODE_COMPLETE) {
dev_err(&unit->device,
"Failed to send a cue to load firmware\n");
err = -EIO;
}
-end:
+
return err;
}
@@ -290,10 +294,6 @@
bebob->midi_output_ports = 2;
}
end:
- if (err < 0) {
- kfree(params);
- bebob->maudio_special_quirk = NULL;
- }
mutex_unlock(&bebob->mutex);
return err;
}
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index 1f5e1d2..ef68999 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -49,6 +49,7 @@
fw_unit_put(dg00x->unit);
mutex_destroy(&dg00x->mutex);
+ kfree(dg00x);
}
static void dg00x_card_free(struct snd_card *card)
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index 71a0613..f2d0733 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -301,6 +301,8 @@
snd_efw_transaction_remove_instance(efw);
snd_efw_stream_destroy_duplex(efw);
snd_card_free(efw->card);
+ kfree(efw->resp_buf);
+ efw->resp_buf = NULL;
dev_info(&efw->unit->device,
"Sound card registration failed: %d\n", err);
}
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 474b06d..696b6cf 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -135,6 +135,7 @@
kfree(oxfw->spec);
mutex_destroy(&oxfw->mutex);
+ kfree(oxfw);
}
/*
@@ -212,6 +213,7 @@
static void do_registration(struct work_struct *work)
{
struct snd_oxfw *oxfw = container_of(work, struct snd_oxfw, dwork.work);
+ int i;
int err;
if (oxfw->registered)
@@ -274,7 +276,15 @@
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
if (oxfw->has_output)
snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
+ for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; ++i) {
+ kfree(oxfw->tx_stream_formats[i]);
+ oxfw->tx_stream_formats[i] = NULL;
+ kfree(oxfw->rx_stream_formats[i]);
+ oxfw->rx_stream_formats[i] = NULL;
+ }
snd_card_free(oxfw->card);
+ kfree(oxfw->spec);
+ oxfw->spec = NULL;
dev_info(&oxfw->unit->device,
"Sound card registration failed: %d\n", err);
}
diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c
index 9dc93a7..4c967ac 100644
--- a/sound/firewire/tascam/tascam.c
+++ b/sound/firewire/tascam/tascam.c
@@ -93,6 +93,7 @@
fw_unit_put(tscm->unit);
mutex_destroy(&tscm->mutex);
+ kfree(tscm);
}
static void tscm_card_free(struct snd_card *card)
diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
index 56fc47b..50b216f 100644
--- a/sound/pci/emu10k1/emufx.c
+++ b/sound/pci/emu10k1/emufx.c
@@ -2520,7 +2520,7 @@
emu->support_tlv = 1;
return put_user(SNDRV_EMU10K1_VERSION, (int __user *)argp);
case SNDRV_EMU10K1_IOCTL_INFO:
- info = kmalloc(sizeof(*info), GFP_KERNEL);
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
snd_emu10k1_fx8010_info(emu, info);
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 4e331dd..f913809 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -2349,7 +2349,8 @@
.driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB },
/* AMD Raven */
{ PCI_DEVICE(0x1022, 0x15e3),
- .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB },
+ .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_SB |
+ AZX_DCAPS_PM_RUNTIME },
/* ATI HDMI */
{ PCI_DEVICE(0x1002, 0x0002),
.driver_data = AZX_DRIVER_ATIHDMI_NS | AZX_DCAPS_PRESET_ATI_HDMI_NS },
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index f03a143..ca29457 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -5698,6 +5698,7 @@
SND_PCI_QUIRK(0x1028, 0x0706, "Dell Inspiron 7559", ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER),
SND_PCI_QUIRK(0x1028, 0x0725, "Dell Inspiron 3162", ALC255_FIXUP_DELL_SPK_NOISE),
SND_PCI_QUIRK(0x1028, 0x075b, "Dell XPS 13 9360", ALC256_FIXUP_DELL_XPS_13_HEADPHONE_NOISE),
+ SND_PCI_QUIRK(0x1028, 0x075c, "Dell XPS 27 7760", ALC298_FIXUP_SPK_VOLUME),
SND_PCI_QUIRK(0x1028, 0x075d, "Dell AIO", ALC298_FIXUP_SPK_VOLUME),
SND_PCI_QUIRK(0x1028, 0x07b0, "Dell Precision 7520", ALC295_FIXUP_DISABLE_DAC3),
SND_PCI_QUIRK(0x1028, 0x0798, "Dell Inspiron 17 7000 Gaming", ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER),
diff --git a/sound/soc/codecs/cs4265.c b/sound/soc/codecs/cs4265.c
index fd966bb..6e8eb1f 100644
--- a/sound/soc/codecs/cs4265.c
+++ b/sound/soc/codecs/cs4265.c
@@ -157,8 +157,8 @@
SOC_SINGLE("Validity Bit Control Switch", CS4265_SPDIF_CTL2,
3, 1, 0),
SOC_ENUM("SPDIF Mono/Stereo", spdif_mono_stereo_enum),
- SOC_SINGLE("MMTLR Data Switch", 0,
- 1, 1, 0),
+ SOC_SINGLE("MMTLR Data Switch", CS4265_SPDIF_CTL2,
+ 0, 1, 0),
SOC_ENUM("Mono Channel Select", spdif_mono_select_enum),
SND_SOC_BYTES("C Data Buffer", CS4265_C_DATA_BUFF, 24),
};
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 682c207..b36c856 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -3929,6 +3929,13 @@
continue;
}
+ /* let users know there is no DAI to link */
+ if (!dai_w->priv) {
+ dev_dbg(card->dev, "dai widget %s has no DAI\n",
+ dai_w->name);
+ continue;
+ }
+
dai = dai_w->priv;
/* ...find all widgets with the same stream and link them */
diff --git a/tools/arch/x86/include/asm/cpufeatures.h b/tools/arch/x86/include/asm/cpufeatures.h
index fbc1474..f6d1bc9 100644
--- a/tools/arch/x86/include/asm/cpufeatures.h
+++ b/tools/arch/x86/include/asm/cpufeatures.h
@@ -104,7 +104,6 @@
#define X86_FEATURE_EXTD_APICID ( 3*32+26) /* has extended APICID (8 bits) */
#define X86_FEATURE_AMD_DCM ( 3*32+27) /* multi-node processor */
#define X86_FEATURE_APERFMPERF ( 3*32+28) /* APERFMPERF */
-/* free, was #define X86_FEATURE_EAGER_FPU ( 3*32+29) * "eagerfpu" Non lazy FPU restore */
#define X86_FEATURE_NONSTOP_TSC_S3 ( 3*32+30) /* TSC doesn't stop in S3 state */
/* Intel-defined CPU features, CPUID level 0x00000001 (ecx), word 4 */
diff --git a/tools/perf/arch/powerpc/util/sym-handling.c b/tools/perf/arch/powerpc/util/sym-handling.c
index de477a3..01a288c 100644
--- a/tools/perf/arch/powerpc/util/sym-handling.c
+++ b/tools/perf/arch/powerpc/util/sym-handling.c
@@ -21,15 +21,16 @@
#endif
-#if !defined(_CALL_ELF) || _CALL_ELF != 2
int arch__choose_best_symbol(struct symbol *syma,
struct symbol *symb __maybe_unused)
{
char *sym = syma->name;
+#if !defined(_CALL_ELF) || _CALL_ELF != 2
/* Skip over any initial dot */
if (*sym == '.')
sym++;
+#endif
/* Avoid "SyS" kernel syscall aliases */
if (strlen(sym) >= 3 && !strncmp(sym, "SyS", 3))
@@ -40,6 +41,7 @@
return SYMBOL_A;
}
+#if !defined(_CALL_ELF) || _CALL_ELF != 2
/* Allow matching against dot variants */
int arch__compare_symbol_names(const char *namea, const char *nameb)
{
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index f55d108..3be8c48 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -241,8 +241,9 @@
{
struct perf_evsel *evsel = zalloc(perf_evsel__object.size);
- if (evsel != NULL)
- perf_evsel__init(evsel, attr, idx);
+ if (!evsel)
+ return NULL;
+ perf_evsel__init(evsel, attr, idx);
if (perf_evsel__is_bpf_output(evsel)) {
evsel->attr.sample_type |= (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME |
diff --git a/tools/vm/page-types.c b/tools/vm/page-types.c
index e92903f..6d5bcba 100644
--- a/tools/vm/page-types.c
+++ b/tools/vm/page-types.c
@@ -155,12 +155,6 @@
};
-static const char * const debugfs_known_mountpoints[] = {
- "/sys/kernel/debug",
- "/debug",
- 0,
-};
-
/*
* data structures
*/
diff --git a/tools/vm/slabinfo.c b/tools/vm/slabinfo.c
index b9d34b3..6975ec4 100644
--- a/tools/vm/slabinfo.c
+++ b/tools/vm/slabinfo.c
@@ -29,8 +29,8 @@
int alias;
int refs;
int aliases, align, cache_dma, cpu_slabs, destroy_by_rcu;
- int hwcache_align, object_size, objs_per_slab;
- int sanity_checks, slab_size, store_user, trace;
+ unsigned int hwcache_align, object_size, objs_per_slab;
+ unsigned int sanity_checks, slab_size, store_user, trace;
int order, poison, reclaim_account, red_zone;
unsigned long partial, objects, slabs, objects_partial, objects_total;
unsigned long alloc_fastpath, alloc_slowpath;