17667 lines
553 KiB
Diff
17667 lines
553 KiB
Diff
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
|
|
index 4cd15aee16c20..66dfc348043d6 100644
|
|
--- a/Documentation/admin-guide/kernel-parameters.txt
|
|
+++ b/Documentation/admin-guide/kernel-parameters.txt
|
|
@@ -6853,6 +6853,9 @@
|
|
pause after every control message);
|
|
o = USB_QUIRK_HUB_SLOW_RESET (Hub needs extra
|
|
delay after resetting its port);
|
|
+ p = USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT
|
|
+ (Reduce timeout of the SET_ADDRESS
|
|
+ request from 5000 ms to 500 ms);
|
|
Example: quirks=0781:5580:bk,0a5c:5834:gij
|
|
|
|
usbhid.mousepoll=
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 40312bb550f06..72a2880afab7a 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -8142,7 +8142,7 @@ M: Geoffrey D. Bennett <g@b4.vu>
|
|
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
|
|
S: Maintained
|
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git
|
|
-F: sound/usb/mixer_scarlett_gen2.c
|
|
+F: sound/usb/mixer_scarlett2.c
|
|
|
|
FORCEDETH GIGABIT ETHERNET DRIVER
|
|
M: Rain River <rain.1986.08.12@gmail.com>
|
|
diff --git a/Makefile b/Makefile
|
|
index a3839877aafd6..bb103505791e4 100644
|
|
--- a/Makefile
|
|
+++ b/Makefile
|
|
@@ -1,7 +1,7 @@
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
VERSION = 6
|
|
PATCHLEVEL = 6
|
|
-SUBLEVEL = 28
|
|
+SUBLEVEL = 29
|
|
EXTRAVERSION =
|
|
NAME = Hurr durr I'ma ninja sloth
|
|
|
|
diff --git a/arch/arm/mach-omap2/pdata-quirks.c b/arch/arm/mach-omap2/pdata-quirks.c
|
|
index c1c0121f478d6..b947bacf23a37 100644
|
|
--- a/arch/arm/mach-omap2/pdata-quirks.c
|
|
+++ b/arch/arm/mach-omap2/pdata-quirks.c
|
|
@@ -275,9 +275,19 @@ static struct platform_device pandora_backlight = {
|
|
.id = -1,
|
|
};
|
|
|
|
+static struct gpiod_lookup_table pandora_soc_audio_gpios = {
|
|
+ .dev_id = "soc-audio",
|
|
+ .table = {
|
|
+ GPIO_LOOKUP("gpio-112-127", 6, "dac", GPIO_ACTIVE_HIGH),
|
|
+ GPIO_LOOKUP("gpio-0-15", 14, "amp", GPIO_ACTIVE_HIGH),
|
|
+ { }
|
|
+ },
|
|
+};
|
|
+
|
|
static void __init omap3_pandora_legacy_init(void)
|
|
{
|
|
platform_device_register(&pandora_backlight);
|
|
+ gpiod_add_lookup_table(&pandora_soc_audio_gpios);
|
|
}
|
|
#endif /* CONFIG_ARCH_OMAP3 */
|
|
|
|
diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h
|
|
index b149cf9f91bc9..b73baaf8ae47b 100644
|
|
--- a/arch/arm64/include/asm/tlbflush.h
|
|
+++ b/arch/arm64/include/asm/tlbflush.h
|
|
@@ -152,12 +152,18 @@ static inline unsigned long get_trans_granule(void)
|
|
#define MAX_TLBI_RANGE_PAGES __TLBI_RANGE_PAGES(31, 3)
|
|
|
|
/*
|
|
- * Generate 'num' values from -1 to 30 with -1 rejected by the
|
|
- * __flush_tlb_range() loop below.
|
|
+ * Generate 'num' values from -1 to 31 with -1 rejected by the
|
|
+ * __flush_tlb_range() loop below. Its return value is only
|
|
+ * significant for a maximum of MAX_TLBI_RANGE_PAGES pages. If
|
|
+ * 'pages' is more than that, you must iterate over the overall
|
|
+ * range.
|
|
*/
|
|
-#define TLBI_RANGE_MASK GENMASK_ULL(4, 0)
|
|
-#define __TLBI_RANGE_NUM(pages, scale) \
|
|
- ((((pages) >> (5 * (scale) + 1)) & TLBI_RANGE_MASK) - 1)
|
|
+#define __TLBI_RANGE_NUM(pages, scale) \
|
|
+ ({ \
|
|
+ int __pages = min((pages), \
|
|
+ __TLBI_RANGE_PAGES(31, (scale))); \
|
|
+ (__pages >> (5 * (scale) + 1)) - 1; \
|
|
+ })
|
|
|
|
/*
|
|
* TLB Invalidation
|
|
@@ -351,29 +357,25 @@ static inline void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch)
|
|
* entries one by one at the granularity of 'stride'. If the TLB
|
|
* range ops are supported, then:
|
|
*
|
|
- * 1. If 'pages' is odd, flush the first page through non-range
|
|
- * operations;
|
|
- *
|
|
- * 2. For remaining pages: the minimum range granularity is decided
|
|
- * by 'scale', so multiple range TLBI operations may be required.
|
|
- * Start from scale = 0, flush the corresponding number of pages
|
|
- * ((num+1)*2^(5*scale+1) starting from 'addr'), then increase it
|
|
- * until no pages left.
|
|
+ * 1. The minimum range granularity is decided by 'scale', so multiple range
|
|
+ * TLBI operations may be required. Start from scale = 3, flush the largest
|
|
+ * possible number of pages ((num+1)*2^(5*scale+1)) that fit into the
|
|
+ * requested range, then decrement scale and continue until one or zero pages
|
|
+ * are left.
|
|
*
|
|
- * Note that certain ranges can be represented by either num = 31 and
|
|
- * scale or num = 0 and scale + 1. The loop below favours the latter
|
|
- * since num is limited to 30 by the __TLBI_RANGE_NUM() macro.
|
|
+ * 2. If there is 1 page remaining, flush it through non-range operations. Range
|
|
+ * operations can only span an even number of pages.
|
|
*/
|
|
#define __flush_tlb_range_op(op, start, pages, stride, \
|
|
asid, tlb_level, tlbi_user) \
|
|
do { \
|
|
int num = 0; \
|
|
- int scale = 0; \
|
|
+ int scale = 3; \
|
|
unsigned long addr; \
|
|
\
|
|
while (pages > 0) { \
|
|
if (!system_supports_tlb_range() || \
|
|
- pages % 2 == 1) { \
|
|
+ pages == 1) { \
|
|
addr = __TLBI_VADDR(start, asid); \
|
|
__tlbi_level(op, addr, tlb_level); \
|
|
if (tlbi_user) \
|
|
@@ -393,7 +395,7 @@ do { \
|
|
start += __TLBI_RANGE_PAGES(num, scale) << PAGE_SHIFT; \
|
|
pages -= __TLBI_RANGE_PAGES(num, scale); \
|
|
} \
|
|
- scale++; \
|
|
+ scale--; \
|
|
} \
|
|
} while (0)
|
|
|
|
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
|
|
index 7b236994f0e15..6517bf2644a08 100644
|
|
--- a/arch/arm64/kernel/head.S
|
|
+++ b/arch/arm64/kernel/head.S
|
|
@@ -569,6 +569,11 @@ SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
|
|
adr_l x1, __hyp_text_end
|
|
adr_l x2, dcache_clean_poc
|
|
blr x2
|
|
+
|
|
+ mov_q x0, INIT_SCTLR_EL2_MMU_OFF
|
|
+ pre_disable_mmu_workaround
|
|
+ msr sctlr_el2, x0
|
|
+ isb
|
|
0:
|
|
mov_q x0, HCR_HOST_NVHE_FLAGS
|
|
msr hcr_el2, x0
|
|
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
|
|
index 924843f1f661b..0a62f458c5cb0 100644
|
|
--- a/arch/arm64/mm/pageattr.c
|
|
+++ b/arch/arm64/mm/pageattr.c
|
|
@@ -219,9 +219,6 @@ bool kernel_page_present(struct page *page)
|
|
pte_t *ptep;
|
|
unsigned long addr = (unsigned long)page_address(page);
|
|
|
|
- if (!can_set_direct_map())
|
|
- return true;
|
|
-
|
|
pgdp = pgd_offset_k(addr);
|
|
if (pgd_none(READ_ONCE(*pgdp)))
|
|
return false;
|
|
diff --git a/arch/powerpc/include/asm/ftrace.h b/arch/powerpc/include/asm/ftrace.h
|
|
index 9e5a39b6a3114..107fc5a484569 100644
|
|
--- a/arch/powerpc/include/asm/ftrace.h
|
|
+++ b/arch/powerpc/include/asm/ftrace.h
|
|
@@ -20,14 +20,6 @@
|
|
#ifndef __ASSEMBLY__
|
|
extern void _mcount(void);
|
|
|
|
-static inline unsigned long ftrace_call_adjust(unsigned long addr)
|
|
-{
|
|
- if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY))
|
|
- addr += MCOUNT_INSN_SIZE;
|
|
-
|
|
- return addr;
|
|
-}
|
|
-
|
|
unsigned long prepare_ftrace_return(unsigned long parent, unsigned long ip,
|
|
unsigned long sp);
|
|
|
|
@@ -142,8 +134,10 @@ static inline u8 this_cpu_get_ftrace_enabled(void) { return 1; }
|
|
#ifdef CONFIG_FUNCTION_TRACER
|
|
extern unsigned int ftrace_tramp_text[], ftrace_tramp_init[];
|
|
void ftrace_free_init_tramp(void);
|
|
+unsigned long ftrace_call_adjust(unsigned long addr);
|
|
#else
|
|
static inline void ftrace_free_init_tramp(void) { }
|
|
+static inline unsigned long ftrace_call_adjust(unsigned long addr) { return addr; }
|
|
#endif
|
|
#endif /* !__ASSEMBLY__ */
|
|
|
|
diff --git a/arch/powerpc/include/asm/sections.h b/arch/powerpc/include/asm/sections.h
|
|
index ea26665f82cfc..f43f3a6b0051c 100644
|
|
--- a/arch/powerpc/include/asm/sections.h
|
|
+++ b/arch/powerpc/include/asm/sections.h
|
|
@@ -14,6 +14,7 @@ typedef struct func_desc func_desc_t;
|
|
|
|
extern char __head_end[];
|
|
extern char __srwx_boundary[];
|
|
+extern char __exittext_begin[], __exittext_end[];
|
|
|
|
/* Patch sites */
|
|
extern s32 patch__call_flush_branch_caches1;
|
|
diff --git a/arch/powerpc/kernel/trace/ftrace.c b/arch/powerpc/kernel/trace/ftrace.c
|
|
index 82010629cf887..d8d6b4fd9a14c 100644
|
|
--- a/arch/powerpc/kernel/trace/ftrace.c
|
|
+++ b/arch/powerpc/kernel/trace/ftrace.c
|
|
@@ -27,10 +27,22 @@
|
|
#include <asm/ftrace.h>
|
|
#include <asm/syscall.h>
|
|
#include <asm/inst.h>
|
|
+#include <asm/sections.h>
|
|
|
|
#define NUM_FTRACE_TRAMPS 2
|
|
static unsigned long ftrace_tramps[NUM_FTRACE_TRAMPS];
|
|
|
|
+unsigned long ftrace_call_adjust(unsigned long addr)
|
|
+{
|
|
+ if (addr >= (unsigned long)__exittext_begin && addr < (unsigned long)__exittext_end)
|
|
+ return 0;
|
|
+
|
|
+ if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY))
|
|
+ addr += MCOUNT_INSN_SIZE;
|
|
+
|
|
+ return addr;
|
|
+}
|
|
+
|
|
static ppc_inst_t ftrace_create_branch_inst(unsigned long ip, unsigned long addr, int link)
|
|
{
|
|
ppc_inst_t op;
|
|
diff --git a/arch/powerpc/kernel/trace/ftrace_64_pg.c b/arch/powerpc/kernel/trace/ftrace_64_pg.c
|
|
index 7b85c3b460a3c..12fab1803bcf4 100644
|
|
--- a/arch/powerpc/kernel/trace/ftrace_64_pg.c
|
|
+++ b/arch/powerpc/kernel/trace/ftrace_64_pg.c
|
|
@@ -37,6 +37,11 @@
|
|
#define NUM_FTRACE_TRAMPS 8
|
|
static unsigned long ftrace_tramps[NUM_FTRACE_TRAMPS];
|
|
|
|
+unsigned long ftrace_call_adjust(unsigned long addr)
|
|
+{
|
|
+ return addr;
|
|
+}
|
|
+
|
|
static ppc_inst_t
|
|
ftrace_call_replace(unsigned long ip, unsigned long addr, int link)
|
|
{
|
|
diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S
|
|
index 1c5970df32336..f420df7888a75 100644
|
|
--- a/arch/powerpc/kernel/vmlinux.lds.S
|
|
+++ b/arch/powerpc/kernel/vmlinux.lds.S
|
|
@@ -281,7 +281,9 @@ SECTIONS
|
|
* to deal with references from __bug_table
|
|
*/
|
|
.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) {
|
|
+ __exittext_begin = .;
|
|
EXIT_TEXT
|
|
+ __exittext_end = .;
|
|
}
|
|
|
|
. = ALIGN(PAGE_SIZE);
|
|
diff --git a/arch/x86/include/asm/barrier.h b/arch/x86/include/asm/barrier.h
|
|
index 0216f63a366b5..d0795b5fab46a 100644
|
|
--- a/arch/x86/include/asm/barrier.h
|
|
+++ b/arch/x86/include/asm/barrier.h
|
|
@@ -79,6 +79,9 @@ do { \
|
|
#define __smp_mb__before_atomic() do { } while (0)
|
|
#define __smp_mb__after_atomic() do { } while (0)
|
|
|
|
+/* Writing to CR3 provides a full memory barrier in switch_mm(). */
|
|
+#define smp_mb__after_switch_mm() do { } while (0)
|
|
+
|
|
#include <asm-generic/barrier.h>
|
|
|
|
#endif /* _ASM_X86_BARRIER_H */
|
|
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
|
|
index fb9f5fa96cc96..ccba66da7a5d7 100644
|
|
--- a/arch/x86/include/asm/kvm_host.h
|
|
+++ b/arch/x86/include/asm/kvm_host.h
|
|
@@ -828,6 +828,7 @@ struct kvm_vcpu_arch {
|
|
int cpuid_nent;
|
|
struct kvm_cpuid_entry2 *cpuid_entries;
|
|
struct kvm_hypervisor_cpuid kvm_cpuid;
|
|
+ bool is_amd_compatible;
|
|
|
|
/*
|
|
* FIXME: Drop this macro and use KVM_NR_GOVERNED_FEATURES directly
|
|
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
|
|
index 5ff69b1d39b20..c2dc9b7426acb 100644
|
|
--- a/arch/x86/kernel/cpu/bugs.c
|
|
+++ b/arch/x86/kernel/cpu/bugs.c
|
|
@@ -1651,7 +1651,8 @@ static void __init bhi_select_mitigation(void)
|
|
return;
|
|
|
|
/* Retpoline mitigates against BHI unless the CPU has RRSBA behavior */
|
|
- if (cpu_feature_enabled(X86_FEATURE_RETPOLINE)) {
|
|
+ if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
|
|
+ !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE)) {
|
|
spec_ctrl_disable_kernel_rrsba();
|
|
if (rrsba_disabled)
|
|
return;
|
|
@@ -2803,11 +2804,13 @@ static const char *spectre_bhi_state(void)
|
|
{
|
|
if (!boot_cpu_has_bug(X86_BUG_BHI))
|
|
return "; BHI: Not affected";
|
|
- else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_HW))
|
|
+ else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_HW))
|
|
return "; BHI: BHI_DIS_S";
|
|
- else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_LOOP))
|
|
+ else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_LOOP))
|
|
return "; BHI: SW loop, KVM: SW loop";
|
|
- else if (boot_cpu_has(X86_FEATURE_RETPOLINE) && rrsba_disabled)
|
|
+ else if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
|
|
+ !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE) &&
|
|
+ rrsba_disabled)
|
|
return "; BHI: Retpoline";
|
|
else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_LOOP_ON_VMEXIT))
|
|
return "; BHI: Vulnerable, KVM: SW loop";
|
|
diff --git a/arch/x86/kernel/cpu/cpuid-deps.c b/arch/x86/kernel/cpu/cpuid-deps.c
|
|
index e462c1d3800a6..6fb6d8a57ceca 100644
|
|
--- a/arch/x86/kernel/cpu/cpuid-deps.c
|
|
+++ b/arch/x86/kernel/cpu/cpuid-deps.c
|
|
@@ -44,7 +44,10 @@ static const struct cpuid_dep cpuid_deps[] = {
|
|
{ X86_FEATURE_F16C, X86_FEATURE_XMM2, },
|
|
{ X86_FEATURE_AES, X86_FEATURE_XMM2 },
|
|
{ X86_FEATURE_SHA_NI, X86_FEATURE_XMM2 },
|
|
+ { X86_FEATURE_GFNI, X86_FEATURE_XMM2 },
|
|
{ X86_FEATURE_FMA, X86_FEATURE_AVX },
|
|
+ { X86_FEATURE_VAES, X86_FEATURE_AVX },
|
|
+ { X86_FEATURE_VPCLMULQDQ, X86_FEATURE_AVX },
|
|
{ X86_FEATURE_AVX2, X86_FEATURE_AVX, },
|
|
{ X86_FEATURE_AVX512F, X86_FEATURE_AVX, },
|
|
{ X86_FEATURE_AVX512IFMA, X86_FEATURE_AVX512F },
|
|
@@ -56,9 +59,6 @@ static const struct cpuid_dep cpuid_deps[] = {
|
|
{ X86_FEATURE_AVX512VL, X86_FEATURE_AVX512F },
|
|
{ X86_FEATURE_AVX512VBMI, X86_FEATURE_AVX512F },
|
|
{ X86_FEATURE_AVX512_VBMI2, X86_FEATURE_AVX512VL },
|
|
- { X86_FEATURE_GFNI, X86_FEATURE_AVX512VL },
|
|
- { X86_FEATURE_VAES, X86_FEATURE_AVX512VL },
|
|
- { X86_FEATURE_VPCLMULQDQ, X86_FEATURE_AVX512VL },
|
|
{ X86_FEATURE_AVX512_VNNI, X86_FEATURE_AVX512VL },
|
|
{ X86_FEATURE_AVX512_BITALG, X86_FEATURE_AVX512VL },
|
|
{ X86_FEATURE_AVX512_4VNNIW, X86_FEATURE_AVX512F },
|
|
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
|
|
index d65f91d0a8bca..77458137cab17 100644
|
|
--- a/arch/x86/kvm/cpuid.c
|
|
+++ b/arch/x86/kvm/cpuid.c
|
|
@@ -362,6 +362,7 @@ static void kvm_vcpu_after_set_cpuid(struct kvm_vcpu *vcpu)
|
|
|
|
kvm_update_pv_runtime(vcpu);
|
|
|
|
+ vcpu->arch.is_amd_compatible = guest_cpuid_is_amd_or_hygon(vcpu);
|
|
vcpu->arch.maxphyaddr = cpuid_query_maxphyaddr(vcpu);
|
|
vcpu->arch.reserved_gpa_bits = kvm_vcpu_reserved_gpa_bits_raw(vcpu);
|
|
|
|
diff --git a/arch/x86/kvm/cpuid.h b/arch/x86/kvm/cpuid.h
|
|
index 284fa4704553d..57ee789ada141 100644
|
|
--- a/arch/x86/kvm/cpuid.h
|
|
+++ b/arch/x86/kvm/cpuid.h
|
|
@@ -125,6 +125,16 @@ static inline bool guest_cpuid_is_intel(struct kvm_vcpu *vcpu)
|
|
return best && is_guest_vendor_intel(best->ebx, best->ecx, best->edx);
|
|
}
|
|
|
|
+static inline bool guest_cpuid_is_amd_compatible(struct kvm_vcpu *vcpu)
|
|
+{
|
|
+ return vcpu->arch.is_amd_compatible;
|
|
+}
|
|
+
|
|
+static inline bool guest_cpuid_is_intel_compatible(struct kvm_vcpu *vcpu)
|
|
+{
|
|
+ return !guest_cpuid_is_amd_compatible(vcpu);
|
|
+}
|
|
+
|
|
static inline int guest_cpuid_family(struct kvm_vcpu *vcpu)
|
|
{
|
|
struct kvm_cpuid_entry2 *best;
|
|
diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
|
|
index 23fab75993a51..66a2c4c0ae106 100644
|
|
--- a/arch/x86/kvm/lapic.c
|
|
+++ b/arch/x86/kvm/lapic.c
|
|
@@ -2772,7 +2772,8 @@ int kvm_apic_local_deliver(struct kvm_lapic *apic, int lvt_type)
|
|
trig_mode = reg & APIC_LVT_LEVEL_TRIGGER;
|
|
|
|
r = __apic_accept_irq(apic, mode, vector, 1, trig_mode, NULL);
|
|
- if (r && lvt_type == APIC_LVTPC)
|
|
+ if (r && lvt_type == APIC_LVTPC &&
|
|
+ guest_cpuid_is_intel_compatible(apic->vcpu))
|
|
kvm_lapic_set_reg(apic, APIC_LVTPC, reg | APIC_LVT_MASKED);
|
|
return r;
|
|
}
|
|
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
|
|
index 11c484d72eab2..294775b7383b4 100644
|
|
--- a/arch/x86/kvm/mmu/mmu.c
|
|
+++ b/arch/x86/kvm/mmu/mmu.c
|
|
@@ -4788,7 +4788,7 @@ static void reset_guest_rsvds_bits_mask(struct kvm_vcpu *vcpu,
|
|
context->cpu_role.base.level, is_efer_nx(context),
|
|
guest_can_use(vcpu, X86_FEATURE_GBPAGES),
|
|
is_cr4_pse(context),
|
|
- guest_cpuid_is_amd_or_hygon(vcpu));
|
|
+ guest_cpuid_is_amd_compatible(vcpu));
|
|
}
|
|
|
|
static void __reset_rsvds_bits_mask_ept(struct rsvd_bits_validate *rsvd_check,
|
|
diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
|
|
index 6cd4dd631a2fa..8eef3ed5fe04e 100644
|
|
--- a/arch/x86/kvm/mmu/tdp_mmu.c
|
|
+++ b/arch/x86/kvm/mmu/tdp_mmu.c
|
|
@@ -1506,6 +1506,16 @@ void kvm_tdp_mmu_try_split_huge_pages(struct kvm *kvm,
|
|
}
|
|
}
|
|
|
|
+static bool tdp_mmu_need_write_protect(struct kvm_mmu_page *sp)
|
|
+{
|
|
+ /*
|
|
+ * All TDP MMU shadow pages share the same role as their root, aside
|
|
+ * from level, so it is valid to key off any shadow page to determine if
|
|
+ * write protection is needed for an entire tree.
|
|
+ */
|
|
+ return kvm_mmu_page_ad_need_write_protect(sp) || !kvm_ad_enabled();
|
|
+}
|
|
+
|
|
/*
|
|
* Clear the dirty status of all the SPTEs mapping GFNs in the memslot. If
|
|
* AD bits are enabled, this will involve clearing the dirty bit on each SPTE.
|
|
@@ -1516,7 +1526,8 @@ void kvm_tdp_mmu_try_split_huge_pages(struct kvm *kvm,
|
|
static bool clear_dirty_gfn_range(struct kvm *kvm, struct kvm_mmu_page *root,
|
|
gfn_t start, gfn_t end)
|
|
{
|
|
- u64 dbit = kvm_ad_enabled() ? shadow_dirty_mask : PT_WRITABLE_MASK;
|
|
+ const u64 dbit = tdp_mmu_need_write_protect(root) ? PT_WRITABLE_MASK :
|
|
+ shadow_dirty_mask;
|
|
struct tdp_iter iter;
|
|
bool spte_set = false;
|
|
|
|
@@ -1530,7 +1541,7 @@ static bool clear_dirty_gfn_range(struct kvm *kvm, struct kvm_mmu_page *root,
|
|
if (!is_shadow_present_pte(iter.old_spte))
|
|
continue;
|
|
|
|
- KVM_MMU_WARN_ON(kvm_ad_enabled() &&
|
|
+ KVM_MMU_WARN_ON(dbit == shadow_dirty_mask &&
|
|
spte_ad_need_write_protect(iter.old_spte));
|
|
|
|
if (!(iter.old_spte & dbit))
|
|
@@ -1578,8 +1589,8 @@ bool kvm_tdp_mmu_clear_dirty_slot(struct kvm *kvm,
|
|
static void clear_dirty_pt_masked(struct kvm *kvm, struct kvm_mmu_page *root,
|
|
gfn_t gfn, unsigned long mask, bool wrprot)
|
|
{
|
|
- u64 dbit = (wrprot || !kvm_ad_enabled()) ? PT_WRITABLE_MASK :
|
|
- shadow_dirty_mask;
|
|
+ const u64 dbit = (wrprot || tdp_mmu_need_write_protect(root)) ? PT_WRITABLE_MASK :
|
|
+ shadow_dirty_mask;
|
|
struct tdp_iter iter;
|
|
|
|
lockdep_assert_held_write(&kvm->mmu_lock);
|
|
@@ -1591,7 +1602,7 @@ static void clear_dirty_pt_masked(struct kvm *kvm, struct kvm_mmu_page *root,
|
|
if (!mask)
|
|
break;
|
|
|
|
- KVM_MMU_WARN_ON(kvm_ad_enabled() &&
|
|
+ KVM_MMU_WARN_ON(dbit == shadow_dirty_mask &&
|
|
spte_ad_need_write_protect(iter.old_spte));
|
|
|
|
if (iter.level > PG_LEVEL_4K ||
|
|
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
|
|
index b2ed051611b08..dae499e2da84e 100644
|
|
--- a/arch/x86/kvm/vmx/vmx.c
|
|
+++ b/arch/x86/kvm/vmx/vmx.c
|
|
@@ -7858,8 +7858,28 @@ static u64 vmx_get_perf_capabilities(void)
|
|
|
|
if (vmx_pebs_supported()) {
|
|
perf_cap |= host_perf_cap & PERF_CAP_PEBS_MASK;
|
|
- if ((perf_cap & PERF_CAP_PEBS_FORMAT) < 4)
|
|
- perf_cap &= ~PERF_CAP_PEBS_BASELINE;
|
|
+
|
|
+ /*
|
|
+ * Disallow adaptive PEBS as it is functionally broken, can be
|
|
+ * used by the guest to read *host* LBRs, and can be used to
|
|
+ * bypass userspace event filters. To correctly and safely
|
|
+ * support adaptive PEBS, KVM needs to:
|
|
+ *
|
|
+ * 1. Account for the ADAPTIVE flag when (re)programming fixed
|
|
+ * counters.
|
|
+ *
|
|
+ * 2. Gain support from perf (or take direct control of counter
|
|
+ * programming) to support events without adaptive PEBS
|
|
+ * enabled for the hardware counter.
|
|
+ *
|
|
+ * 3. Ensure LBR MSRs cannot hold host data on VM-Entry with
|
|
+ * adaptive PEBS enabled and MSR_PEBS_DATA_CFG.LBRS=1.
|
|
+ *
|
|
+ * 4. Document which PMU events are effectively exposed to the
|
|
+ * guest via adaptive PEBS, and make adaptive PEBS mutually
|
|
+ * exclusive with KVM_SET_PMU_EVENT_FILTER if necessary.
|
|
+ */
|
|
+ perf_cap &= ~PERF_CAP_PEBS_BASELINE;
|
|
}
|
|
|
|
return perf_cap;
|
|
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
|
|
index 4ed8a7dc05369..cab7680f0d681 100644
|
|
--- a/arch/x86/kvm/x86.c
|
|
+++ b/arch/x86/kvm/x86.c
|
|
@@ -3317,7 +3317,7 @@ static bool is_mci_status_msr(u32 msr)
|
|
static bool can_set_mci_status(struct kvm_vcpu *vcpu)
|
|
{
|
|
/* McStatusWrEn enabled? */
|
|
- if (guest_cpuid_is_amd_or_hygon(vcpu))
|
|
+ if (guest_cpuid_is_amd_compatible(vcpu))
|
|
return !!(vcpu->arch.msr_hwcr & BIT_ULL(18));
|
|
|
|
return false;
|
|
diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c
|
|
index 1fbc9b921c4fc..736c2eb8c0f37 100644
|
|
--- a/drivers/accessibility/speakup/main.c
|
|
+++ b/drivers/accessibility/speakup/main.c
|
|
@@ -574,7 +574,7 @@ static u_long get_word(struct vc_data *vc)
|
|
}
|
|
attr_ch = get_char(vc, (u_short *)tmp_pos, &spk_attr);
|
|
buf[cnt++] = attr_ch;
|
|
- while (tmpx < vc->vc_cols - 1) {
|
|
+ while (tmpx < vc->vc_cols - 1 && cnt < sizeof(buf) - 1) {
|
|
tmp_pos += 2;
|
|
tmpx++;
|
|
ch = get_char(vc, (u_short *)tmp_pos, &temp);
|
|
diff --git a/drivers/android/binder.c b/drivers/android/binder.c
|
|
index 8460458ebe3d4..64f4573656c6d 100644
|
|
--- a/drivers/android/binder.c
|
|
+++ b/drivers/android/binder.c
|
|
@@ -1708,8 +1708,10 @@ static size_t binder_get_object(struct binder_proc *proc,
|
|
size_t object_size = 0;
|
|
|
|
read_size = min_t(size_t, sizeof(*object), buffer->data_size - offset);
|
|
- if (offset > buffer->data_size || read_size < sizeof(*hdr))
|
|
+ if (offset > buffer->data_size || read_size < sizeof(*hdr) ||
|
|
+ !IS_ALIGNED(offset, sizeof(u32)))
|
|
return 0;
|
|
+
|
|
if (u) {
|
|
if (copy_from_user(object, u + offset, read_size))
|
|
return 0;
|
|
diff --git a/drivers/char/random.c b/drivers/char/random.c
|
|
index 3cb37760dfec2..7b5d4822fa3ae 100644
|
|
--- a/drivers/char/random.c
|
|
+++ b/drivers/char/random.c
|
|
@@ -702,7 +702,7 @@ static void extract_entropy(void *buf, size_t len)
|
|
|
|
static void __cold _credit_init_bits(size_t bits)
|
|
{
|
|
- static struct execute_work set_ready;
|
|
+ static DECLARE_WORK(set_ready, crng_set_ready);
|
|
unsigned int new, orig, add;
|
|
unsigned long flags;
|
|
|
|
@@ -718,8 +718,8 @@ static void __cold _credit_init_bits(size_t bits)
|
|
|
|
if (orig < POOL_READY_BITS && new >= POOL_READY_BITS) {
|
|
crng_reseed(NULL); /* Sets crng_init to CRNG_READY under base_crng.lock. */
|
|
- if (static_key_initialized)
|
|
- execute_in_process_context(crng_set_ready, &set_ready);
|
|
+ if (static_key_initialized && system_unbound_wq)
|
|
+ queue_work(system_unbound_wq, &set_ready);
|
|
atomic_notifier_call_chain(&random_ready_notifier, 0, NULL);
|
|
wake_up_interruptible(&crng_init_wait);
|
|
kill_fasync(&fasync, SIGIO, POLL_IN);
|
|
@@ -890,8 +890,8 @@ void __init random_init(void)
|
|
|
|
/*
|
|
* If we were initialized by the cpu or bootloader before jump labels
|
|
- * are initialized, then we should enable the static branch here, where
|
|
- * it's guaranteed that jump labels have been initialized.
|
|
+ * or workqueues are initialized, then we should enable the static
|
|
+ * branch here, where it's guaranteed that these have been initialized.
|
|
*/
|
|
if (!static_branch_likely(&crng_is_ready) && crng_init >= CRNG_READY)
|
|
crng_set_ready(NULL);
|
|
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
|
|
index 50228cb0c5590..4a67c0d4823cf 100644
|
|
--- a/drivers/clk/clk.c
|
|
+++ b/drivers/clk/clk.c
|
|
@@ -37,6 +37,10 @@ static HLIST_HEAD(clk_root_list);
|
|
static HLIST_HEAD(clk_orphan_list);
|
|
static LIST_HEAD(clk_notifier_list);
|
|
|
|
+/* List of registered clks that use runtime PM */
|
|
+static HLIST_HEAD(clk_rpm_list);
|
|
+static DEFINE_MUTEX(clk_rpm_list_lock);
|
|
+
|
|
static const struct hlist_head *all_lists[] = {
|
|
&clk_root_list,
|
|
&clk_orphan_list,
|
|
@@ -59,6 +63,7 @@ struct clk_core {
|
|
struct clk_hw *hw;
|
|
struct module *owner;
|
|
struct device *dev;
|
|
+ struct hlist_node rpm_node;
|
|
struct device_node *of_node;
|
|
struct clk_core *parent;
|
|
struct clk_parent_map *parents;
|
|
@@ -122,6 +127,89 @@ static void clk_pm_runtime_put(struct clk_core *core)
|
|
pm_runtime_put_sync(core->dev);
|
|
}
|
|
|
|
+/**
|
|
+ * clk_pm_runtime_get_all() - Runtime "get" all clk provider devices
|
|
+ *
|
|
+ * Call clk_pm_runtime_get() on all runtime PM enabled clks in the clk tree so
|
|
+ * that disabling unused clks avoids a deadlock where a device is runtime PM
|
|
+ * resuming/suspending and the runtime PM callback is trying to grab the
|
|
+ * prepare_lock for something like clk_prepare_enable() while
|
|
+ * clk_disable_unused_subtree() holds the prepare_lock and is trying to runtime
|
|
+ * PM resume/suspend the device as well.
|
|
+ *
|
|
+ * Context: Acquires the 'clk_rpm_list_lock' and returns with the lock held on
|
|
+ * success. Otherwise the lock is released on failure.
|
|
+ *
|
|
+ * Return: 0 on success, negative errno otherwise.
|
|
+ */
|
|
+static int clk_pm_runtime_get_all(void)
|
|
+{
|
|
+ int ret;
|
|
+ struct clk_core *core, *failed;
|
|
+
|
|
+ /*
|
|
+ * Grab the list lock to prevent any new clks from being registered
|
|
+ * or unregistered until clk_pm_runtime_put_all().
|
|
+ */
|
|
+ mutex_lock(&clk_rpm_list_lock);
|
|
+
|
|
+ /*
|
|
+ * Runtime PM "get" all the devices that are needed for the clks
|
|
+ * currently registered. Do this without holding the prepare_lock, to
|
|
+ * avoid the deadlock.
|
|
+ */
|
|
+ hlist_for_each_entry(core, &clk_rpm_list, rpm_node) {
|
|
+ ret = clk_pm_runtime_get(core);
|
|
+ if (ret) {
|
|
+ failed = core;
|
|
+ pr_err("clk: Failed to runtime PM get '%s' for clk '%s'\n",
|
|
+ dev_name(failed->dev), failed->name);
|
|
+ goto err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err:
|
|
+ hlist_for_each_entry(core, &clk_rpm_list, rpm_node) {
|
|
+ if (core == failed)
|
|
+ break;
|
|
+
|
|
+ clk_pm_runtime_put(core);
|
|
+ }
|
|
+ mutex_unlock(&clk_rpm_list_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * clk_pm_runtime_put_all() - Runtime "put" all clk provider devices
|
|
+ *
|
|
+ * Put the runtime PM references taken in clk_pm_runtime_get_all() and release
|
|
+ * the 'clk_rpm_list_lock'.
|
|
+ */
|
|
+static void clk_pm_runtime_put_all(void)
|
|
+{
|
|
+ struct clk_core *core;
|
|
+
|
|
+ hlist_for_each_entry(core, &clk_rpm_list, rpm_node)
|
|
+ clk_pm_runtime_put(core);
|
|
+ mutex_unlock(&clk_rpm_list_lock);
|
|
+}
|
|
+
|
|
+static void clk_pm_runtime_init(struct clk_core *core)
|
|
+{
|
|
+ struct device *dev = core->dev;
|
|
+
|
|
+ if (dev && pm_runtime_enabled(dev)) {
|
|
+ core->rpm_enabled = true;
|
|
+
|
|
+ mutex_lock(&clk_rpm_list_lock);
|
|
+ hlist_add_head(&core->rpm_node, &clk_rpm_list);
|
|
+ mutex_unlock(&clk_rpm_list_lock);
|
|
+ }
|
|
+}
|
|
+
|
|
/*** locking ***/
|
|
static void clk_prepare_lock(void)
|
|
{
|
|
@@ -1362,9 +1450,6 @@ static void __init clk_unprepare_unused_subtree(struct clk_core *core)
|
|
if (core->flags & CLK_IGNORE_UNUSED)
|
|
return;
|
|
|
|
- if (clk_pm_runtime_get(core))
|
|
- return;
|
|
-
|
|
if (clk_core_is_prepared(core)) {
|
|
trace_clk_unprepare(core);
|
|
if (core->ops->unprepare_unused)
|
|
@@ -1373,8 +1458,6 @@ static void __init clk_unprepare_unused_subtree(struct clk_core *core)
|
|
core->ops->unprepare(core->hw);
|
|
trace_clk_unprepare_complete(core);
|
|
}
|
|
-
|
|
- clk_pm_runtime_put(core);
|
|
}
|
|
|
|
static void __init clk_disable_unused_subtree(struct clk_core *core)
|
|
@@ -1390,9 +1473,6 @@ static void __init clk_disable_unused_subtree(struct clk_core *core)
|
|
if (core->flags & CLK_OPS_PARENT_ENABLE)
|
|
clk_core_prepare_enable(core->parent);
|
|
|
|
- if (clk_pm_runtime_get(core))
|
|
- goto unprepare_out;
|
|
-
|
|
flags = clk_enable_lock();
|
|
|
|
if (core->enable_count)
|
|
@@ -1417,8 +1497,6 @@ static void __init clk_disable_unused_subtree(struct clk_core *core)
|
|
|
|
unlock_out:
|
|
clk_enable_unlock(flags);
|
|
- clk_pm_runtime_put(core);
|
|
-unprepare_out:
|
|
if (core->flags & CLK_OPS_PARENT_ENABLE)
|
|
clk_core_disable_unprepare(core->parent);
|
|
}
|
|
@@ -1434,6 +1512,7 @@ __setup("clk_ignore_unused", clk_ignore_unused_setup);
|
|
static int __init clk_disable_unused(void)
|
|
{
|
|
struct clk_core *core;
|
|
+ int ret;
|
|
|
|
if (clk_ignore_unused) {
|
|
pr_warn("clk: Not disabling unused clocks\n");
|
|
@@ -1442,6 +1521,13 @@ static int __init clk_disable_unused(void)
|
|
|
|
pr_info("clk: Disabling unused clocks\n");
|
|
|
|
+ ret = clk_pm_runtime_get_all();
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ /*
|
|
+ * Grab the prepare lock to keep the clk topology stable while iterating
|
|
+ * over clks.
|
|
+ */
|
|
clk_prepare_lock();
|
|
|
|
hlist_for_each_entry(core, &clk_root_list, child_node)
|
|
@@ -1458,6 +1544,8 @@ static int __init clk_disable_unused(void)
|
|
|
|
clk_prepare_unlock();
|
|
|
|
+ clk_pm_runtime_put_all();
|
|
+
|
|
return 0;
|
|
}
|
|
late_initcall_sync(clk_disable_unused);
|
|
@@ -3191,28 +3279,41 @@ static void clk_summary_show_one(struct seq_file *s, struct clk_core *c,
|
|
int level)
|
|
{
|
|
int phase;
|
|
+ struct clk *clk_user;
|
|
+ int multi_node = 0;
|
|
|
|
- seq_printf(s, "%*s%-*s %7d %8d %8d %11lu %10lu ",
|
|
+ seq_printf(s, "%*s%-*s %-7d %-8d %-8d %-11lu %-10lu ",
|
|
level * 3 + 1, "",
|
|
- 30 - level * 3, c->name,
|
|
+ 35 - level * 3, c->name,
|
|
c->enable_count, c->prepare_count, c->protect_count,
|
|
clk_core_get_rate_recalc(c),
|
|
clk_core_get_accuracy_recalc(c));
|
|
|
|
phase = clk_core_get_phase(c);
|
|
if (phase >= 0)
|
|
- seq_printf(s, "%5d", phase);
|
|
+ seq_printf(s, "%-5d", phase);
|
|
else
|
|
seq_puts(s, "-----");
|
|
|
|
- seq_printf(s, " %6d", clk_core_get_scaled_duty_cycle(c, 100000));
|
|
+ seq_printf(s, " %-6d", clk_core_get_scaled_duty_cycle(c, 100000));
|
|
|
|
if (c->ops->is_enabled)
|
|
- seq_printf(s, " %9c\n", clk_core_is_enabled(c) ? 'Y' : 'N');
|
|
+ seq_printf(s, " %5c ", clk_core_is_enabled(c) ? 'Y' : 'N');
|
|
else if (!c->ops->enable)
|
|
- seq_printf(s, " %9c\n", 'Y');
|
|
+ seq_printf(s, " %5c ", 'Y');
|
|
else
|
|
- seq_printf(s, " %9c\n", '?');
|
|
+ seq_printf(s, " %5c ", '?');
|
|
+
|
|
+ hlist_for_each_entry(clk_user, &c->clks, clks_node) {
|
|
+ seq_printf(s, "%*s%-*s %-25s\n",
|
|
+ level * 3 + 2 + 105 * multi_node, "",
|
|
+ 30,
|
|
+ clk_user->dev_id ? clk_user->dev_id : "deviceless",
|
|
+ clk_user->con_id ? clk_user->con_id : "no_connection_id");
|
|
+
|
|
+ multi_node = 1;
|
|
+ }
|
|
+
|
|
}
|
|
|
|
static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c,
|
|
@@ -3220,9 +3321,7 @@ static void clk_summary_show_subtree(struct seq_file *s, struct clk_core *c,
|
|
{
|
|
struct clk_core *child;
|
|
|
|
- clk_pm_runtime_get(c);
|
|
clk_summary_show_one(s, c, level);
|
|
- clk_pm_runtime_put(c);
|
|
|
|
hlist_for_each_entry(child, &c->children, child_node)
|
|
clk_summary_show_subtree(s, child, level + 1);
|
|
@@ -3232,10 +3331,15 @@ static int clk_summary_show(struct seq_file *s, void *data)
|
|
{
|
|
struct clk_core *c;
|
|
struct hlist_head **lists = s->private;
|
|
+ int ret;
|
|
|
|
- seq_puts(s, " enable prepare protect duty hardware\n");
|
|
- seq_puts(s, " clock count count count rate accuracy phase cycle enable\n");
|
|
- seq_puts(s, "-------------------------------------------------------------------------------------------------------\n");
|
|
+ seq_puts(s, " enable prepare protect duty hardware connection\n");
|
|
+ seq_puts(s, " clock count count count rate accuracy phase cycle enable consumer id\n");
|
|
+ seq_puts(s, "---------------------------------------------------------------------------------------------------------------------------------------------\n");
|
|
+
|
|
+ ret = clk_pm_runtime_get_all();
|
|
+ if (ret)
|
|
+ return ret;
|
|
|
|
clk_prepare_lock();
|
|
|
|
@@ -3244,6 +3348,7 @@ static int clk_summary_show(struct seq_file *s, void *data)
|
|
clk_summary_show_subtree(s, c, 0);
|
|
|
|
clk_prepare_unlock();
|
|
+ clk_pm_runtime_put_all();
|
|
|
|
return 0;
|
|
}
|
|
@@ -3291,8 +3396,14 @@ static int clk_dump_show(struct seq_file *s, void *data)
|
|
struct clk_core *c;
|
|
bool first_node = true;
|
|
struct hlist_head **lists = s->private;
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_pm_runtime_get_all();
|
|
+ if (ret)
|
|
+ return ret;
|
|
|
|
seq_putc(s, '{');
|
|
+
|
|
clk_prepare_lock();
|
|
|
|
for (; *lists; lists++) {
|
|
@@ -3305,6 +3416,7 @@ static int clk_dump_show(struct seq_file *s, void *data)
|
|
}
|
|
|
|
clk_prepare_unlock();
|
|
+ clk_pm_runtime_put_all();
|
|
|
|
seq_puts(s, "}\n");
|
|
return 0;
|
|
@@ -3919,8 +4031,6 @@ static int __clk_core_init(struct clk_core *core)
|
|
}
|
|
|
|
clk_core_reparent_orphans_nolock();
|
|
-
|
|
- kref_init(&core->ref);
|
|
out:
|
|
clk_pm_runtime_put(core);
|
|
unlock:
|
|
@@ -4149,6 +4259,22 @@ static void clk_core_free_parent_map(struct clk_core *core)
|
|
kfree(core->parents);
|
|
}
|
|
|
|
+/* Free memory allocated for a struct clk_core */
|
|
+static void __clk_release(struct kref *ref)
|
|
+{
|
|
+ struct clk_core *core = container_of(ref, struct clk_core, ref);
|
|
+
|
|
+ if (core->rpm_enabled) {
|
|
+ mutex_lock(&clk_rpm_list_lock);
|
|
+ hlist_del(&core->rpm_node);
|
|
+ mutex_unlock(&clk_rpm_list_lock);
|
|
+ }
|
|
+
|
|
+ clk_core_free_parent_map(core);
|
|
+ kfree_const(core->name);
|
|
+ kfree(core);
|
|
+}
|
|
+
|
|
static struct clk *
|
|
__clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
|
|
{
|
|
@@ -4169,6 +4295,8 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
|
|
goto fail_out;
|
|
}
|
|
|
|
+ kref_init(&core->ref);
|
|
+
|
|
core->name = kstrdup_const(init->name, GFP_KERNEL);
|
|
if (!core->name) {
|
|
ret = -ENOMEM;
|
|
@@ -4181,9 +4309,8 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
|
|
}
|
|
core->ops = init->ops;
|
|
|
|
- if (dev && pm_runtime_enabled(dev))
|
|
- core->rpm_enabled = true;
|
|
core->dev = dev;
|
|
+ clk_pm_runtime_init(core);
|
|
core->of_node = np;
|
|
if (dev && dev->driver)
|
|
core->owner = dev->driver->owner;
|
|
@@ -4223,12 +4350,10 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
|
|
hw->clk = NULL;
|
|
|
|
fail_create_clk:
|
|
- clk_core_free_parent_map(core);
|
|
fail_parents:
|
|
fail_ops:
|
|
- kfree_const(core->name);
|
|
fail_name:
|
|
- kfree(core);
|
|
+ kref_put(&core->ref, __clk_release);
|
|
fail_out:
|
|
return ERR_PTR(ret);
|
|
}
|
|
@@ -4308,18 +4433,6 @@ int of_clk_hw_register(struct device_node *node, struct clk_hw *hw)
|
|
}
|
|
EXPORT_SYMBOL_GPL(of_clk_hw_register);
|
|
|
|
-/* Free memory allocated for a clock. */
|
|
-static void __clk_release(struct kref *ref)
|
|
-{
|
|
- struct clk_core *core = container_of(ref, struct clk_core, ref);
|
|
-
|
|
- lockdep_assert_held(&prepare_lock);
|
|
-
|
|
- clk_core_free_parent_map(core);
|
|
- kfree_const(core->name);
|
|
- kfree(core);
|
|
-}
|
|
-
|
|
/*
|
|
* Empty clk_ops for unregistered clocks. These are used temporarily
|
|
* after clk_unregister() was called on a clock and until last clock
|
|
diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c
|
|
index 2e55368dc4d82..bd37ab4d1a9bb 100644
|
|
--- a/drivers/clk/mediatek/clk-mtk.c
|
|
+++ b/drivers/clk/mediatek/clk-mtk.c
|
|
@@ -13,6 +13,7 @@
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "clk-mtk.h"
|
|
@@ -494,6 +495,16 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev,
|
|
return IS_ERR(base) ? PTR_ERR(base) : -ENOMEM;
|
|
}
|
|
|
|
+
|
|
+ devm_pm_runtime_enable(&pdev->dev);
|
|
+ /*
|
|
+ * Do a pm_runtime_resume_and_get() to workaround a possible
|
|
+ * deadlock between clk_register() and the genpd framework.
|
|
+ */
|
|
+ r = pm_runtime_resume_and_get(&pdev->dev);
|
|
+ if (r)
|
|
+ return r;
|
|
+
|
|
/* Calculate how many clk_hw_onecell_data entries to allocate */
|
|
num_clks = mcd->num_clks + mcd->num_composite_clks;
|
|
num_clks += mcd->num_fixed_clks + mcd->num_factor_clks;
|
|
@@ -574,6 +585,8 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev,
|
|
goto unregister_clks;
|
|
}
|
|
|
|
+ pm_runtime_put(&pdev->dev);
|
|
+
|
|
return r;
|
|
|
|
unregister_clks:
|
|
@@ -604,6 +617,8 @@ static int __mtk_clk_simple_probe(struct platform_device *pdev,
|
|
free_base:
|
|
if (mcd->shared_io && base)
|
|
iounmap(base);
|
|
+
|
|
+ pm_runtime_put(&pdev->dev);
|
|
return r;
|
|
}
|
|
|
|
diff --git a/drivers/comedi/drivers/vmk80xx.c b/drivers/comedi/drivers/vmk80xx.c
|
|
index 4536ed43f65b2..84dce5184a77a 100644
|
|
--- a/drivers/comedi/drivers/vmk80xx.c
|
|
+++ b/drivers/comedi/drivers/vmk80xx.c
|
|
@@ -641,33 +641,22 @@ static int vmk80xx_find_usb_endpoints(struct comedi_device *dev)
|
|
struct vmk80xx_private *devpriv = dev->private;
|
|
struct usb_interface *intf = comedi_to_usb_interface(dev);
|
|
struct usb_host_interface *iface_desc = intf->cur_altsetting;
|
|
- struct usb_endpoint_descriptor *ep_desc;
|
|
- int i;
|
|
-
|
|
- if (iface_desc->desc.bNumEndpoints != 2)
|
|
- return -ENODEV;
|
|
-
|
|
- for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
|
|
- ep_desc = &iface_desc->endpoint[i].desc;
|
|
-
|
|
- if (usb_endpoint_is_int_in(ep_desc) ||
|
|
- usb_endpoint_is_bulk_in(ep_desc)) {
|
|
- if (!devpriv->ep_rx)
|
|
- devpriv->ep_rx = ep_desc;
|
|
- continue;
|
|
- }
|
|
+ struct usb_endpoint_descriptor *ep_rx_desc, *ep_tx_desc;
|
|
+ int ret;
|
|
|
|
- if (usb_endpoint_is_int_out(ep_desc) ||
|
|
- usb_endpoint_is_bulk_out(ep_desc)) {
|
|
- if (!devpriv->ep_tx)
|
|
- devpriv->ep_tx = ep_desc;
|
|
- continue;
|
|
- }
|
|
- }
|
|
+ if (devpriv->model == VMK8061_MODEL)
|
|
+ ret = usb_find_common_endpoints(iface_desc, &ep_rx_desc,
|
|
+ &ep_tx_desc, NULL, NULL);
|
|
+ else
|
|
+ ret = usb_find_common_endpoints(iface_desc, NULL, NULL,
|
|
+ &ep_rx_desc, &ep_tx_desc);
|
|
|
|
- if (!devpriv->ep_rx || !devpriv->ep_tx)
|
|
+ if (ret)
|
|
return -ENODEV;
|
|
|
|
+ devpriv->ep_rx = ep_rx_desc;
|
|
+ devpriv->ep_tx = ep_tx_desc;
|
|
+
|
|
if (!usb_endpoint_maxp(devpriv->ep_rx) || !usb_endpoint_maxp(devpriv->ep_tx))
|
|
return -EINVAL;
|
|
|
|
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
|
|
index 928107d0bfea4..1124e2d4f8530 100644
|
|
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
|
|
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
|
|
@@ -562,7 +562,6 @@ static int amdgpu_ttm_io_mem_reserve(struct ttm_device *bdev,
|
|
struct ttm_resource *mem)
|
|
{
|
|
struct amdgpu_device *adev = amdgpu_ttm_adev(bdev);
|
|
- size_t bus_size = (size_t)mem->size;
|
|
|
|
switch (mem->mem_type) {
|
|
case TTM_PL_SYSTEM:
|
|
@@ -573,9 +572,6 @@ static int amdgpu_ttm_io_mem_reserve(struct ttm_device *bdev,
|
|
break;
|
|
case TTM_PL_VRAM:
|
|
mem->bus.offset = mem->start << PAGE_SHIFT;
|
|
- /* check if it's visible */
|
|
- if ((mem->bus.offset + bus_size) > adev->gmc.visible_vram_size)
|
|
- return -EINVAL;
|
|
|
|
if (adev->mman.aper_base_kaddr &&
|
|
mem->placement & TTM_PL_FLAG_CONTIGUOUS)
|
|
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
|
|
index 9fe1278fd5861..f5e78b0c08f7e 100644
|
|
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
|
|
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
|
|
@@ -1500,6 +1500,37 @@ static void amdgpu_vm_bo_insert_map(struct amdgpu_device *adev,
|
|
trace_amdgpu_vm_bo_map(bo_va, mapping);
|
|
}
|
|
|
|
+/* Validate operation parameters to prevent potential abuse */
|
|
+static int amdgpu_vm_verify_parameters(struct amdgpu_device *adev,
|
|
+ struct amdgpu_bo *bo,
|
|
+ uint64_t saddr,
|
|
+ uint64_t offset,
|
|
+ uint64_t size)
|
|
+{
|
|
+ uint64_t tmp, lpfn;
|
|
+
|
|
+ if (saddr & AMDGPU_GPU_PAGE_MASK
|
|
+ || offset & AMDGPU_GPU_PAGE_MASK
|
|
+ || size & AMDGPU_GPU_PAGE_MASK)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (check_add_overflow(saddr, size, &tmp)
|
|
+ || check_add_overflow(offset, size, &tmp)
|
|
+ || size == 0 /* which also leads to end < begin */)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* make sure object fit at this offset */
|
|
+ if (bo && offset + size > amdgpu_bo_size(bo))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* Ensure last pfn not exceed max_pfn */
|
|
+ lpfn = (saddr + size - 1) >> AMDGPU_GPU_PAGE_SHIFT;
|
|
+ if (lpfn >= adev->vm_manager.max_pfn)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
/**
|
|
* amdgpu_vm_bo_map - map bo inside a vm
|
|
*
|
|
@@ -1526,21 +1557,14 @@ int amdgpu_vm_bo_map(struct amdgpu_device *adev,
|
|
struct amdgpu_bo *bo = bo_va->base.bo;
|
|
struct amdgpu_vm *vm = bo_va->base.vm;
|
|
uint64_t eaddr;
|
|
+ int r;
|
|
|
|
- /* validate the parameters */
|
|
- if (saddr & ~PAGE_MASK || offset & ~PAGE_MASK || size & ~PAGE_MASK)
|
|
- return -EINVAL;
|
|
- if (saddr + size <= saddr || offset + size <= offset)
|
|
- return -EINVAL;
|
|
-
|
|
- /* make sure object fit at this offset */
|
|
- eaddr = saddr + size - 1;
|
|
- if ((bo && offset + size > amdgpu_bo_size(bo)) ||
|
|
- (eaddr >= adev->vm_manager.max_pfn << AMDGPU_GPU_PAGE_SHIFT))
|
|
- return -EINVAL;
|
|
+ r = amdgpu_vm_verify_parameters(adev, bo, saddr, offset, size);
|
|
+ if (r)
|
|
+ return r;
|
|
|
|
saddr /= AMDGPU_GPU_PAGE_SIZE;
|
|
- eaddr /= AMDGPU_GPU_PAGE_SIZE;
|
|
+ eaddr = saddr + (size - 1) / AMDGPU_GPU_PAGE_SIZE;
|
|
|
|
tmp = amdgpu_vm_it_iter_first(&vm->va, saddr, eaddr);
|
|
if (tmp) {
|
|
@@ -1593,17 +1617,9 @@ int amdgpu_vm_bo_replace_map(struct amdgpu_device *adev,
|
|
uint64_t eaddr;
|
|
int r;
|
|
|
|
- /* validate the parameters */
|
|
- if (saddr & ~PAGE_MASK || offset & ~PAGE_MASK || size & ~PAGE_MASK)
|
|
- return -EINVAL;
|
|
- if (saddr + size <= saddr || offset + size <= offset)
|
|
- return -EINVAL;
|
|
-
|
|
- /* make sure object fit at this offset */
|
|
- eaddr = saddr + size - 1;
|
|
- if ((bo && offset + size > amdgpu_bo_size(bo)) ||
|
|
- (eaddr >= adev->vm_manager.max_pfn << AMDGPU_GPU_PAGE_SHIFT))
|
|
- return -EINVAL;
|
|
+ r = amdgpu_vm_verify_parameters(adev, bo, saddr, offset, size);
|
|
+ if (r)
|
|
+ return r;
|
|
|
|
/* Allocate all the needed memory */
|
|
mapping = kmalloc(sizeof(*mapping), GFP_KERNEL);
|
|
@@ -1617,7 +1633,7 @@ int amdgpu_vm_bo_replace_map(struct amdgpu_device *adev,
|
|
}
|
|
|
|
saddr /= AMDGPU_GPU_PAGE_SIZE;
|
|
- eaddr /= AMDGPU_GPU_PAGE_SIZE;
|
|
+ eaddr = saddr + (size - 1) / AMDGPU_GPU_PAGE_SIZE;
|
|
|
|
mapping->start = saddr;
|
|
mapping->last = eaddr;
|
|
@@ -1704,10 +1720,14 @@ int amdgpu_vm_bo_clear_mappings(struct amdgpu_device *adev,
|
|
struct amdgpu_bo_va_mapping *before, *after, *tmp, *next;
|
|
LIST_HEAD(removed);
|
|
uint64_t eaddr;
|
|
+ int r;
|
|
+
|
|
+ r = amdgpu_vm_verify_parameters(adev, NULL, saddr, 0, size);
|
|
+ if (r)
|
|
+ return r;
|
|
|
|
- eaddr = saddr + size - 1;
|
|
saddr /= AMDGPU_GPU_PAGE_SIZE;
|
|
- eaddr /= AMDGPU_GPU_PAGE_SIZE;
|
|
+ eaddr = saddr + (size - 1) / AMDGPU_GPU_PAGE_SIZE;
|
|
|
|
/* Allocate all the needed memory */
|
|
before = kzalloc(sizeof(*before), GFP_KERNEL);
|
|
diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
|
|
index fbf053001af97..7a1a574106fac 100644
|
|
--- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c
|
|
+++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
|
|
@@ -818,9 +818,9 @@ struct kfd_process *kfd_create_process(struct task_struct *thread)
|
|
mutex_lock(&kfd_processes_mutex);
|
|
|
|
if (kfd_is_locked()) {
|
|
- mutex_unlock(&kfd_processes_mutex);
|
|
pr_debug("KFD is locked! Cannot create process");
|
|
- return ERR_PTR(-EINVAL);
|
|
+ process = ERR_PTR(-EINVAL);
|
|
+ goto out;
|
|
}
|
|
|
|
/* A prior open of /dev/kfd could have already created the process. */
|
|
diff --git a/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_optc.c b/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_optc.c
|
|
index e817fa4efeee5..058dee76054ea 100644
|
|
--- a/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_optc.c
|
|
+++ b/drivers/gpu/drm/amd/display/dc/dcn32/dcn32_optc.c
|
|
@@ -236,9 +236,6 @@ static void optc32_setup_manual_trigger(struct timing_generator *optc)
|
|
OTG_V_TOTAL_MAX_SEL, 1,
|
|
OTG_FORCE_LOCK_ON_EVENT, 0,
|
|
OTG_SET_V_TOTAL_MIN_MASK, (1 << 1)); /* TRIGA */
|
|
-
|
|
- // Setup manual flow control for EOF via TRIG_A
|
|
- optc->funcs->setup_manual_trigger(optc);
|
|
}
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c
|
|
index 3fe5e6439c401..aa93129c3397e 100644
|
|
--- a/drivers/gpu/drm/drm_panel_orientation_quirks.c
|
|
+++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c
|
|
@@ -348,6 +348,12 @@ static const struct dmi_system_id orientation_data[] = {
|
|
DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "IdeaPad Duet 3 10IGL5"),
|
|
},
|
|
.driver_data = (void *)&lcd1200x1920_rightside_up,
|
|
+ }, { /* Lenovo Legion Go 8APU1 */
|
|
+ .matches = {
|
|
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"),
|
|
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
|
|
+ },
|
|
+ .driver_data = (void *)&lcd1600x2560_leftside_up,
|
|
}, { /* Lenovo Yoga Book X90F / X90L */
|
|
.matches = {
|
|
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c b/drivers/gpu/drm/i915/display/intel_atomic.c
|
|
index 7cf51dd8c0567..aaddd8c0cfa0e 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_atomic.c
|
|
+++ b/drivers/gpu/drm/i915/display/intel_atomic.c
|
|
@@ -259,6 +259,7 @@ intel_crtc_duplicate_state(struct drm_crtc *crtc)
|
|
drm_property_blob_get(crtc_state->post_csc_lut);
|
|
|
|
crtc_state->update_pipe = false;
|
|
+ crtc_state->update_m_n = false;
|
|
crtc_state->disable_lp_wm = false;
|
|
crtc_state->disable_cxsr = false;
|
|
crtc_state->update_wm_pre = false;
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c
|
|
index 5aa6b998a1cb1..fc3a6eb1de741 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_cdclk.c
|
|
+++ b/drivers/gpu/drm/i915/display/intel_cdclk.c
|
|
@@ -2453,7 +2453,8 @@ intel_set_cdclk_pre_plane_update(struct intel_atomic_state *state)
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
- enum pipe pipe = new_cdclk_state->pipe;
|
|
+ struct intel_cdclk_config cdclk_config;
|
|
+ enum pipe pipe;
|
|
|
|
if (!intel_cdclk_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual))
|
|
@@ -2462,12 +2463,25 @@ intel_set_cdclk_pre_plane_update(struct intel_atomic_state *state)
|
|
if (IS_DG2(i915))
|
|
intel_cdclk_pcode_pre_notify(state);
|
|
|
|
- if (new_cdclk_state->disable_pipes ||
|
|
- old_cdclk_state->actual.cdclk <= new_cdclk_state->actual.cdclk) {
|
|
- drm_WARN_ON(&i915->drm, !new_cdclk_state->base.changed);
|
|
+ if (new_cdclk_state->disable_pipes) {
|
|
+ cdclk_config = new_cdclk_state->actual;
|
|
+ pipe = INVALID_PIPE;
|
|
+ } else {
|
|
+ if (new_cdclk_state->actual.cdclk >= old_cdclk_state->actual.cdclk) {
|
|
+ cdclk_config = new_cdclk_state->actual;
|
|
+ pipe = new_cdclk_state->pipe;
|
|
+ } else {
|
|
+ cdclk_config = old_cdclk_state->actual;
|
|
+ pipe = INVALID_PIPE;
|
|
+ }
|
|
|
|
- intel_set_cdclk(i915, &new_cdclk_state->actual, pipe);
|
|
+ cdclk_config.voltage_level = max(new_cdclk_state->actual.voltage_level,
|
|
+ old_cdclk_state->actual.voltage_level);
|
|
}
|
|
+
|
|
+ drm_WARN_ON(&i915->drm, !new_cdclk_state->base.changed);
|
|
+
|
|
+ intel_set_cdclk(i915, &cdclk_config, pipe);
|
|
}
|
|
|
|
/**
|
|
@@ -2485,7 +2499,7 @@ intel_set_cdclk_post_plane_update(struct intel_atomic_state *state)
|
|
intel_atomic_get_old_cdclk_state(state);
|
|
const struct intel_cdclk_state *new_cdclk_state =
|
|
intel_atomic_get_new_cdclk_state(state);
|
|
- enum pipe pipe = new_cdclk_state->pipe;
|
|
+ enum pipe pipe;
|
|
|
|
if (!intel_cdclk_changed(&old_cdclk_state->actual,
|
|
&new_cdclk_state->actual))
|
|
@@ -2495,11 +2509,14 @@ intel_set_cdclk_post_plane_update(struct intel_atomic_state *state)
|
|
intel_cdclk_pcode_post_notify(state);
|
|
|
|
if (!new_cdclk_state->disable_pipes &&
|
|
- old_cdclk_state->actual.cdclk > new_cdclk_state->actual.cdclk) {
|
|
- drm_WARN_ON(&i915->drm, !new_cdclk_state->base.changed);
|
|
+ new_cdclk_state->actual.cdclk < old_cdclk_state->actual.cdclk)
|
|
+ pipe = new_cdclk_state->pipe;
|
|
+ else
|
|
+ pipe = INVALID_PIPE;
|
|
|
|
- intel_set_cdclk(i915, &new_cdclk_state->actual, pipe);
|
|
- }
|
|
+ drm_WARN_ON(&i915->drm, !new_cdclk_state->base.changed);
|
|
+
|
|
+ intel_set_cdclk(i915, &new_cdclk_state->actual, pipe);
|
|
}
|
|
|
|
static int intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state)
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c
|
|
index 182c6dd64f47c..cfbfbfed3f5e6 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_crtc.c
|
|
+++ b/drivers/gpu/drm/i915/display/intel_crtc.c
|
|
@@ -468,9 +468,56 @@ static int intel_mode_vblank_start(const struct drm_display_mode *mode)
|
|
return vblank_start;
|
|
}
|
|
|
|
+static void intel_crtc_vblank_evade_scanlines(struct intel_atomic_state *state,
|
|
+ struct intel_crtc *crtc,
|
|
+ int *min, int *max, int *vblank_start)
|
|
+{
|
|
+ const struct intel_crtc_state *old_crtc_state =
|
|
+ intel_atomic_get_old_crtc_state(state, crtc);
|
|
+ const struct intel_crtc_state *new_crtc_state =
|
|
+ intel_atomic_get_new_crtc_state(state, crtc);
|
|
+ const struct intel_crtc_state *crtc_state;
|
|
+ const struct drm_display_mode *adjusted_mode;
|
|
+
|
|
+ /*
|
|
+ * During fastsets/etc. the transcoder is still
|
|
+ * running with the old timings at this point.
|
|
+ *
|
|
+ * TODO: maybe just use the active timings here?
|
|
+ */
|
|
+ if (intel_crtc_needs_modeset(new_crtc_state))
|
|
+ crtc_state = new_crtc_state;
|
|
+ else
|
|
+ crtc_state = old_crtc_state;
|
|
+
|
|
+ adjusted_mode = &crtc_state->hw.adjusted_mode;
|
|
+
|
|
+ if (crtc->mode_flags & I915_MODE_FLAG_VRR) {
|
|
+ if (intel_vrr_is_push_sent(crtc_state))
|
|
+ *vblank_start = intel_vrr_vmin_vblank_start(crtc_state);
|
|
+ else
|
|
+ *vblank_start = intel_vrr_vmax_vblank_start(crtc_state);
|
|
+ } else {
|
|
+ *vblank_start = intel_mode_vblank_start(adjusted_mode);
|
|
+ }
|
|
+
|
|
+ /* FIXME needs to be calibrated sensibly */
|
|
+ *min = *vblank_start - intel_usecs_to_scanlines(adjusted_mode,
|
|
+ VBLANK_EVASION_TIME_US);
|
|
+ *max = *vblank_start - 1;
|
|
+
|
|
+ /*
|
|
+ * M/N is double buffered on the transcoder's undelayed vblank,
|
|
+ * so with seamless M/N we must evade both vblanks.
|
|
+ */
|
|
+ if (new_crtc_state->update_m_n)
|
|
+ *min -= adjusted_mode->crtc_vblank_start - adjusted_mode->crtc_vdisplay;
|
|
+}
|
|
+
|
|
/**
|
|
* intel_pipe_update_start() - start update of a set of display registers
|
|
- * @new_crtc_state: the new crtc state
|
|
+ * @state: the atomic state
|
|
+ * @crtc: the crtc
|
|
*
|
|
* Mark the start of an update to pipe registers that should be updated
|
|
* atomically regarding vblank. If the next vblank will happens within
|
|
@@ -480,11 +527,12 @@ static int intel_mode_vblank_start(const struct drm_display_mode *mode)
|
|
* until a subsequent call to intel_pipe_update_end(). That is done to
|
|
* avoid random delays.
|
|
*/
|
|
-void intel_pipe_update_start(struct intel_crtc_state *new_crtc_state)
|
|
+void intel_pipe_update_start(struct intel_atomic_state *state,
|
|
+ struct intel_crtc *crtc)
|
|
{
|
|
- struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc);
|
|
struct drm_i915_private *dev_priv = to_i915(crtc->base.dev);
|
|
- const struct drm_display_mode *adjusted_mode = &new_crtc_state->hw.adjusted_mode;
|
|
+ struct intel_crtc_state *new_crtc_state =
|
|
+ intel_atomic_get_new_crtc_state(state, crtc);
|
|
long timeout = msecs_to_jiffies_timeout(1);
|
|
int scanline, min, max, vblank_start;
|
|
wait_queue_head_t *wq = drm_crtc_vblank_waitqueue(&crtc->base);
|
|
@@ -500,27 +548,7 @@ void intel_pipe_update_start(struct intel_crtc_state *new_crtc_state)
|
|
if (intel_crtc_needs_vblank_work(new_crtc_state))
|
|
intel_crtc_vblank_work_init(new_crtc_state);
|
|
|
|
- if (new_crtc_state->vrr.enable) {
|
|
- if (intel_vrr_is_push_sent(new_crtc_state))
|
|
- vblank_start = intel_vrr_vmin_vblank_start(new_crtc_state);
|
|
- else
|
|
- vblank_start = intel_vrr_vmax_vblank_start(new_crtc_state);
|
|
- } else {
|
|
- vblank_start = intel_mode_vblank_start(adjusted_mode);
|
|
- }
|
|
-
|
|
- /* FIXME needs to be calibrated sensibly */
|
|
- min = vblank_start - intel_usecs_to_scanlines(adjusted_mode,
|
|
- VBLANK_EVASION_TIME_US);
|
|
- max = vblank_start - 1;
|
|
-
|
|
- /*
|
|
- * M/N is double buffered on the transcoder's undelayed vblank,
|
|
- * so with seamless M/N we must evade both vblanks.
|
|
- */
|
|
- if (new_crtc_state->seamless_m_n && intel_crtc_needs_fastset(new_crtc_state))
|
|
- min -= adjusted_mode->crtc_vblank_start - adjusted_mode->crtc_vdisplay;
|
|
-
|
|
+ intel_crtc_vblank_evade_scanlines(state, crtc, &min, &max, &vblank_start);
|
|
if (min <= 0 || max <= 0)
|
|
goto irq_disable;
|
|
|
|
@@ -631,15 +659,18 @@ static void dbg_vblank_evade(struct intel_crtc *crtc, ktime_t end) {}
|
|
|
|
/**
|
|
* intel_pipe_update_end() - end update of a set of display registers
|
|
- * @new_crtc_state: the new crtc state
|
|
+ * @state: the atomic state
|
|
+ * @crtc: the crtc
|
|
*
|
|
* Mark the end of an update started with intel_pipe_update_start(). This
|
|
* re-enables interrupts and verifies the update was actually completed
|
|
* before a vblank.
|
|
*/
|
|
-void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state)
|
|
+void intel_pipe_update_end(struct intel_atomic_state *state,
|
|
+ struct intel_crtc *crtc)
|
|
{
|
|
- struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc);
|
|
+ struct intel_crtc_state *new_crtc_state =
|
|
+ intel_atomic_get_new_crtc_state(state, crtc);
|
|
enum pipe pipe = crtc->pipe;
|
|
int scanline_end = intel_get_crtc_scanline(crtc);
|
|
u32 end_vbl_count = intel_crtc_get_vblank_counter(crtc);
|
|
@@ -697,15 +728,6 @@ void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state)
|
|
*/
|
|
intel_vrr_send_push(new_crtc_state);
|
|
|
|
- /*
|
|
- * Seamless M/N update may need to update frame timings.
|
|
- *
|
|
- * FIXME Should be synchronized with the start of vblank somehow...
|
|
- */
|
|
- if (new_crtc_state->seamless_m_n && intel_crtc_needs_fastset(new_crtc_state))
|
|
- intel_crtc_update_active_timings(new_crtc_state,
|
|
- new_crtc_state->vrr.enable);
|
|
-
|
|
local_irq_enable();
|
|
|
|
if (intel_vgpu_active(dev_priv))
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_crtc.h b/drivers/gpu/drm/i915/display/intel_crtc.h
|
|
index 51a4c8df9e657..22d7993d1f0ba 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_crtc.h
|
|
+++ b/drivers/gpu/drm/i915/display/intel_crtc.h
|
|
@@ -36,8 +36,10 @@ void intel_crtc_state_reset(struct intel_crtc_state *crtc_state,
|
|
u32 intel_crtc_get_vblank_counter(struct intel_crtc *crtc);
|
|
void intel_crtc_vblank_on(const struct intel_crtc_state *crtc_state);
|
|
void intel_crtc_vblank_off(const struct intel_crtc_state *crtc_state);
|
|
-void intel_pipe_update_start(struct intel_crtc_state *new_crtc_state);
|
|
-void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state);
|
|
+void intel_pipe_update_start(struct intel_atomic_state *state,
|
|
+ struct intel_crtc *crtc);
|
|
+void intel_pipe_update_end(struct intel_atomic_state *state,
|
|
+ struct intel_crtc *crtc);
|
|
void intel_wait_for_vblank_workers(struct intel_atomic_state *state);
|
|
struct intel_crtc *intel_first_crtc(struct drm_i915_private *i915);
|
|
struct intel_crtc *intel_crtc_for_pipe(struct drm_i915_private *i915,
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
|
|
index a072fbb9872aa..1a59fca40252c 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_display.c
|
|
+++ b/drivers/gpu/drm/i915/display/intel_display.c
|
|
@@ -5215,7 +5215,7 @@ intel_pipe_config_compare(const struct intel_crtc_state *current_config,
|
|
PIPE_CONF_CHECK_X(lane_lat_optim_mask);
|
|
|
|
if (HAS_DOUBLE_BUFFERED_M_N(dev_priv)) {
|
|
- if (!fastset || !pipe_config->seamless_m_n)
|
|
+ if (!fastset || !pipe_config->update_m_n)
|
|
PIPE_CONF_CHECK_M_N(dp_m_n);
|
|
} else {
|
|
PIPE_CONF_CHECK_M_N(dp_m_n);
|
|
@@ -5353,7 +5353,7 @@ intel_pipe_config_compare(const struct intel_crtc_state *current_config,
|
|
if (IS_G4X(dev_priv) || DISPLAY_VER(dev_priv) >= 5)
|
|
PIPE_CONF_CHECK_I(pipe_bpp);
|
|
|
|
- if (!fastset || !pipe_config->seamless_m_n) {
|
|
+ if (!fastset || !pipe_config->update_m_n) {
|
|
PIPE_CONF_CHECK_I(hw.pipe_mode.crtc_clock);
|
|
PIPE_CONF_CHECK_I(hw.adjusted_mode.crtc_clock);
|
|
}
|
|
@@ -5448,6 +5448,7 @@ int intel_modeset_all_pipes(struct intel_atomic_state *state,
|
|
|
|
crtc_state->uapi.mode_changed = true;
|
|
crtc_state->update_pipe = false;
|
|
+ crtc_state->update_m_n = false;
|
|
|
|
ret = drm_atomic_add_affected_connectors(&state->base,
|
|
&crtc->base);
|
|
@@ -5565,13 +5566,14 @@ static void intel_crtc_check_fastset(const struct intel_crtc_state *old_crtc_sta
|
|
{
|
|
struct drm_i915_private *i915 = to_i915(old_crtc_state->uapi.crtc->dev);
|
|
|
|
- if (!intel_pipe_config_compare(old_crtc_state, new_crtc_state, true)) {
|
|
+ if (!intel_pipe_config_compare(old_crtc_state, new_crtc_state, true))
|
|
drm_dbg_kms(&i915->drm, "fastset requirement not met, forcing full modeset\n");
|
|
+ else
|
|
+ new_crtc_state->uapi.mode_changed = false;
|
|
|
|
- return;
|
|
- }
|
|
+ if (intel_crtc_needs_modeset(new_crtc_state))
|
|
+ new_crtc_state->update_m_n = false;
|
|
|
|
- new_crtc_state->uapi.mode_changed = false;
|
|
if (!intel_crtc_needs_modeset(new_crtc_state))
|
|
new_crtc_state->update_pipe = true;
|
|
}
|
|
@@ -6297,6 +6299,7 @@ int intel_atomic_check(struct drm_device *dev,
|
|
if (intel_cpu_transcoders_need_modeset(state, BIT(master))) {
|
|
new_crtc_state->uapi.mode_changed = true;
|
|
new_crtc_state->update_pipe = false;
|
|
+ new_crtc_state->update_m_n = false;
|
|
}
|
|
}
|
|
|
|
@@ -6309,6 +6312,7 @@ int intel_atomic_check(struct drm_device *dev,
|
|
if (intel_cpu_transcoders_need_modeset(state, trans)) {
|
|
new_crtc_state->uapi.mode_changed = true;
|
|
new_crtc_state->update_pipe = false;
|
|
+ new_crtc_state->update_m_n = false;
|
|
}
|
|
}
|
|
|
|
@@ -6316,6 +6320,7 @@ int intel_atomic_check(struct drm_device *dev,
|
|
if (intel_pipes_need_modeset(state, new_crtc_state->bigjoiner_pipes)) {
|
|
new_crtc_state->uapi.mode_changed = true;
|
|
new_crtc_state->update_pipe = false;
|
|
+ new_crtc_state->update_m_n = false;
|
|
}
|
|
}
|
|
}
|
|
@@ -6494,7 +6499,7 @@ static void intel_pipe_fastset(const struct intel_crtc_state *old_crtc_state,
|
|
IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv))
|
|
hsw_set_linetime_wm(new_crtc_state);
|
|
|
|
- if (new_crtc_state->seamless_m_n)
|
|
+ if (new_crtc_state->update_m_n)
|
|
intel_cpu_transcoder_set_m1_n1(crtc, new_crtc_state->cpu_transcoder,
|
|
&new_crtc_state->dp_m_n);
|
|
}
|
|
@@ -6533,6 +6538,8 @@ static void commit_pipe_post_planes(struct intel_atomic_state *state,
|
|
struct intel_crtc *crtc)
|
|
{
|
|
struct drm_i915_private *dev_priv = to_i915(state->base.dev);
|
|
+ const struct intel_crtc_state *old_crtc_state =
|
|
+ intel_atomic_get_old_crtc_state(state, crtc);
|
|
const struct intel_crtc_state *new_crtc_state =
|
|
intel_atomic_get_new_crtc_state(state, crtc);
|
|
|
|
@@ -6544,6 +6551,9 @@ static void commit_pipe_post_planes(struct intel_atomic_state *state,
|
|
if (DISPLAY_VER(dev_priv) >= 9 &&
|
|
!intel_crtc_needs_modeset(new_crtc_state))
|
|
skl_detach_scalers(new_crtc_state);
|
|
+
|
|
+ if (vrr_enabling(old_crtc_state, new_crtc_state))
|
|
+ intel_vrr_enable(new_crtc_state);
|
|
}
|
|
|
|
static void intel_enable_crtc(struct intel_atomic_state *state,
|
|
@@ -6584,12 +6594,6 @@ static void intel_update_crtc(struct intel_atomic_state *state,
|
|
intel_dpt_configure(crtc);
|
|
}
|
|
|
|
- if (vrr_enabling(old_crtc_state, new_crtc_state)) {
|
|
- intel_vrr_enable(new_crtc_state);
|
|
- intel_crtc_update_active_timings(new_crtc_state,
|
|
- new_crtc_state->vrr.enable);
|
|
- }
|
|
-
|
|
if (!modeset) {
|
|
if (new_crtc_state->preload_luts &&
|
|
intel_crtc_needs_color_update(new_crtc_state))
|
|
@@ -6616,7 +6620,7 @@ static void intel_update_crtc(struct intel_atomic_state *state,
|
|
intel_crtc_planes_update_noarm(state, crtc);
|
|
|
|
/* Perform vblank evasion around commit operation */
|
|
- intel_pipe_update_start(new_crtc_state);
|
|
+ intel_pipe_update_start(state, crtc);
|
|
|
|
commit_pipe_pre_planes(state, crtc);
|
|
|
|
@@ -6624,7 +6628,16 @@ static void intel_update_crtc(struct intel_atomic_state *state,
|
|
|
|
commit_pipe_post_planes(state, crtc);
|
|
|
|
- intel_pipe_update_end(new_crtc_state);
|
|
+ intel_pipe_update_end(state, crtc);
|
|
+
|
|
+ /*
|
|
+ * VRR/Seamless M/N update may need to update frame timings.
|
|
+ *
|
|
+ * FIXME Should be synchronized with the start of vblank somehow...
|
|
+ */
|
|
+ if (vrr_enabling(old_crtc_state, new_crtc_state) || new_crtc_state->update_m_n)
|
|
+ intel_crtc_update_active_timings(new_crtc_state,
|
|
+ new_crtc_state->vrr.enable);
|
|
|
|
/*
|
|
* We usually enable FIFO underrun interrupts as part of the
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_display_device.h b/drivers/gpu/drm/i915/display/intel_display_device.h
|
|
index 215e682bd8b7a..5fd07c1817766 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_display_device.h
|
|
+++ b/drivers/gpu/drm/i915/display/intel_display_device.h
|
|
@@ -46,6 +46,7 @@ struct drm_printer;
|
|
#define HAS_DPT(i915) (DISPLAY_VER(i915) >= 13)
|
|
#define HAS_DSB(i915) (DISPLAY_INFO(i915)->has_dsb)
|
|
#define HAS_DSC(__i915) (DISPLAY_RUNTIME_INFO(__i915)->has_dsc)
|
|
+#define HAS_DSC_MST(__i915) (DISPLAY_VER(__i915) >= 12 && HAS_DSC(__i915))
|
|
#define HAS_FBC(i915) (DISPLAY_RUNTIME_INFO(i915)->fbc_mask != 0)
|
|
#define HAS_FPGA_DBG_UNCLAIMED(i915) (DISPLAY_INFO(i915)->has_fpga_dbg)
|
|
#define HAS_FW_BLC(i915) (DISPLAY_VER(i915) > 2)
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h
|
|
index 8b0dc2b75da4a..1c23b186aff20 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_display_types.h
|
|
+++ b/drivers/gpu/drm/i915/display/intel_display_types.h
|
|
@@ -1084,6 +1084,7 @@ struct intel_crtc_state {
|
|
|
|
unsigned fb_bits; /* framebuffers to flip */
|
|
bool update_pipe; /* can a fast modeset be performed? */
|
|
+ bool update_m_n; /* update M/N seamlessly during fastset? */
|
|
bool disable_cxsr;
|
|
bool update_wm_pre, update_wm_post; /* watermarks are updated */
|
|
bool fifo_changed; /* FIFO split is changed */
|
|
@@ -1196,7 +1197,6 @@ struct intel_crtc_state {
|
|
/* m2_n2 for eDP downclock */
|
|
struct intel_link_m_n dp_m2_n2;
|
|
bool has_drrs;
|
|
- bool seamless_m_n;
|
|
|
|
/* PSR is supported but might not be enabled due the lack of enabled planes */
|
|
bool has_psr;
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
|
|
index 18ee4f2a87f9e..ccc47cf4d15d8 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_dp.c
|
|
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
|
|
@@ -1310,13 +1310,14 @@ bool intel_dp_has_hdmi_sink(struct intel_dp *intel_dp)
|
|
static bool intel_dp_source_supports_fec(struct intel_dp *intel_dp,
|
|
const struct intel_crtc_state *pipe_config)
|
|
{
|
|
+ struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base;
|
|
struct drm_i915_private *dev_priv = dp_to_i915(intel_dp);
|
|
|
|
- /* On TGL, FEC is supported on all Pipes */
|
|
if (DISPLAY_VER(dev_priv) >= 12)
|
|
return true;
|
|
|
|
- if (DISPLAY_VER(dev_priv) == 11 && pipe_config->cpu_transcoder != TRANSCODER_A)
|
|
+ if (DISPLAY_VER(dev_priv) == 11 && encoder->port != PORT_A &&
|
|
+ !intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DP_MST))
|
|
return true;
|
|
|
|
return false;
|
|
@@ -2147,8 +2148,12 @@ intel_dp_drrs_compute_config(struct intel_connector *connector,
|
|
intel_panel_downclock_mode(connector, &pipe_config->hw.adjusted_mode);
|
|
int pixel_clock;
|
|
|
|
- if (has_seamless_m_n(connector))
|
|
- pipe_config->seamless_m_n = true;
|
|
+ /*
|
|
+ * FIXME all joined pipes share the same transcoder.
|
|
+ * Need to account for that when updating M/N live.
|
|
+ */
|
|
+ if (has_seamless_m_n(connector) && !pipe_config->bigjoiner_pipes)
|
|
+ pipe_config->update_m_n = true;
|
|
|
|
if (!can_enable_drrs(connector, pipe_config, downclock_mode)) {
|
|
if (intel_cpu_transcoder_has_m2_n2(i915, pipe_config->cpu_transcoder))
|
|
diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c
|
|
index f104bd7f8c2a6..d2f8f20722d92 100644
|
|
--- a/drivers/gpu/drm/i915/display/intel_dp_mst.c
|
|
+++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c
|
|
@@ -964,7 +964,7 @@ intel_dp_mst_mode_valid_ctx(struct drm_connector *connector,
|
|
return 0;
|
|
}
|
|
|
|
- if (DISPLAY_VER(dev_priv) >= 10 &&
|
|
+ if (HAS_DSC_MST(dev_priv) &&
|
|
drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd)) {
|
|
/*
|
|
* TBD pass the connector BPC,
|
|
diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c
|
|
index 6f180ee138531..46e4a45e3c72a 100644
|
|
--- a/drivers/gpu/drm/i915/i915_vma.c
|
|
+++ b/drivers/gpu/drm/i915/i915_vma.c
|
|
@@ -33,6 +33,7 @@
|
|
#include "gt/intel_engine.h"
|
|
#include "gt/intel_engine_heartbeat.h"
|
|
#include "gt/intel_gt.h"
|
|
+#include "gt/intel_gt_pm.h"
|
|
#include "gt/intel_gt_requests.h"
|
|
#include "gt/intel_tlb.h"
|
|
|
|
@@ -102,12 +103,34 @@ static inline struct i915_vma *active_to_vma(struct i915_active *ref)
|
|
|
|
static int __i915_vma_active(struct i915_active *ref)
|
|
{
|
|
- return i915_vma_tryget(active_to_vma(ref)) ? 0 : -ENOENT;
|
|
+ struct i915_vma *vma = active_to_vma(ref);
|
|
+
|
|
+ if (!i915_vma_tryget(vma))
|
|
+ return -ENOENT;
|
|
+
|
|
+ /*
|
|
+ * Exclude global GTT VMA from holding a GT wakeref
|
|
+ * while active, otherwise GPU never goes idle.
|
|
+ */
|
|
+ if (!i915_vma_is_ggtt(vma))
|
|
+ intel_gt_pm_get(vma->vm->gt);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static void __i915_vma_retire(struct i915_active *ref)
|
|
{
|
|
- i915_vma_put(active_to_vma(ref));
|
|
+ struct i915_vma *vma = active_to_vma(ref);
|
|
+
|
|
+ if (!i915_vma_is_ggtt(vma)) {
|
|
+ /*
|
|
+ * Since we can be called from atomic contexts,
|
|
+ * use an async variant of intel_gt_pm_put().
|
|
+ */
|
|
+ intel_gt_pm_put_async(vma->vm->gt);
|
|
+ }
|
|
+
|
|
+ i915_vma_put(vma);
|
|
}
|
|
|
|
static struct i915_vma *
|
|
@@ -1403,7 +1426,7 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww,
|
|
struct i915_vma_work *work = NULL;
|
|
struct dma_fence *moving = NULL;
|
|
struct i915_vma_resource *vma_res = NULL;
|
|
- intel_wakeref_t wakeref = 0;
|
|
+ intel_wakeref_t wakeref;
|
|
unsigned int bound;
|
|
int err;
|
|
|
|
@@ -1423,8 +1446,14 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww,
|
|
if (err)
|
|
return err;
|
|
|
|
- if (flags & PIN_GLOBAL)
|
|
- wakeref = intel_runtime_pm_get(&vma->vm->i915->runtime_pm);
|
|
+ /*
|
|
+ * In case of a global GTT, we must hold a runtime-pm wakeref
|
|
+ * while global PTEs are updated. In other cases, we hold
|
|
+ * the rpm reference while the VMA is active. Since runtime
|
|
+ * resume may require allocations, which are forbidden inside
|
|
+ * vm->mutex, get the first rpm wakeref outside of the mutex.
|
|
+ */
|
|
+ wakeref = intel_runtime_pm_get(&vma->vm->i915->runtime_pm);
|
|
|
|
if (flags & vma->vm->bind_async_flags) {
|
|
/* lock VM */
|
|
@@ -1560,8 +1589,7 @@ int i915_vma_pin_ww(struct i915_vma *vma, struct i915_gem_ww_ctx *ww,
|
|
if (work)
|
|
dma_fence_work_commit_imm(&work->base);
|
|
err_rpm:
|
|
- if (wakeref)
|
|
- intel_runtime_pm_put(&vma->vm->i915->runtime_pm, wakeref);
|
|
+ intel_runtime_pm_put(&vma->vm->i915->runtime_pm, wakeref);
|
|
|
|
if (moving)
|
|
dma_fence_put(moving);
|
|
diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_0_sm8150.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_0_sm8150.h
|
|
index 99acaf917e430..f0c3804f42587 100644
|
|
--- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_0_sm8150.h
|
|
+++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_0_sm8150.h
|
|
@@ -77,7 +77,7 @@ static const struct dpu_sspp_cfg sm8150_sspp[] = {
|
|
.name = "sspp_0", .id = SSPP_VIG0,
|
|
.base = 0x4000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_0,
|
|
+ .sblk = &sm8150_vig_sblk_0,
|
|
.xin_id = 0,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG0,
|
|
@@ -85,7 +85,7 @@ static const struct dpu_sspp_cfg sm8150_sspp[] = {
|
|
.name = "sspp_1", .id = SSPP_VIG1,
|
|
.base = 0x6000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_1,
|
|
+ .sblk = &sm8150_vig_sblk_1,
|
|
.xin_id = 4,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG1,
|
|
@@ -93,7 +93,7 @@ static const struct dpu_sspp_cfg sm8150_sspp[] = {
|
|
.name = "sspp_2", .id = SSPP_VIG2,
|
|
.base = 0x8000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_2,
|
|
+ .sblk = &sm8150_vig_sblk_2,
|
|
.xin_id = 8,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG2,
|
|
@@ -101,7 +101,7 @@ static const struct dpu_sspp_cfg sm8150_sspp[] = {
|
|
.name = "sspp_3", .id = SSPP_VIG3,
|
|
.base = 0xa000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_3,
|
|
+ .sblk = &sm8150_vig_sblk_3,
|
|
.xin_id = 12,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG3,
|
|
diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_1_sc8180x.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_1_sc8180x.h
|
|
index c92fbf24fbac1..47de71e71e310 100644
|
|
--- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_1_sc8180x.h
|
|
+++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_5_1_sc8180x.h
|
|
@@ -76,7 +76,7 @@ static const struct dpu_sspp_cfg sc8180x_sspp[] = {
|
|
.name = "sspp_0", .id = SSPP_VIG0,
|
|
.base = 0x4000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_0,
|
|
+ .sblk = &sm8150_vig_sblk_0,
|
|
.xin_id = 0,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG0,
|
|
@@ -84,7 +84,7 @@ static const struct dpu_sspp_cfg sc8180x_sspp[] = {
|
|
.name = "sspp_1", .id = SSPP_VIG1,
|
|
.base = 0x6000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_1,
|
|
+ .sblk = &sm8150_vig_sblk_1,
|
|
.xin_id = 4,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG1,
|
|
@@ -92,7 +92,7 @@ static const struct dpu_sspp_cfg sc8180x_sspp[] = {
|
|
.name = "sspp_2", .id = SSPP_VIG2,
|
|
.base = 0x8000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_2,
|
|
+ .sblk = &sm8150_vig_sblk_2,
|
|
.xin_id = 8,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG2,
|
|
@@ -100,7 +100,7 @@ static const struct dpu_sspp_cfg sc8180x_sspp[] = {
|
|
.name = "sspp_3", .id = SSPP_VIG3,
|
|
.base = 0xa000, .len = 0x1f0,
|
|
.features = VIG_SDM845_MASK,
|
|
- .sblk = &sdm845_vig_sblk_3,
|
|
+ .sblk = &sm8150_vig_sblk_3,
|
|
.xin_id = 12,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG3,
|
|
diff --git a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_8_1_sm8450.h b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_8_1_sm8450.h
|
|
index 8a19cfa274dea..72a1726371cae 100644
|
|
--- a/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_8_1_sm8450.h
|
|
+++ b/drivers/gpu/drm/msm/disp/dpu1/catalog/dpu_8_1_sm8450.h
|
|
@@ -77,7 +77,7 @@ static const struct dpu_sspp_cfg sm8450_sspp[] = {
|
|
.name = "sspp_0", .id = SSPP_VIG0,
|
|
.base = 0x4000, .len = 0x32c,
|
|
.features = VIG_SC7180_MASK,
|
|
- .sblk = &sm8250_vig_sblk_0,
|
|
+ .sblk = &sm8450_vig_sblk_0,
|
|
.xin_id = 0,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG0,
|
|
@@ -85,7 +85,7 @@ static const struct dpu_sspp_cfg sm8450_sspp[] = {
|
|
.name = "sspp_1", .id = SSPP_VIG1,
|
|
.base = 0x6000, .len = 0x32c,
|
|
.features = VIG_SC7180_MASK,
|
|
- .sblk = &sm8250_vig_sblk_1,
|
|
+ .sblk = &sm8450_vig_sblk_1,
|
|
.xin_id = 4,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG1,
|
|
@@ -93,7 +93,7 @@ static const struct dpu_sspp_cfg sm8450_sspp[] = {
|
|
.name = "sspp_2", .id = SSPP_VIG2,
|
|
.base = 0x8000, .len = 0x32c,
|
|
.features = VIG_SC7180_MASK,
|
|
- .sblk = &sm8250_vig_sblk_2,
|
|
+ .sblk = &sm8450_vig_sblk_2,
|
|
.xin_id = 8,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG2,
|
|
@@ -101,7 +101,7 @@ static const struct dpu_sspp_cfg sm8450_sspp[] = {
|
|
.name = "sspp_3", .id = SSPP_VIG3,
|
|
.base = 0xa000, .len = 0x32c,
|
|
.features = VIG_SC7180_MASK,
|
|
- .sblk = &sm8250_vig_sblk_3,
|
|
+ .sblk = &sm8450_vig_sblk_3,
|
|
.xin_id = 12,
|
|
.type = SSPP_TYPE_VIG,
|
|
.clk_ctrl = DPU_CLK_CTRL_VIG3,
|
|
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c
|
|
index 713dfc0797181..77d09f961d866 100644
|
|
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c
|
|
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c
|
|
@@ -250,14 +250,17 @@ static const uint32_t wb2_formats[] = {
|
|
* SSPP sub blocks config
|
|
*************************************************************/
|
|
|
|
+#define SSPP_SCALER_VER(maj, min) (((maj) << 16) | (min))
|
|
+
|
|
/* SSPP common configuration */
|
|
-#define _VIG_SBLK(sdma_pri, qseed_ver) \
|
|
+#define _VIG_SBLK(sdma_pri, qseed_ver, scaler_ver) \
|
|
{ \
|
|
.maxdwnscale = MAX_DOWNSCALE_RATIO, \
|
|
.maxupscale = MAX_UPSCALE_RATIO, \
|
|
.smart_dma_priority = sdma_pri, \
|
|
.scaler_blk = {.name = "scaler", \
|
|
.id = qseed_ver, \
|
|
+ .version = scaler_ver, \
|
|
.base = 0xa00, .len = 0xa0,}, \
|
|
.csc_blk = {.name = "csc", \
|
|
.id = DPU_SSPP_CSC_10BIT, \
|
|
@@ -269,13 +272,14 @@ static const uint32_t wb2_formats[] = {
|
|
.rotation_cfg = NULL, \
|
|
}
|
|
|
|
-#define _VIG_SBLK_ROT(sdma_pri, qseed_ver, rot_cfg) \
|
|
+#define _VIG_SBLK_ROT(sdma_pri, qseed_ver, scaler_ver, rot_cfg) \
|
|
{ \
|
|
.maxdwnscale = MAX_DOWNSCALE_RATIO, \
|
|
.maxupscale = MAX_UPSCALE_RATIO, \
|
|
.smart_dma_priority = sdma_pri, \
|
|
.scaler_blk = {.name = "scaler", \
|
|
.id = qseed_ver, \
|
|
+ .version = scaler_ver, \
|
|
.base = 0xa00, .len = 0xa0,}, \
|
|
.csc_blk = {.name = "csc", \
|
|
.id = DPU_SSPP_CSC_10BIT, \
|
|
@@ -299,13 +303,17 @@ static const uint32_t wb2_formats[] = {
|
|
}
|
|
|
|
static const struct dpu_sspp_sub_blks msm8998_vig_sblk_0 =
|
|
- _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 2));
|
|
static const struct dpu_sspp_sub_blks msm8998_vig_sblk_1 =
|
|
- _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 2));
|
|
static const struct dpu_sspp_sub_blks msm8998_vig_sblk_2 =
|
|
- _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 2));
|
|
static const struct dpu_sspp_sub_blks msm8998_vig_sblk_3 =
|
|
- _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(0, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 2));
|
|
|
|
static const struct dpu_rotation_cfg dpu_rot_sc7280_cfg_v2 = {
|
|
.rot_maxheight = 1088,
|
|
@@ -314,13 +322,30 @@ static const struct dpu_rotation_cfg dpu_rot_sc7280_cfg_v2 = {
|
|
};
|
|
|
|
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_0 =
|
|
- _VIG_SBLK(5, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(5, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 3));
|
|
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_1 =
|
|
- _VIG_SBLK(6, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(6, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 3));
|
|
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_2 =
|
|
- _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 3));
|
|
static const struct dpu_sspp_sub_blks sdm845_vig_sblk_3 =
|
|
- _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED3);
|
|
+ _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 3));
|
|
+
|
|
+static const struct dpu_sspp_sub_blks sm8150_vig_sblk_0 =
|
|
+ _VIG_SBLK(5, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 4));
|
|
+static const struct dpu_sspp_sub_blks sm8150_vig_sblk_1 =
|
|
+ _VIG_SBLK(6, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 4));
|
|
+static const struct dpu_sspp_sub_blks sm8150_vig_sblk_2 =
|
|
+ _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 4));
|
|
+static const struct dpu_sspp_sub_blks sm8150_vig_sblk_3 =
|
|
+ _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED3,
|
|
+ SSPP_SCALER_VER(1, 4));
|
|
|
|
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_0 = _DMA_SBLK(1);
|
|
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_1 = _DMA_SBLK(2);
|
|
@@ -328,34 +353,60 @@ static const struct dpu_sspp_sub_blks sdm845_dma_sblk_2 = _DMA_SBLK(3);
|
|
static const struct dpu_sspp_sub_blks sdm845_dma_sblk_3 = _DMA_SBLK(4);
|
|
|
|
static const struct dpu_sspp_sub_blks sc7180_vig_sblk_0 =
|
|
- _VIG_SBLK(4, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(4, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0));
|
|
|
|
static const struct dpu_sspp_sub_blks sc7280_vig_sblk_0 =
|
|
- _VIG_SBLK_ROT(4, DPU_SSPP_SCALER_QSEED4, &dpu_rot_sc7280_cfg_v2);
|
|
+ _VIG_SBLK_ROT(4, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0),
|
|
+ &dpu_rot_sc7280_cfg_v2);
|
|
|
|
static const struct dpu_sspp_sub_blks sm6115_vig_sblk_0 =
|
|
- _VIG_SBLK(2, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(2, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0));
|
|
|
|
static const struct dpu_sspp_sub_blks sm6125_vig_sblk_0 =
|
|
- _VIG_SBLK(3, DPU_SSPP_SCALER_QSEED3LITE);
|
|
+ _VIG_SBLK(3, DPU_SSPP_SCALER_QSEED3LITE,
|
|
+ SSPP_SCALER_VER(2, 4));
|
|
|
|
static const struct dpu_sspp_sub_blks sm8250_vig_sblk_0 =
|
|
- _VIG_SBLK(5, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(5, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0));
|
|
static const struct dpu_sspp_sub_blks sm8250_vig_sblk_1 =
|
|
- _VIG_SBLK(6, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(6, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0));
|
|
static const struct dpu_sspp_sub_blks sm8250_vig_sblk_2 =
|
|
- _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0));
|
|
static const struct dpu_sspp_sub_blks sm8250_vig_sblk_3 =
|
|
- _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 0));
|
|
+
|
|
+static const struct dpu_sspp_sub_blks sm8450_vig_sblk_0 =
|
|
+ _VIG_SBLK(5, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 1));
|
|
+static const struct dpu_sspp_sub_blks sm8450_vig_sblk_1 =
|
|
+ _VIG_SBLK(6, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 1));
|
|
+static const struct dpu_sspp_sub_blks sm8450_vig_sblk_2 =
|
|
+ _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 1));
|
|
+static const struct dpu_sspp_sub_blks sm8450_vig_sblk_3 =
|
|
+ _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 1));
|
|
|
|
static const struct dpu_sspp_sub_blks sm8550_vig_sblk_0 =
|
|
- _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(7, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 2));
|
|
static const struct dpu_sspp_sub_blks sm8550_vig_sblk_1 =
|
|
- _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(8, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 2));
|
|
static const struct dpu_sspp_sub_blks sm8550_vig_sblk_2 =
|
|
- _VIG_SBLK(9, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(9, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 2));
|
|
static const struct dpu_sspp_sub_blks sm8550_vig_sblk_3 =
|
|
- _VIG_SBLK(10, DPU_SSPP_SCALER_QSEED4);
|
|
+ _VIG_SBLK(10, DPU_SSPP_SCALER_QSEED4,
|
|
+ SSPP_SCALER_VER(3, 2));
|
|
static const struct dpu_sspp_sub_blks sm8550_dma_sblk_4 = _DMA_SBLK(5);
|
|
static const struct dpu_sspp_sub_blks sm8550_dma_sblk_5 = _DMA_SBLK(6);
|
|
|
|
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
|
|
index 6c9634209e9fc..3f82d84bd1c90 100644
|
|
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
|
|
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h
|
|
@@ -269,7 +269,8 @@ enum {
|
|
/**
|
|
* struct dpu_scaler_blk: Scaler information
|
|
* @info: HW register and features supported by this sub-blk
|
|
- * @version: qseed block revision
|
|
+ * @version: qseed block revision, on QSEED3+ platforms this is the value of
|
|
+ * scaler_blk.base + QSEED3_HW_VERSION registers.
|
|
*/
|
|
struct dpu_scaler_blk {
|
|
DPU_HW_SUBBLK_INFO;
|
|
diff --git a/drivers/gpu/drm/nouveau/nouveau_bios.c b/drivers/gpu/drm/nouveau/nouveau_bios.c
|
|
index 189903b65edc9..48cf593383b34 100644
|
|
--- a/drivers/gpu/drm/nouveau/nouveau_bios.c
|
|
+++ b/drivers/gpu/drm/nouveau/nouveau_bios.c
|
|
@@ -23,6 +23,7 @@
|
|
*/
|
|
|
|
#include "nouveau_drv.h"
|
|
+#include "nouveau_bios.h"
|
|
#include "nouveau_reg.h"
|
|
#include "dispnv04/hw.h"
|
|
#include "nouveau_encoder.h"
|
|
@@ -1675,7 +1676,7 @@ apply_dcb_encoder_quirks(struct drm_device *dev, int idx, u32 *conn, u32 *conf)
|
|
*/
|
|
if (nv_match_device(dev, 0x0201, 0x1462, 0x8851)) {
|
|
if (*conn == 0xf2005014 && *conf == 0xffffffff) {
|
|
- fabricate_dcb_output(dcb, DCB_OUTPUT_TMDS, 1, 1, 1);
|
|
+ fabricate_dcb_output(dcb, DCB_OUTPUT_TMDS, 1, 1, DCB_OUTPUT_B);
|
|
return false;
|
|
}
|
|
}
|
|
@@ -1761,26 +1762,26 @@ fabricate_dcb_encoder_table(struct drm_device *dev, struct nvbios *bios)
|
|
#ifdef __powerpc__
|
|
/* Apple iMac G4 NV17 */
|
|
if (of_machine_is_compatible("PowerMac4,5")) {
|
|
- fabricate_dcb_output(dcb, DCB_OUTPUT_TMDS, 0, all_heads, 1);
|
|
- fabricate_dcb_output(dcb, DCB_OUTPUT_ANALOG, 1, all_heads, 2);
|
|
+ fabricate_dcb_output(dcb, DCB_OUTPUT_TMDS, 0, all_heads, DCB_OUTPUT_B);
|
|
+ fabricate_dcb_output(dcb, DCB_OUTPUT_ANALOG, 1, all_heads, DCB_OUTPUT_C);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Make up some sane defaults */
|
|
fabricate_dcb_output(dcb, DCB_OUTPUT_ANALOG,
|
|
- bios->legacy.i2c_indices.crt, 1, 1);
|
|
+ bios->legacy.i2c_indices.crt, 1, DCB_OUTPUT_B);
|
|
|
|
if (nv04_tv_identify(dev, bios->legacy.i2c_indices.tv) >= 0)
|
|
fabricate_dcb_output(dcb, DCB_OUTPUT_TV,
|
|
bios->legacy.i2c_indices.tv,
|
|
- all_heads, 0);
|
|
+ all_heads, DCB_OUTPUT_A);
|
|
|
|
else if (bios->tmds.output0_script_ptr ||
|
|
bios->tmds.output1_script_ptr)
|
|
fabricate_dcb_output(dcb, DCB_OUTPUT_TMDS,
|
|
bios->legacy.i2c_indices.panel,
|
|
- all_heads, 1);
|
|
+ all_heads, DCB_OUTPUT_B);
|
|
}
|
|
|
|
static int
|
|
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
|
|
index 4b2d7465d22f7..f4989f0526ecb 100644
|
|
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
|
|
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/instmem/nv50.c
|
|
@@ -221,8 +221,11 @@ nv50_instobj_acquire(struct nvkm_memory *memory)
|
|
void __iomem *map = NULL;
|
|
|
|
/* Already mapped? */
|
|
- if (refcount_inc_not_zero(&iobj->maps))
|
|
+ if (refcount_inc_not_zero(&iobj->maps)) {
|
|
+ /* read barrier match the wmb on refcount set */
|
|
+ smp_rmb();
|
|
return iobj->map;
|
|
+ }
|
|
|
|
/* Take the lock, and re-check that another thread hasn't
|
|
* already mapped the object in the meantime.
|
|
@@ -249,6 +252,8 @@ nv50_instobj_acquire(struct nvkm_memory *memory)
|
|
iobj->base.memory.ptrs = &nv50_instobj_fast;
|
|
else
|
|
iobj->base.memory.ptrs = &nv50_instobj_slow;
|
|
+ /* barrier to ensure the ptrs are written before refcount is set */
|
|
+ smp_wmb();
|
|
refcount_set(&iobj->maps, 1);
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/panel/panel-visionox-rm69299.c b/drivers/gpu/drm/panel/panel-visionox-rm69299.c
|
|
index c2806e4fd553b..6e946e5a036ee 100644
|
|
--- a/drivers/gpu/drm/panel/panel-visionox-rm69299.c
|
|
+++ b/drivers/gpu/drm/panel/panel-visionox-rm69299.c
|
|
@@ -261,8 +261,6 @@ static void visionox_rm69299_remove(struct mipi_dsi_device *dsi)
|
|
struct visionox_rm69299 *ctx = mipi_dsi_get_drvdata(dsi);
|
|
|
|
mipi_dsi_detach(ctx->dsi);
|
|
- mipi_dsi_device_unregister(ctx->dsi);
|
|
-
|
|
drm_panel_remove(&ctx->panel);
|
|
}
|
|
|
|
diff --git a/drivers/gpu/drm/radeon/radeon_atombios.c b/drivers/gpu/drm/radeon/radeon_atombios.c
|
|
index 85c4bb186203c..061396e7fa0f9 100644
|
|
--- a/drivers/gpu/drm/radeon/radeon_atombios.c
|
|
+++ b/drivers/gpu/drm/radeon/radeon_atombios.c
|
|
@@ -922,8 +922,12 @@ bool radeon_get_atom_connector_info_from_supported_devices_table(struct
|
|
max_device = ATOM_MAX_SUPPORTED_DEVICE_INFO;
|
|
|
|
for (i = 0; i < max_device; i++) {
|
|
- ATOM_CONNECTOR_INFO_I2C ci =
|
|
- supported_devices->info.asConnInfo[i];
|
|
+ ATOM_CONNECTOR_INFO_I2C ci;
|
|
+
|
|
+ if (frev > 1)
|
|
+ ci = supported_devices->info_2d1.asConnInfo[i];
|
|
+ else
|
|
+ ci = supported_devices->info.asConnInfo[i];
|
|
|
|
bios_connectors[i].valid = false;
|
|
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c b/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c
|
|
index c52c7bf1485b1..717d624e9a052 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_blit.c
|
|
@@ -456,8 +456,10 @@ int vmw_bo_cpu_blit(struct ttm_buffer_object *dst,
|
|
.no_wait_gpu = false
|
|
};
|
|
u32 j, initial_line = dst_offset / dst_stride;
|
|
- struct vmw_bo_blit_line_data d;
|
|
+ struct vmw_bo_blit_line_data d = {0};
|
|
int ret = 0;
|
|
+ struct page **dst_pages = NULL;
|
|
+ struct page **src_pages = NULL;
|
|
|
|
/* Buffer objects need to be either pinned or reserved: */
|
|
if (!(dst->pin_count))
|
|
@@ -477,12 +479,35 @@ int vmw_bo_cpu_blit(struct ttm_buffer_object *dst,
|
|
return ret;
|
|
}
|
|
|
|
+ if (!src->ttm->pages && src->ttm->sg) {
|
|
+ src_pages = kvmalloc_array(src->ttm->num_pages,
|
|
+ sizeof(struct page *), GFP_KERNEL);
|
|
+ if (!src_pages)
|
|
+ return -ENOMEM;
|
|
+ ret = drm_prime_sg_to_page_array(src->ttm->sg, src_pages,
|
|
+ src->ttm->num_pages);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ }
|
|
+ if (!dst->ttm->pages && dst->ttm->sg) {
|
|
+ dst_pages = kvmalloc_array(dst->ttm->num_pages,
|
|
+ sizeof(struct page *), GFP_KERNEL);
|
|
+ if (!dst_pages) {
|
|
+ ret = -ENOMEM;
|
|
+ goto out;
|
|
+ }
|
|
+ ret = drm_prime_sg_to_page_array(dst->ttm->sg, dst_pages,
|
|
+ dst->ttm->num_pages);
|
|
+ if (ret)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
d.mapped_dst = 0;
|
|
d.mapped_src = 0;
|
|
d.dst_addr = NULL;
|
|
d.src_addr = NULL;
|
|
- d.dst_pages = dst->ttm->pages;
|
|
- d.src_pages = src->ttm->pages;
|
|
+ d.dst_pages = dst->ttm->pages ? dst->ttm->pages : dst_pages;
|
|
+ d.src_pages = src->ttm->pages ? src->ttm->pages : src_pages;
|
|
d.dst_num_pages = PFN_UP(dst->resource->size);
|
|
d.src_num_pages = PFN_UP(src->resource->size);
|
|
d.dst_prot = ttm_io_prot(dst, dst->resource, PAGE_KERNEL);
|
|
@@ -504,6 +529,10 @@ int vmw_bo_cpu_blit(struct ttm_buffer_object *dst,
|
|
kunmap_atomic(d.src_addr);
|
|
if (d.dst_addr)
|
|
kunmap_atomic(d.dst_addr);
|
|
+ if (src_pages)
|
|
+ kvfree(src_pages);
|
|
+ if (dst_pages)
|
|
+ kvfree(dst_pages);
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c
|
|
index 2bfac3aad7b7d..4aac88cc5f913 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c
|
|
@@ -377,7 +377,8 @@ static int vmw_bo_init(struct vmw_private *dev_priv,
|
|
{
|
|
struct ttm_operation_ctx ctx = {
|
|
.interruptible = params->bo_type != ttm_bo_type_kernel,
|
|
- .no_wait_gpu = false
|
|
+ .no_wait_gpu = false,
|
|
+ .resv = params->resv,
|
|
};
|
|
struct ttm_device *bdev = &dev_priv->bdev;
|
|
struct drm_device *vdev = &dev_priv->drm;
|
|
@@ -394,8 +395,8 @@ static int vmw_bo_init(struct vmw_private *dev_priv,
|
|
|
|
vmw_bo_placement_set(vmw_bo, params->domain, params->busy_domain);
|
|
ret = ttm_bo_init_reserved(bdev, &vmw_bo->tbo, params->bo_type,
|
|
- &vmw_bo->placement, 0, &ctx, NULL,
|
|
- NULL, destroy);
|
|
+ &vmw_bo->placement, 0, &ctx,
|
|
+ params->sg, params->resv, destroy);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.h b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.h
|
|
index 0d496dc9c6af7..f349642e6190d 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.h
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.h
|
|
@@ -55,6 +55,8 @@ struct vmw_bo_params {
|
|
enum ttm_bo_type bo_type;
|
|
size_t size;
|
|
bool pin;
|
|
+ struct dma_resv *resv;
|
|
+ struct sg_table *sg;
|
|
};
|
|
|
|
/**
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
|
|
index 0a304706e0132..58fb40c93100a 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c
|
|
@@ -1628,6 +1628,7 @@ static const struct drm_driver driver = {
|
|
|
|
.prime_fd_to_handle = vmw_prime_fd_to_handle,
|
|
.prime_handle_to_fd = vmw_prime_handle_to_fd,
|
|
+ .gem_prime_import_sg_table = vmw_prime_import_sg_table,
|
|
|
|
.fops = &vmwgfx_driver_fops,
|
|
.name = VMWGFX_DRIVER_NAME,
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
|
|
index 3cd5090dedfc5..6acc7ad0e9eb8 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.h
|
|
@@ -1131,6 +1131,9 @@ extern int vmw_prime_handle_to_fd(struct drm_device *dev,
|
|
struct drm_file *file_priv,
|
|
uint32_t handle, uint32_t flags,
|
|
int *prime_fd);
|
|
+struct drm_gem_object *vmw_prime_import_sg_table(struct drm_device *dev,
|
|
+ struct dma_buf_attachment *attach,
|
|
+ struct sg_table *table);
|
|
|
|
/*
|
|
* MemoryOBject management - vmwgfx_mob.c
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c b/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c
|
|
index 12787bb9c111d..d6bcaf078b1f4 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c
|
|
@@ -149,6 +149,38 @@ int vmw_gem_object_create_with_handle(struct vmw_private *dev_priv,
|
|
return ret;
|
|
}
|
|
|
|
+struct drm_gem_object *vmw_prime_import_sg_table(struct drm_device *dev,
|
|
+ struct dma_buf_attachment *attach,
|
|
+ struct sg_table *table)
|
|
+{
|
|
+ int ret;
|
|
+ struct vmw_private *dev_priv = vmw_priv(dev);
|
|
+ struct drm_gem_object *gem = NULL;
|
|
+ struct vmw_bo *vbo;
|
|
+ struct vmw_bo_params params = {
|
|
+ .domain = (dev_priv->has_mob) ? VMW_BO_DOMAIN_SYS : VMW_BO_DOMAIN_VRAM,
|
|
+ .busy_domain = VMW_BO_DOMAIN_SYS,
|
|
+ .bo_type = ttm_bo_type_sg,
|
|
+ .size = attach->dmabuf->size,
|
|
+ .pin = false,
|
|
+ .resv = attach->dmabuf->resv,
|
|
+ .sg = table,
|
|
+
|
|
+ };
|
|
+
|
|
+ dma_resv_lock(params.resv, NULL);
|
|
+
|
|
+ ret = vmw_bo_create(dev_priv, ¶ms, &vbo);
|
|
+ if (ret != 0)
|
|
+ goto out_no_bo;
|
|
+
|
|
+ vbo->tbo.base.funcs = &vmw_gem_object_funcs;
|
|
+
|
|
+ gem = &vbo->tbo.base;
|
|
+out_no_bo:
|
|
+ dma_resv_unlock(params.resv);
|
|
+ return gem;
|
|
+}
|
|
|
|
int vmw_gem_object_create_ioctl(struct drm_device *dev, void *data,
|
|
struct drm_file *filp)
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c
|
|
index 5681a1b42aa24..a884072851322 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c
|
|
@@ -926,6 +926,7 @@ int vmw_du_cursor_plane_atomic_check(struct drm_plane *plane,
|
|
int vmw_du_crtc_atomic_check(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
+ struct vmw_private *vmw = vmw_priv(crtc->dev);
|
|
struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state,
|
|
crtc);
|
|
struct vmw_display_unit *du = vmw_crtc_to_du(new_state->crtc);
|
|
@@ -933,9 +934,13 @@ int vmw_du_crtc_atomic_check(struct drm_crtc *crtc,
|
|
bool has_primary = new_state->plane_mask &
|
|
drm_plane_mask(crtc->primary);
|
|
|
|
- /* We always want to have an active plane with an active CRTC */
|
|
- if (has_primary != new_state->enable)
|
|
- return -EINVAL;
|
|
+ /*
|
|
+ * This is fine in general, but broken userspace might expect
|
|
+ * some actual rendering so give a clue as why it's blank.
|
|
+ */
|
|
+ if (new_state->enable && !has_primary)
|
|
+ drm_dbg_driver(&vmw->drm,
|
|
+ "CRTC without a primary plane will be blank.\n");
|
|
|
|
|
|
if (new_state->connector_mask != connector_mask &&
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h
|
|
index db81e635dc061..9fda4f4ec7a97 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.h
|
|
@@ -243,10 +243,10 @@ struct vmw_framebuffer_bo {
|
|
|
|
|
|
static const uint32_t __maybe_unused vmw_primary_plane_formats[] = {
|
|
- DRM_FORMAT_XRGB1555,
|
|
- DRM_FORMAT_RGB565,
|
|
DRM_FORMAT_XRGB8888,
|
|
DRM_FORMAT_ARGB8888,
|
|
+ DRM_FORMAT_RGB565,
|
|
+ DRM_FORMAT_XRGB1555,
|
|
};
|
|
|
|
static const uint32_t __maybe_unused vmw_cursor_plane_formats[] = {
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c b/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c
|
|
index 2d72a5ee7c0c7..c99cad4449915 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_prime.c
|
|
@@ -75,8 +75,12 @@ int vmw_prime_fd_to_handle(struct drm_device *dev,
|
|
int fd, u32 *handle)
|
|
{
|
|
struct ttm_object_file *tfile = vmw_fpriv(file_priv)->tfile;
|
|
+ int ret = ttm_prime_fd_to_handle(tfile, fd, handle);
|
|
|
|
- return ttm_prime_fd_to_handle(tfile, fd, handle);
|
|
+ if (ret)
|
|
+ ret = drm_gem_prime_fd_to_handle(dev, file_priv, fd, handle);
|
|
+
|
|
+ return ret;
|
|
}
|
|
|
|
int vmw_prime_handle_to_fd(struct drm_device *dev,
|
|
@@ -85,5 +89,12 @@ int vmw_prime_handle_to_fd(struct drm_device *dev,
|
|
int *prime_fd)
|
|
{
|
|
struct ttm_object_file *tfile = vmw_fpriv(file_priv)->tfile;
|
|
- return ttm_prime_handle_to_fd(tfile, handle, flags, prime_fd);
|
|
+ int ret;
|
|
+
|
|
+ if (handle > VMWGFX_NUM_MOB)
|
|
+ ret = ttm_prime_handle_to_fd(tfile, handle, flags, prime_fd);
|
|
+ else
|
|
+ ret = drm_gem_prime_handle_to_fd(dev, file_priv, handle, flags, prime_fd);
|
|
+
|
|
+ return ret;
|
|
}
|
|
diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c b/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c
|
|
index af8562c95cc35..fcb87d83760ef 100644
|
|
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c
|
|
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_ttm_buffer.c
|
|
@@ -220,13 +220,18 @@ static int vmw_ttm_map_dma(struct vmw_ttm_tt *vmw_tt)
|
|
switch (dev_priv->map_mode) {
|
|
case vmw_dma_map_bind:
|
|
case vmw_dma_map_populate:
|
|
- vsgt->sgt = &vmw_tt->sgt;
|
|
- ret = sg_alloc_table_from_pages_segment(
|
|
- &vmw_tt->sgt, vsgt->pages, vsgt->num_pages, 0,
|
|
- (unsigned long)vsgt->num_pages << PAGE_SHIFT,
|
|
- dma_get_max_seg_size(dev_priv->drm.dev), GFP_KERNEL);
|
|
- if (ret)
|
|
- goto out_sg_alloc_fail;
|
|
+ if (vmw_tt->dma_ttm.page_flags & TTM_TT_FLAG_EXTERNAL) {
|
|
+ vsgt->sgt = vmw_tt->dma_ttm.sg;
|
|
+ } else {
|
|
+ vsgt->sgt = &vmw_tt->sgt;
|
|
+ ret = sg_alloc_table_from_pages_segment(&vmw_tt->sgt,
|
|
+ vsgt->pages, vsgt->num_pages, 0,
|
|
+ (unsigned long)vsgt->num_pages << PAGE_SHIFT,
|
|
+ dma_get_max_seg_size(dev_priv->drm.dev),
|
|
+ GFP_KERNEL);
|
|
+ if (ret)
|
|
+ goto out_sg_alloc_fail;
|
|
+ }
|
|
|
|
ret = vmw_ttm_map_for_dma(vmw_tt);
|
|
if (unlikely(ret != 0))
|
|
@@ -241,8 +246,9 @@ static int vmw_ttm_map_dma(struct vmw_ttm_tt *vmw_tt)
|
|
return 0;
|
|
|
|
out_map_fail:
|
|
- sg_free_table(vmw_tt->vsgt.sgt);
|
|
- vmw_tt->vsgt.sgt = NULL;
|
|
+ drm_warn(&dev_priv->drm, "VSG table map failed!");
|
|
+ sg_free_table(vsgt->sgt);
|
|
+ vsgt->sgt = NULL;
|
|
out_sg_alloc_fail:
|
|
return ret;
|
|
}
|
|
@@ -388,15 +394,17 @@ static void vmw_ttm_destroy(struct ttm_device *bdev, struct ttm_tt *ttm)
|
|
static int vmw_ttm_populate(struct ttm_device *bdev,
|
|
struct ttm_tt *ttm, struct ttm_operation_ctx *ctx)
|
|
{
|
|
- int ret;
|
|
+ bool external = (ttm->page_flags & TTM_TT_FLAG_EXTERNAL) != 0;
|
|
|
|
- /* TODO: maybe completely drop this ? */
|
|
if (ttm_tt_is_populated(ttm))
|
|
return 0;
|
|
|
|
- ret = ttm_pool_alloc(&bdev->pool, ttm, ctx);
|
|
+ if (external && ttm->sg)
|
|
+ return drm_prime_sg_to_dma_addr_array(ttm->sg,
|
|
+ ttm->dma_address,
|
|
+ ttm->num_pages);
|
|
|
|
- return ret;
|
|
+ return ttm_pool_alloc(&bdev->pool, ttm, ctx);
|
|
}
|
|
|
|
static void vmw_ttm_unpopulate(struct ttm_device *bdev,
|
|
@@ -404,6 +412,10 @@ static void vmw_ttm_unpopulate(struct ttm_device *bdev,
|
|
{
|
|
struct vmw_ttm_tt *vmw_tt = container_of(ttm, struct vmw_ttm_tt,
|
|
dma_ttm);
|
|
+ bool external = (ttm->page_flags & TTM_TT_FLAG_EXTERNAL) != 0;
|
|
+
|
|
+ if (external)
|
|
+ return;
|
|
|
|
vmw_ttm_unbind(bdev, ttm);
|
|
|
|
@@ -422,6 +434,7 @@ static struct ttm_tt *vmw_ttm_tt_create(struct ttm_buffer_object *bo,
|
|
{
|
|
struct vmw_ttm_tt *vmw_be;
|
|
int ret;
|
|
+ bool external = bo->type == ttm_bo_type_sg;
|
|
|
|
vmw_be = kzalloc(sizeof(*vmw_be), GFP_KERNEL);
|
|
if (!vmw_be)
|
|
@@ -430,7 +443,10 @@ static struct ttm_tt *vmw_ttm_tt_create(struct ttm_buffer_object *bo,
|
|
vmw_be->dev_priv = vmw_priv_from_ttm(bo->bdev);
|
|
vmw_be->mob = NULL;
|
|
|
|
- if (vmw_be->dev_priv->map_mode == vmw_dma_alloc_coherent)
|
|
+ if (external)
|
|
+ page_flags |= TTM_TT_FLAG_EXTERNAL | TTM_TT_FLAG_EXTERNAL_MAPPABLE;
|
|
+
|
|
+ if (vmw_be->dev_priv->map_mode == vmw_dma_alloc_coherent || external)
|
|
ret = ttm_sg_tt_init(&vmw_be->dma_ttm, bo, page_flags,
|
|
ttm_cached);
|
|
else
|
|
diff --git a/drivers/infiniband/core/cm.c b/drivers/infiniband/core/cm.c
|
|
index bf0df6ee4f785..07fb8d3c037f0 100644
|
|
--- a/drivers/infiniband/core/cm.c
|
|
+++ b/drivers/infiniband/core/cm.c
|
|
@@ -1026,23 +1026,26 @@ static void cm_reset_to_idle(struct cm_id_private *cm_id_priv)
|
|
}
|
|
}
|
|
|
|
-static noinline void cm_destroy_id_wait_timeout(struct ib_cm_id *cm_id)
|
|
+static noinline void cm_destroy_id_wait_timeout(struct ib_cm_id *cm_id,
|
|
+ enum ib_cm_state old_state)
|
|
{
|
|
struct cm_id_private *cm_id_priv;
|
|
|
|
cm_id_priv = container_of(cm_id, struct cm_id_private, id);
|
|
- pr_err("%s: cm_id=%p timed out. state=%d refcnt=%d\n", __func__,
|
|
- cm_id, cm_id->state, refcount_read(&cm_id_priv->refcount));
|
|
+ pr_err("%s: cm_id=%p timed out. state %d -> %d, refcnt=%d\n", __func__,
|
|
+ cm_id, old_state, cm_id->state, refcount_read(&cm_id_priv->refcount));
|
|
}
|
|
|
|
static void cm_destroy_id(struct ib_cm_id *cm_id, int err)
|
|
{
|
|
struct cm_id_private *cm_id_priv;
|
|
+ enum ib_cm_state old_state;
|
|
struct cm_work *work;
|
|
int ret;
|
|
|
|
cm_id_priv = container_of(cm_id, struct cm_id_private, id);
|
|
spin_lock_irq(&cm_id_priv->lock);
|
|
+ old_state = cm_id->state;
|
|
retest:
|
|
switch (cm_id->state) {
|
|
case IB_CM_LISTEN:
|
|
@@ -1151,7 +1154,7 @@ static void cm_destroy_id(struct ib_cm_id *cm_id, int err)
|
|
msecs_to_jiffies(
|
|
CM_DESTROY_ID_WAIT_TIMEOUT));
|
|
if (!ret) /* timeout happened */
|
|
- cm_destroy_id_wait_timeout(cm_id);
|
|
+ cm_destroy_id_wait_timeout(cm_id, old_state);
|
|
} while (!ret);
|
|
|
|
while ((work = cm_dequeue_work(cm_id_priv)) != NULL)
|
|
diff --git a/drivers/infiniband/hw/mlx5/mad.c b/drivers/infiniband/hw/mlx5/mad.c
|
|
index 8102ef113b7e0..64dae68c43e62 100644
|
|
--- a/drivers/infiniband/hw/mlx5/mad.c
|
|
+++ b/drivers/infiniband/hw/mlx5/mad.c
|
|
@@ -188,7 +188,8 @@ static int process_pma_cmd(struct mlx5_ib_dev *dev, u32 port_num,
|
|
mdev = dev->mdev;
|
|
mdev_port_num = 1;
|
|
}
|
|
- if (MLX5_CAP_GEN(dev->mdev, num_ports) == 1) {
|
|
+ if (MLX5_CAP_GEN(dev->mdev, num_ports) == 1 &&
|
|
+ !mlx5_core_mp_enabled(mdev)) {
|
|
/* set local port to one for Function-Per-Port HCA. */
|
|
mdev = dev->mdev;
|
|
mdev_port_num = 1;
|
|
diff --git a/drivers/infiniband/sw/rxe/rxe.c b/drivers/infiniband/sw/rxe/rxe.c
|
|
index 54c723a6eddac..6f9ec8db014c7 100644
|
|
--- a/drivers/infiniband/sw/rxe/rxe.c
|
|
+++ b/drivers/infiniband/sw/rxe/rxe.c
|
|
@@ -33,6 +33,8 @@ void rxe_dealloc(struct ib_device *ib_dev)
|
|
|
|
if (rxe->tfm)
|
|
crypto_free_shash(rxe->tfm);
|
|
+
|
|
+ mutex_destroy(&rxe->usdev_lock);
|
|
}
|
|
|
|
/* initialize rxe device parameters */
|
|
diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c
|
|
index 50bac2d79d9b5..68edb07d4443e 100644
|
|
--- a/drivers/interconnect/core.c
|
|
+++ b/drivers/interconnect/core.c
|
|
@@ -176,6 +176,8 @@ static struct icc_path *path_init(struct device *dev, struct icc_node *dst,
|
|
|
|
path->num_nodes = num_nodes;
|
|
|
|
+ mutex_lock(&icc_bw_lock);
|
|
+
|
|
for (i = num_nodes - 1; i >= 0; i--) {
|
|
node->provider->users++;
|
|
hlist_add_head(&path->reqs[i].req_node, &node->req_list);
|
|
@@ -186,6 +188,8 @@ static struct icc_path *path_init(struct device *dev, struct icc_node *dst,
|
|
node = node->reverse;
|
|
}
|
|
|
|
+ mutex_unlock(&icc_bw_lock);
|
|
+
|
|
return path;
|
|
}
|
|
|
|
@@ -792,12 +796,16 @@ void icc_put(struct icc_path *path)
|
|
pr_err("%s: error (%d)\n", __func__, ret);
|
|
|
|
mutex_lock(&icc_lock);
|
|
+ mutex_lock(&icc_bw_lock);
|
|
+
|
|
for (i = 0; i < path->num_nodes; i++) {
|
|
node = path->reqs[i].node;
|
|
hlist_del(&path->reqs[i].req_node);
|
|
if (!WARN_ON(!node->provider->users))
|
|
node->provider->users--;
|
|
}
|
|
+
|
|
+ mutex_unlock(&icc_bw_lock);
|
|
mutex_unlock(&icc_lock);
|
|
|
|
kfree_const(path->name);
|
|
diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c
|
|
index cf6727d9c81f3..468191438849e 100644
|
|
--- a/drivers/media/common/videobuf2/videobuf2-core.c
|
|
+++ b/drivers/media/common/videobuf2/videobuf2-core.c
|
|
@@ -2648,9 +2648,14 @@ static int __vb2_init_fileio(struct vb2_queue *q, int read)
|
|
return -EBUSY;
|
|
|
|
/*
|
|
- * Start with count 1, driver can increase it in queue_setup()
|
|
+ * Start with q->min_buffers_needed + 1, driver can increase it in
|
|
+ * queue_setup()
|
|
+ *
|
|
+ * 'min_buffers_needed' buffers need to be queued up before you
|
|
+ * can start streaming, plus 1 for userspace (or in this case,
|
|
+ * kernelspace) processing.
|
|
*/
|
|
- count = 1;
|
|
+ count = max(2, q->min_buffers_needed + 1);
|
|
|
|
dprintk(q, 3, "setting up file io: mode %s, count %d, read_once %d, write_immediately %d\n",
|
|
(read) ? "read" : "write", count, q->fileio_read_once,
|
|
diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
|
|
index 8cf636c540322..bd4e3df44865e 100644
|
|
--- a/drivers/misc/mei/pci-me.c
|
|
+++ b/drivers/misc/mei/pci-me.c
|
|
@@ -116,7 +116,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
|
|
{MEI_PCI_DEVICE(MEI_DEV_ID_ADP_P, MEI_ME_PCH15_CFG)},
|
|
{MEI_PCI_DEVICE(MEI_DEV_ID_ADP_N, MEI_ME_PCH15_CFG)},
|
|
|
|
- {MEI_PCI_DEVICE(MEI_DEV_ID_RPL_S, MEI_ME_PCH15_CFG)},
|
|
+ {MEI_PCI_DEVICE(MEI_DEV_ID_RPL_S, MEI_ME_PCH15_SPS_CFG)},
|
|
|
|
{MEI_PCI_DEVICE(MEI_DEV_ID_MTL_M, MEI_ME_PCH15_CFG)},
|
|
{MEI_PCI_DEVICE(MEI_DEV_ID_ARL_S, MEI_ME_PCH15_CFG)},
|
|
diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
|
|
index 88f081672f6fb..53ead0989777f 100644
|
|
--- a/drivers/net/dsa/mt7530.c
|
|
+++ b/drivers/net/dsa/mt7530.c
|
|
@@ -1948,14 +1948,16 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port,
|
|
|
|
static int mt753x_mirror_port_get(unsigned int id, u32 val)
|
|
{
|
|
- return (id == ID_MT7531) ? MT7531_MIRROR_PORT_GET(val) :
|
|
- MIRROR_PORT(val);
|
|
+ return (id == ID_MT7531 || id == ID_MT7988) ?
|
|
+ MT7531_MIRROR_PORT_GET(val) :
|
|
+ MIRROR_PORT(val);
|
|
}
|
|
|
|
static int mt753x_mirror_port_set(unsigned int id, u32 val)
|
|
{
|
|
- return (id == ID_MT7531) ? MT7531_MIRROR_PORT_SET(val) :
|
|
- MIRROR_PORT(val);
|
|
+ return (id == ID_MT7531 || id == ID_MT7988) ?
|
|
+ MT7531_MIRROR_PORT_SET(val) :
|
|
+ MIRROR_PORT(val);
|
|
}
|
|
|
|
static int mt753x_port_mirror_add(struct dsa_switch *ds, int port,
|
|
@@ -2470,8 +2472,6 @@ mt7530_setup(struct dsa_switch *ds)
|
|
SYS_CTRL_PHY_RST | SYS_CTRL_SW_RST |
|
|
SYS_CTRL_REG_RST);
|
|
|
|
- mt7530_pll_setup(priv);
|
|
-
|
|
/* Lower Tx driving for TRGMII path */
|
|
for (i = 0; i < NUM_TRGMII_CTRL; i++)
|
|
mt7530_write(priv, MT7530_TRGMII_TD_ODT(i),
|
|
@@ -2489,6 +2489,9 @@ mt7530_setup(struct dsa_switch *ds)
|
|
|
|
priv->p6_interface = PHY_INTERFACE_MODE_NA;
|
|
|
|
+ if ((val & HWTRAP_XTAL_MASK) == HWTRAP_XTAL_40MHZ)
|
|
+ mt7530_pll_setup(priv);
|
|
+
|
|
mt753x_trap_frames(priv);
|
|
|
|
/* Enable and reset MIB counters */
|
|
@@ -2518,6 +2521,9 @@ mt7530_setup(struct dsa_switch *ds)
|
|
PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
|
|
}
|
|
|
|
+ /* Allow mirroring frames received on the local port (monitor port). */
|
|
+ mt7530_set(priv, MT753X_AGC, LOCAL_EN);
|
|
+
|
|
/* Setup VLAN ID 0 for VLAN-unaware bridges */
|
|
ret = mt7530_setup_vlan0(priv);
|
|
if (ret)
|
|
@@ -2626,6 +2632,9 @@ mt7531_setup_common(struct dsa_switch *ds)
|
|
PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
|
|
}
|
|
|
|
+ /* Allow mirroring frames received on the local port (monitor port). */
|
|
+ mt7530_set(priv, MT753X_AGC, LOCAL_EN);
|
|
+
|
|
/* Flush the FDB table */
|
|
ret = mt7530_fdb_cmd(priv, MT7530_FDB_FLUSH, NULL);
|
|
if (ret < 0)
|
|
@@ -2704,18 +2713,25 @@ mt7531_setup(struct dsa_switch *ds)
|
|
priv->p5_interface = PHY_INTERFACE_MODE_NA;
|
|
priv->p6_interface = PHY_INTERFACE_MODE_NA;
|
|
|
|
- /* Enable PHY core PLL, since phy_device has not yet been created
|
|
- * provided for phy_[read,write]_mmd_indirect is called, we provide
|
|
- * our own mt7531_ind_mmd_phy_[read,write] to complete this
|
|
- * function.
|
|
+ /* Enable Energy-Efficient Ethernet (EEE) and PHY core PLL, since
|
|
+ * phy_device has not yet been created provided for
|
|
+ * phy_[read,write]_mmd_indirect is called, we provide our own
|
|
+ * mt7531_ind_mmd_phy_[read,write] to complete this function.
|
|
*/
|
|
val = mt7531_ind_c45_phy_read(priv, MT753X_CTRL_PHY_ADDR,
|
|
MDIO_MMD_VEND2, CORE_PLL_GROUP4);
|
|
- val |= MT7531_PHY_PLL_BYPASS_MODE;
|
|
+ val |= MT7531_RG_SYSPLL_DMY2 | MT7531_PHY_PLL_BYPASS_MODE;
|
|
val &= ~MT7531_PHY_PLL_OFF;
|
|
mt7531_ind_c45_phy_write(priv, MT753X_CTRL_PHY_ADDR, MDIO_MMD_VEND2,
|
|
CORE_PLL_GROUP4, val);
|
|
|
|
+ /* Disable EEE advertisement on the switch PHYs. */
|
|
+ for (i = MT753X_CTRL_PHY_ADDR;
|
|
+ i < MT753X_CTRL_PHY_ADDR + MT7530_NUM_PHYS; i++) {
|
|
+ mt7531_ind_c45_phy_write(priv, i, MDIO_MMD_AN, MDIO_AN_EEE_ADV,
|
|
+ 0);
|
|
+ }
|
|
+
|
|
mt7531_setup_common(ds);
|
|
|
|
/* Setup VLAN ID 0 for VLAN-unaware bridges */
|
|
diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h
|
|
index ddefeb69afda1..0ad52d3cbfebb 100644
|
|
--- a/drivers/net/dsa/mt7530.h
|
|
+++ b/drivers/net/dsa/mt7530.h
|
|
@@ -32,6 +32,10 @@ enum mt753x_id {
|
|
#define SYSC_REG_RSTCTRL 0x34
|
|
#define RESET_MCM BIT(2)
|
|
|
|
+/* Register for ARL global control */
|
|
+#define MT753X_AGC 0xc
|
|
+#define LOCAL_EN BIT(7)
|
|
+
|
|
/* Registers to mac forward control for unknown frames */
|
|
#define MT7530_MFC 0x10
|
|
#define BC_FFP(x) (((x) & 0xff) << 24)
|
|
@@ -630,6 +634,7 @@ enum mt7531_clk_skew {
|
|
#define RG_SYSPLL_DDSFBK_EN BIT(12)
|
|
#define RG_SYSPLL_BIAS_EN BIT(11)
|
|
#define RG_SYSPLL_BIAS_LPF_EN BIT(10)
|
|
+#define MT7531_RG_SYSPLL_DMY2 BIT(6)
|
|
#define MT7531_PHY_PLL_OFF BIT(5)
|
|
#define MT7531_PHY_PLL_BYPASS_MODE BIT(4)
|
|
|
|
diff --git a/drivers/net/ethernet/intel/ice/ice_tc_lib.c b/drivers/net/ethernet/intel/ice/ice_tc_lib.c
|
|
index dd03cb69ad26b..76ad5930c0102 100644
|
|
--- a/drivers/net/ethernet/intel/ice/ice_tc_lib.c
|
|
+++ b/drivers/net/ethernet/intel/ice/ice_tc_lib.c
|
|
@@ -28,6 +28,8 @@ ice_tc_count_lkups(u32 flags, struct ice_tc_flower_lyr_2_4_hdrs *headers,
|
|
* - ICE_TC_FLWR_FIELD_VLAN_TPID (present if specified)
|
|
* - Tunnel flag (present if tunnel)
|
|
*/
|
|
+ if (fltr->direction == ICE_ESWITCH_FLTR_EGRESS)
|
|
+ lkups_cnt++;
|
|
|
|
if (flags & ICE_TC_FLWR_FIELD_TENANT_ID)
|
|
lkups_cnt++;
|
|
@@ -363,6 +365,11 @@ ice_tc_fill_rules(struct ice_hw *hw, u32 flags,
|
|
/* Always add direction metadata */
|
|
ice_rule_add_direction_metadata(&list[ICE_TC_METADATA_LKUP_IDX]);
|
|
|
|
+ if (tc_fltr->direction == ICE_ESWITCH_FLTR_EGRESS) {
|
|
+ ice_rule_add_src_vsi_metadata(&list[i]);
|
|
+ i++;
|
|
+ }
|
|
+
|
|
rule_info->tun_type = ice_sw_type_from_tunnel(tc_fltr->tunnel_type);
|
|
if (tc_fltr->tunnel_type != TNL_LAST) {
|
|
i = ice_tc_fill_tunnel_outer(flags, tc_fltr, list, i);
|
|
@@ -731,7 +738,7 @@ ice_eswitch_add_tc_fltr(struct ice_vsi *vsi, struct ice_tc_flower_fltr *fltr)
|
|
int ret;
|
|
int i;
|
|
|
|
- if (!flags || (flags & ICE_TC_FLWR_FIELD_ENC_SRC_L4_PORT)) {
|
|
+ if (flags & ICE_TC_FLWR_FIELD_ENC_SRC_L4_PORT) {
|
|
NL_SET_ERR_MSG_MOD(fltr->extack, "Unsupported encap field(s)");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
@@ -779,6 +786,7 @@ ice_eswitch_add_tc_fltr(struct ice_vsi *vsi, struct ice_tc_flower_fltr *fltr)
|
|
|
|
/* specify the cookie as filter_rule_id */
|
|
rule_info.fltr_rule_id = fltr->cookie;
|
|
+ rule_info.src_vsi = vsi->idx;
|
|
|
|
ret = ice_add_adv_rule(hw, list, lkups_cnt, &rule_info, &rule_added);
|
|
if (ret == -EEXIST) {
|
|
@@ -1440,7 +1448,10 @@ ice_parse_cls_flower(struct net_device *filter_dev, struct ice_vsi *vsi,
|
|
(BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IPV4_ADDRS) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IPV6_ADDRS) |
|
|
BIT_ULL(FLOW_DISSECTOR_KEY_ENC_KEYID) |
|
|
- BIT_ULL(FLOW_DISSECTOR_KEY_ENC_PORTS))) {
|
|
+ BIT_ULL(FLOW_DISSECTOR_KEY_ENC_PORTS) |
|
|
+ BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IP) |
|
|
+ BIT_ULL(FLOW_DISSECTOR_KEY_ENC_OPTS) |
|
|
+ BIT_ULL(FLOW_DISSECTOR_KEY_ENC_CONTROL))) {
|
|
NL_SET_ERR_MSG_MOD(fltr->extack, "Tunnel key used, but device isn't a tunnel");
|
|
return -EOPNOTSUPP;
|
|
} else {
|
|
diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c
|
|
index 423ce54eaea69..46bdbee9d38ad 100644
|
|
--- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c
|
|
+++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_tc.c
|
|
@@ -588,6 +588,7 @@ static int otx2_tc_prepare_flow(struct otx2_nic *nic, struct otx2_tc_flow *node,
|
|
|
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
|
|
struct flow_match_control match;
|
|
+ u32 val;
|
|
|
|
flow_rule_match_control(rule, &match);
|
|
if (match.mask->flags & FLOW_DIS_FIRST_FRAG) {
|
|
@@ -596,12 +597,14 @@ static int otx2_tc_prepare_flow(struct otx2_nic *nic, struct otx2_tc_flow *node,
|
|
}
|
|
|
|
if (match.mask->flags & FLOW_DIS_IS_FRAGMENT) {
|
|
+ val = match.key->flags & FLOW_DIS_IS_FRAGMENT;
|
|
if (ntohs(flow_spec->etype) == ETH_P_IP) {
|
|
- flow_spec->ip_flag = IPV4_FLAG_MORE;
|
|
+ flow_spec->ip_flag = val ? IPV4_FLAG_MORE : 0;
|
|
flow_mask->ip_flag = IPV4_FLAG_MORE;
|
|
req->features |= BIT_ULL(NPC_IPFRAG_IPV4);
|
|
} else if (ntohs(flow_spec->etype) == ETH_P_IPV6) {
|
|
- flow_spec->next_header = IPPROTO_FRAGMENT;
|
|
+ flow_spec->next_header = val ?
|
|
+ IPPROTO_FRAGMENT : 0;
|
|
flow_mask->next_header = 0xff;
|
|
req->features |= BIT_ULL(NPC_IPFRAG_IPV6);
|
|
} else {
|
|
diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c
|
|
index 94376aa2b34c5..c7196055c8c98 100644
|
|
--- a/drivers/net/ethernet/mediatek/mtk_wed.c
|
|
+++ b/drivers/net/ethernet/mediatek/mtk_wed.c
|
|
@@ -598,13 +598,13 @@ mtk_wed_dma_disable(struct mtk_wed_device *dev)
|
|
static void
|
|
mtk_wed_stop(struct mtk_wed_device *dev)
|
|
{
|
|
+ mtk_wed_dma_disable(dev);
|
|
mtk_wed_set_ext_int(dev, false);
|
|
|
|
wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER, 0);
|
|
wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, 0);
|
|
wdma_w32(dev, MTK_WDMA_INT_MASK, 0);
|
|
wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
|
|
- wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
|
|
|
|
if (dev->hw->version == 1)
|
|
return;
|
|
@@ -617,7 +617,6 @@ static void
|
|
mtk_wed_deinit(struct mtk_wed_device *dev)
|
|
{
|
|
mtk_wed_stop(dev);
|
|
- mtk_wed_dma_disable(dev);
|
|
|
|
wed_clr(dev, MTK_WED_CTRL,
|
|
MTK_WED_CTRL_WDMA_INT_AGENT_EN |
|
|
@@ -1703,9 +1702,6 @@ mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
|
|
static void
|
|
mtk_wed_irq_set_mask(struct mtk_wed_device *dev, u32 mask)
|
|
{
|
|
- if (!dev->running)
|
|
- return;
|
|
-
|
|
mtk_wed_set_ext_int(dev, !!mask);
|
|
wed_w32(dev, MTK_WED_INT_MASK, mask);
|
|
}
|
|
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_arfs.c b/drivers/net/ethernet/mellanox/mlx5/core/en_arfs.c
|
|
index e66f486faafe1..415fec7763bd2 100644
|
|
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_arfs.c
|
|
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_arfs.c
|
|
@@ -45,6 +45,10 @@ struct arfs_table {
|
|
struct hlist_head rules_hash[ARFS_HASH_SIZE];
|
|
};
|
|
|
|
+enum {
|
|
+ MLX5E_ARFS_STATE_ENABLED,
|
|
+};
|
|
+
|
|
enum arfs_type {
|
|
ARFS_IPV4_TCP,
|
|
ARFS_IPV6_TCP,
|
|
@@ -59,6 +63,7 @@ struct mlx5e_arfs_tables {
|
|
spinlock_t arfs_lock;
|
|
int last_filter_id;
|
|
struct workqueue_struct *wq;
|
|
+ unsigned long state;
|
|
};
|
|
|
|
struct arfs_tuple {
|
|
@@ -169,6 +174,8 @@ int mlx5e_arfs_enable(struct mlx5e_flow_steering *fs)
|
|
return err;
|
|
}
|
|
}
|
|
+ set_bit(MLX5E_ARFS_STATE_ENABLED, &arfs->state);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -454,6 +461,8 @@ static void arfs_del_rules(struct mlx5e_flow_steering *fs)
|
|
int i;
|
|
int j;
|
|
|
|
+ clear_bit(MLX5E_ARFS_STATE_ENABLED, &arfs->state);
|
|
+
|
|
spin_lock_bh(&arfs->arfs_lock);
|
|
mlx5e_for_each_arfs_rule(rule, htmp, arfs->arfs_tables, i, j) {
|
|
hlist_del_init(&rule->hlist);
|
|
@@ -626,17 +635,8 @@ static void arfs_handle_work(struct work_struct *work)
|
|
struct mlx5_flow_handle *rule;
|
|
|
|
arfs = mlx5e_fs_get_arfs(priv->fs);
|
|
- mutex_lock(&priv->state_lock);
|
|
- if (!test_bit(MLX5E_STATE_OPENED, &priv->state)) {
|
|
- spin_lock_bh(&arfs->arfs_lock);
|
|
- hlist_del(&arfs_rule->hlist);
|
|
- spin_unlock_bh(&arfs->arfs_lock);
|
|
-
|
|
- mutex_unlock(&priv->state_lock);
|
|
- kfree(arfs_rule);
|
|
- goto out;
|
|
- }
|
|
- mutex_unlock(&priv->state_lock);
|
|
+ if (!test_bit(MLX5E_ARFS_STATE_ENABLED, &arfs->state))
|
|
+ return;
|
|
|
|
if (!arfs_rule->rule) {
|
|
rule = arfs_add_rule(priv, arfs_rule);
|
|
@@ -752,6 +752,11 @@ int mlx5e_rx_flow_steer(struct net_device *dev, const struct sk_buff *skb,
|
|
return -EPROTONOSUPPORT;
|
|
|
|
spin_lock_bh(&arfs->arfs_lock);
|
|
+ if (!test_bit(MLX5E_ARFS_STATE_ENABLED, &arfs->state)) {
|
|
+ spin_unlock_bh(&arfs->arfs_lock);
|
|
+ return -EPERM;
|
|
+ }
|
|
+
|
|
arfs_rule = arfs_find_rule(arfs_t, &fk);
|
|
if (arfs_rule) {
|
|
if (arfs_rule->rxq == rxq_index || work_busy(&arfs_rule->arfs_work)) {
|
|
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
|
|
index 3047d7015c525..1789800faaeb6 100644
|
|
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
|
|
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c
|
|
@@ -1868,6 +1868,7 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev)
|
|
if (err)
|
|
goto abort;
|
|
|
|
+ dev->priv.eswitch = esw;
|
|
err = esw_offloads_init(esw);
|
|
if (err)
|
|
goto reps_err;
|
|
@@ -1892,11 +1893,6 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev)
|
|
esw->offloads.encap = DEVLINK_ESWITCH_ENCAP_MODE_BASIC;
|
|
else
|
|
esw->offloads.encap = DEVLINK_ESWITCH_ENCAP_MODE_NONE;
|
|
- if (MLX5_ESWITCH_MANAGER(dev) &&
|
|
- mlx5_esw_vport_match_metadata_supported(esw))
|
|
- esw->flags |= MLX5_ESWITCH_VPORT_MATCH_METADATA;
|
|
-
|
|
- dev->priv.eswitch = esw;
|
|
BLOCKING_INIT_NOTIFIER_HEAD(&esw->n_head);
|
|
|
|
esw_info(dev,
|
|
@@ -1908,6 +1904,7 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev)
|
|
|
|
reps_err:
|
|
mlx5_esw_vports_cleanup(esw);
|
|
+ dev->priv.eswitch = NULL;
|
|
abort:
|
|
if (esw->work_queue)
|
|
destroy_workqueue(esw->work_queue);
|
|
@@ -1926,7 +1923,6 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw)
|
|
|
|
esw_info(esw->dev, "cleanup\n");
|
|
|
|
- esw->dev->priv.eswitch = NULL;
|
|
destroy_workqueue(esw->work_queue);
|
|
WARN_ON(refcount_read(&esw->qos.refcnt));
|
|
mutex_destroy(&esw->state_lock);
|
|
@@ -1937,6 +1933,7 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw)
|
|
mutex_destroy(&esw->offloads.encap_tbl_lock);
|
|
mutex_destroy(&esw->offloads.decap_tbl_lock);
|
|
esw_offloads_cleanup(esw);
|
|
+ esw->dev->priv.eswitch = NULL;
|
|
mlx5_esw_vports_cleanup(esw);
|
|
debugfs_remove_recursive(esw->debugfs_root);
|
|
devl_params_unregister(priv_to_devlink(esw->dev), mlx5_eswitch_params,
|
|
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
|
|
index baaae628b0a0f..e3cce110e52fd 100644
|
|
--- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
|
|
+++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c
|
|
@@ -2476,6 +2476,10 @@ int esw_offloads_init(struct mlx5_eswitch *esw)
|
|
if (err)
|
|
return err;
|
|
|
|
+ if (MLX5_ESWITCH_MANAGER(esw->dev) &&
|
|
+ mlx5_esw_vport_match_metadata_supported(esw))
|
|
+ esw->flags |= MLX5_ESWITCH_VPORT_MATCH_METADATA;
|
|
+
|
|
err = devl_params_register(priv_to_devlink(esw->dev),
|
|
esw_devlink_params,
|
|
ARRAY_SIZE(esw_devlink_params));
|
|
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c
|
|
index af3fac090b828..e51cac1e1811e 100644
|
|
--- a/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c
|
|
+++ b/drivers/net/ethernet/mellanox/mlx5/core/lag/lag.c
|
|
@@ -703,8 +703,10 @@ int mlx5_deactivate_lag(struct mlx5_lag *ldev)
|
|
return err;
|
|
}
|
|
|
|
- if (test_bit(MLX5_LAG_MODE_FLAG_HASH_BASED, &flags))
|
|
+ if (test_bit(MLX5_LAG_MODE_FLAG_HASH_BASED, &flags)) {
|
|
mlx5_lag_port_sel_destroy(ldev);
|
|
+ ldev->buckets = 1;
|
|
+ }
|
|
if (mlx5_lag_has_drop_rule(ldev))
|
|
mlx5_lag_drop_rule_cleanup(ldev);
|
|
|
|
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_flower.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_flower.c
|
|
index 523e0c470894f..55f255a3c9db6 100644
|
|
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc_flower.c
|
|
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc_flower.c
|
|
@@ -36,6 +36,27 @@ struct sparx5_tc_flower_template {
|
|
u16 l3_proto; /* protocol specified in the template */
|
|
};
|
|
|
|
+/* SparX-5 VCAP fragment types:
|
|
+ * 0 = no fragment, 1 = initial fragment,
|
|
+ * 2 = suspicious fragment, 3 = valid follow-up fragment
|
|
+ */
|
|
+enum { /* key / mask */
|
|
+ FRAG_NOT = 0x03, /* 0 / 3 */
|
|
+ FRAG_SOME = 0x11, /* 1 / 1 */
|
|
+ FRAG_FIRST = 0x13, /* 1 / 3 */
|
|
+ FRAG_LATER = 0x33, /* 3 / 3 */
|
|
+ FRAG_INVAL = 0xff, /* invalid */
|
|
+};
|
|
+
|
|
+/* Flower fragment flag to VCAP fragment type mapping */
|
|
+static const u8 sparx5_vcap_frag_map[4][4] = { /* is_frag */
|
|
+ { FRAG_INVAL, FRAG_INVAL, FRAG_INVAL, FRAG_FIRST }, /* 0/0 */
|
|
+ { FRAG_NOT, FRAG_NOT, FRAG_INVAL, FRAG_INVAL }, /* 0/1 */
|
|
+ { FRAG_INVAL, FRAG_INVAL, FRAG_INVAL, FRAG_INVAL }, /* 1/0 */
|
|
+ { FRAG_SOME, FRAG_LATER, FRAG_INVAL, FRAG_FIRST } /* 1/1 */
|
|
+ /* 0/0 0/1 1/0 1/1 <-- first_frag */
|
|
+};
|
|
+
|
|
static int
|
|
sparx5_tc_flower_es0_tpid(struct vcap_tc_flower_parse_usage *st)
|
|
{
|
|
@@ -145,29 +166,27 @@ sparx5_tc_flower_handler_control_usage(struct vcap_tc_flower_parse_usage *st)
|
|
flow_rule_match_control(st->frule, &mt);
|
|
|
|
if (mt.mask->flags) {
|
|
- if (mt.mask->flags & FLOW_DIS_FIRST_FRAG) {
|
|
- if (mt.key->flags & FLOW_DIS_FIRST_FRAG) {
|
|
- value = 1; /* initial fragment */
|
|
- mask = 0x3;
|
|
- } else {
|
|
- if (mt.mask->flags & FLOW_DIS_IS_FRAGMENT) {
|
|
- value = 3; /* follow up fragment */
|
|
- mask = 0x3;
|
|
- } else {
|
|
- value = 0; /* no fragment */
|
|
- mask = 0x3;
|
|
- }
|
|
- }
|
|
- } else {
|
|
- if (mt.mask->flags & FLOW_DIS_IS_FRAGMENT) {
|
|
- value = 3; /* follow up fragment */
|
|
- mask = 0x3;
|
|
- } else {
|
|
- value = 0; /* no fragment */
|
|
- mask = 0x3;
|
|
- }
|
|
+ u8 is_frag_key = !!(mt.key->flags & FLOW_DIS_IS_FRAGMENT);
|
|
+ u8 is_frag_mask = !!(mt.mask->flags & FLOW_DIS_IS_FRAGMENT);
|
|
+ u8 is_frag_idx = (is_frag_key << 1) | is_frag_mask;
|
|
+
|
|
+ u8 first_frag_key = !!(mt.key->flags & FLOW_DIS_FIRST_FRAG);
|
|
+ u8 first_frag_mask = !!(mt.mask->flags & FLOW_DIS_FIRST_FRAG);
|
|
+ u8 first_frag_idx = (first_frag_key << 1) | first_frag_mask;
|
|
+
|
|
+ /* Lookup verdict based on the 2 + 2 input bits */
|
|
+ u8 vdt = sparx5_vcap_frag_map[is_frag_idx][first_frag_idx];
|
|
+
|
|
+ if (vdt == FRAG_INVAL) {
|
|
+ NL_SET_ERR_MSG_MOD(st->fco->common.extack,
|
|
+ "Match on invalid fragment flag combination");
|
|
+ return -EINVAL;
|
|
}
|
|
|
|
+ /* Extract VCAP fragment key and mask from verdict */
|
|
+ value = (vdt >> 4) & 0x3;
|
|
+ mask = vdt & 0x3;
|
|
+
|
|
err = vcap_rule_add_key_u32(st->vrule,
|
|
VCAP_KF_L3_FRAGMENT_TYPE,
|
|
value, mask);
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h
|
|
index b0dd8adce3560..4dbc076f72d65 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/common.h
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/common.h
|
|
@@ -550,6 +550,7 @@ extern const struct stmmac_hwtimestamp stmmac_ptp;
|
|
extern const struct stmmac_mode_ops dwmac4_ring_mode_ops;
|
|
|
|
struct mac_link {
|
|
+ u32 caps;
|
|
u32 speed_mask;
|
|
u32 speed10;
|
|
u32 speed100;
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
|
|
index 51f121f867457..63998d65fef8e 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
|
|
@@ -1096,6 +1096,8 @@ static struct mac_device_info *sun8i_dwmac_setup(void *ppriv)
|
|
|
|
priv->dev->priv_flags |= IFF_UNICAST_FLT;
|
|
|
|
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_10 | MAC_100 | MAC_1000;
|
|
/* The loopback bit seems to be re-set when link change
|
|
* Simply mask it each time
|
|
* Speed 10/100/1000 are set in BIT(2)/BIT(3)
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
|
|
index 3927609abc441..8555299443f4e 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c
|
|
@@ -539,6 +539,8 @@ int dwmac1000_setup(struct stmmac_priv *priv)
|
|
if (mac->multicast_filter_bins)
|
|
mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
|
|
|
|
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_10 | MAC_100 | MAC_1000;
|
|
mac->link.duplex = GMAC_CONTROL_DM;
|
|
mac->link.speed10 = GMAC_CONTROL_PS;
|
|
mac->link.speed100 = GMAC_CONTROL_PS | GMAC_CONTROL_FES;
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c
|
|
index a6e8d7bd95886..7667d103cd0eb 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c
|
|
@@ -175,6 +175,8 @@ int dwmac100_setup(struct stmmac_priv *priv)
|
|
dev_info(priv->device, "\tDWMAC100\n");
|
|
|
|
mac->pcsr = priv->ioaddr;
|
|
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_10 | MAC_100;
|
|
mac->link.duplex = MAC_CONTROL_F;
|
|
mac->link.speed10 = 0;
|
|
mac->link.speed100 = 0;
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c
|
|
index 683c34e609638..4ead0ddf43a7a 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c
|
|
@@ -70,7 +70,10 @@ static void dwmac4_core_init(struct mac_device_info *hw,
|
|
|
|
static void dwmac4_phylink_get_caps(struct stmmac_priv *priv)
|
|
{
|
|
- priv->phylink_config.mac_capabilities |= MAC_2500FD;
|
|
+ if (priv->plat->tx_queues_to_use > 1)
|
|
+ priv->hw->link.caps &= ~(MAC_10HD | MAC_100HD | MAC_1000HD);
|
|
+ else
|
|
+ priv->hw->link.caps |= (MAC_10HD | MAC_100HD | MAC_1000HD);
|
|
}
|
|
|
|
static void dwmac4_rx_queue_enable(struct mac_device_info *hw,
|
|
@@ -1347,6 +1350,8 @@ int dwmac4_setup(struct stmmac_priv *priv)
|
|
if (mac->multicast_filter_bins)
|
|
mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
|
|
|
|
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_10 | MAC_100 | MAC_1000 | MAC_2500FD;
|
|
mac->link.duplex = GMAC_CONFIG_DM;
|
|
mac->link.speed10 = GMAC_CONFIG_PS;
|
|
mac->link.speed100 = GMAC_CONFIG_FES | GMAC_CONFIG_PS;
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
|
|
index 24c53b7255a2e..8bc317d2f7a61 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c
|
|
@@ -47,14 +47,6 @@ static void dwxgmac2_core_init(struct mac_device_info *hw,
|
|
writel(XGMAC_INT_DEFAULT_EN, ioaddr + XGMAC_INT_EN);
|
|
}
|
|
|
|
-static void xgmac_phylink_get_caps(struct stmmac_priv *priv)
|
|
-{
|
|
- priv->phylink_config.mac_capabilities |= MAC_2500FD | MAC_5000FD |
|
|
- MAC_10000FD | MAC_25000FD |
|
|
- MAC_40000FD | MAC_50000FD |
|
|
- MAC_100000FD;
|
|
-}
|
|
-
|
|
static void dwxgmac2_set_mac(void __iomem *ioaddr, bool enable)
|
|
{
|
|
u32 tx = readl(ioaddr + XGMAC_TX_CONFIG);
|
|
@@ -1591,7 +1583,6 @@ static void dwxgmac3_fpe_configure(void __iomem *ioaddr, struct stmmac_fpe_cfg *
|
|
|
|
const struct stmmac_ops dwxgmac210_ops = {
|
|
.core_init = dwxgmac2_core_init,
|
|
- .phylink_get_caps = xgmac_phylink_get_caps,
|
|
.set_mac = dwxgmac2_set_mac,
|
|
.rx_ipc = dwxgmac2_rx_ipc,
|
|
.rx_queue_enable = dwxgmac2_rx_queue_enable,
|
|
@@ -1653,7 +1644,6 @@ static void dwxlgmac2_rx_queue_enable(struct mac_device_info *hw, u8 mode,
|
|
|
|
const struct stmmac_ops dwxlgmac2_ops = {
|
|
.core_init = dwxgmac2_core_init,
|
|
- .phylink_get_caps = xgmac_phylink_get_caps,
|
|
.set_mac = dwxgmac2_set_mac,
|
|
.rx_ipc = dwxgmac2_rx_ipc,
|
|
.rx_queue_enable = dwxlgmac2_rx_queue_enable,
|
|
@@ -1714,6 +1704,9 @@ int dwxgmac2_setup(struct stmmac_priv *priv)
|
|
if (mac->multicast_filter_bins)
|
|
mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
|
|
|
|
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_1000FD | MAC_2500FD | MAC_5000FD |
|
|
+ MAC_10000FD;
|
|
mac->link.duplex = 0;
|
|
mac->link.speed10 = XGMAC_CONFIG_SS_10_MII;
|
|
mac->link.speed100 = XGMAC_CONFIG_SS_100_MII;
|
|
@@ -1751,6 +1744,11 @@ int dwxlgmac2_setup(struct stmmac_priv *priv)
|
|
if (mac->multicast_filter_bins)
|
|
mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
|
|
|
|
+ mac->link.caps = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_1000FD | MAC_2500FD | MAC_5000FD |
|
|
+ MAC_10000FD | MAC_25000FD |
|
|
+ MAC_40000FD | MAC_50000FD |
|
|
+ MAC_100000FD;
|
|
mac->link.duplex = 0;
|
|
mac->link.speed1000 = XLGMAC_CONFIG_SS_1000;
|
|
mac->link.speed2500 = XLGMAC_CONFIG_SS_2500;
|
|
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
|
|
index d1adb102a1d49..19c58ad8df345 100644
|
|
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
|
|
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
|
|
@@ -1198,17 +1198,6 @@ static int stmmac_init_phy(struct net_device *dev)
|
|
return ret;
|
|
}
|
|
|
|
-static void stmmac_set_half_duplex(struct stmmac_priv *priv)
|
|
-{
|
|
- /* Half-Duplex can only work with single tx queue */
|
|
- if (priv->plat->tx_queues_to_use > 1)
|
|
- priv->phylink_config.mac_capabilities &=
|
|
- ~(MAC_10HD | MAC_100HD | MAC_1000HD);
|
|
- else
|
|
- priv->phylink_config.mac_capabilities |=
|
|
- (MAC_10HD | MAC_100HD | MAC_1000HD);
|
|
-}
|
|
-
|
|
static int stmmac_phy_setup(struct stmmac_priv *priv)
|
|
{
|
|
struct stmmac_mdio_bus_data *mdio_bus_data;
|
|
@@ -1236,15 +1225,11 @@ static int stmmac_phy_setup(struct stmmac_priv *priv)
|
|
xpcs_get_interfaces(priv->hw->xpcs,
|
|
priv->phylink_config.supported_interfaces);
|
|
|
|
- priv->phylink_config.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
- MAC_10FD | MAC_100FD |
|
|
- MAC_1000FD;
|
|
-
|
|
- stmmac_set_half_duplex(priv);
|
|
-
|
|
/* Get the MAC specific capabilities */
|
|
stmmac_mac_phylink_get_caps(priv);
|
|
|
|
+ priv->phylink_config.mac_capabilities = priv->hw->link.caps;
|
|
+
|
|
max_speed = priv->plat->max_speed;
|
|
if (max_speed)
|
|
phylink_limit_mac_speed(&priv->phylink_config, max_speed);
|
|
@@ -7184,6 +7169,7 @@ int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
int ret = 0, i;
|
|
+ int max_speed;
|
|
|
|
if (netif_running(dev))
|
|
stmmac_release(dev);
|
|
@@ -7197,7 +7183,14 @@ int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt)
|
|
priv->rss.table[i] = ethtool_rxfh_indir_default(i,
|
|
rx_cnt);
|
|
|
|
- stmmac_set_half_duplex(priv);
|
|
+ stmmac_mac_phylink_get_caps(priv);
|
|
+
|
|
+ priv->phylink_config.mac_capabilities = priv->hw->link.caps;
|
|
+
|
|
+ max_speed = priv->plat->max_speed;
|
|
+ if (max_speed)
|
|
+ phylink_limit_mac_speed(&priv->phylink_config, max_speed);
|
|
+
|
|
stmmac_napi_add(dev);
|
|
|
|
if (netif_running(dev))
|
|
diff --git a/drivers/net/ethernet/ti/am65-cpsw-nuss.c b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
|
|
index c62b0f99f2bc4..d556e705ec000 100644
|
|
--- a/drivers/net/ethernet/ti/am65-cpsw-nuss.c
|
|
+++ b/drivers/net/ethernet/ti/am65-cpsw-nuss.c
|
|
@@ -2716,6 +2716,8 @@ static void am65_cpsw_unregister_devlink(struct am65_cpsw_common *common)
|
|
|
|
static int am65_cpsw_nuss_register_ndevs(struct am65_cpsw_common *common)
|
|
{
|
|
+ struct am65_cpsw_rx_chn *rx_chan = &common->rx_chns;
|
|
+ struct am65_cpsw_tx_chn *tx_chan = common->tx_chns;
|
|
struct device *dev = common->dev;
|
|
struct am65_cpsw_port *port;
|
|
int ret = 0, i;
|
|
@@ -2728,6 +2730,22 @@ static int am65_cpsw_nuss_register_ndevs(struct am65_cpsw_common *common)
|
|
if (ret)
|
|
return ret;
|
|
|
|
+ /* The DMA Channels are not guaranteed to be in a clean state.
|
|
+ * Reset and disable them to ensure that they are back to the
|
|
+ * clean state and ready to be used.
|
|
+ */
|
|
+ for (i = 0; i < common->tx_ch_num; i++) {
|
|
+ k3_udma_glue_reset_tx_chn(tx_chan[i].tx_chn, &tx_chan[i],
|
|
+ am65_cpsw_nuss_tx_cleanup);
|
|
+ k3_udma_glue_disable_tx_chn(tx_chan[i].tx_chn);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < AM65_CPSW_MAX_RX_FLOWS; i++)
|
|
+ k3_udma_glue_reset_rx_chn(rx_chan->rx_chn, i, rx_chan,
|
|
+ am65_cpsw_nuss_rx_cleanup, !!i);
|
|
+
|
|
+ k3_udma_glue_disable_rx_chn(rx_chan->rx_chn);
|
|
+
|
|
ret = am65_cpsw_nuss_register_devlink(common);
|
|
if (ret)
|
|
return ret;
|
|
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
|
|
index 8f95a562b8d0c..86515f0c2b6c1 100644
|
|
--- a/drivers/net/tun.c
|
|
+++ b/drivers/net/tun.c
|
|
@@ -2132,14 +2132,16 @@ static ssize_t tun_put_user(struct tun_struct *tun,
|
|
tun_is_little_endian(tun), true,
|
|
vlan_hlen)) {
|
|
struct skb_shared_info *sinfo = skb_shinfo(skb);
|
|
- pr_err("unexpected GSO type: "
|
|
- "0x%x, gso_size %d, hdr_len %d\n",
|
|
- sinfo->gso_type, tun16_to_cpu(tun, gso.gso_size),
|
|
- tun16_to_cpu(tun, gso.hdr_len));
|
|
- print_hex_dump(KERN_ERR, "tun: ",
|
|
- DUMP_PREFIX_NONE,
|
|
- 16, 1, skb->head,
|
|
- min((int)tun16_to_cpu(tun, gso.hdr_len), 64), true);
|
|
+
|
|
+ if (net_ratelimit()) {
|
|
+ netdev_err(tun->dev, "unexpected GSO type: 0x%x, gso_size %d, hdr_len %d\n",
|
|
+ sinfo->gso_type, tun16_to_cpu(tun, gso.gso_size),
|
|
+ tun16_to_cpu(tun, gso.hdr_len));
|
|
+ print_hex_dump(KERN_ERR, "tun: ",
|
|
+ DUMP_PREFIX_NONE,
|
|
+ 16, 1, skb->head,
|
|
+ min((int)tun16_to_cpu(tun, gso.hdr_len), 64), true);
|
|
+ }
|
|
WARN_ON_ONCE(1);
|
|
return -EINVAL;
|
|
}
|
|
diff --git a/drivers/net/usb/ax88179_178a.c b/drivers/net/usb/ax88179_178a.c
|
|
index e0e9b4c53cb02..3078511f76083 100644
|
|
--- a/drivers/net/usb/ax88179_178a.c
|
|
+++ b/drivers/net/usb/ax88179_178a.c
|
|
@@ -1317,6 +1317,8 @@ static int ax88179_bind(struct usbnet *dev, struct usb_interface *intf)
|
|
|
|
netif_set_tso_max_size(dev->net, 16384);
|
|
|
|
+ ax88179_reset(dev);
|
|
+
|
|
return 0;
|
|
}
|
|
|
|
@@ -1695,7 +1697,6 @@ static const struct driver_info ax88179_info = {
|
|
.unbind = ax88179_unbind,
|
|
.status = ax88179_status,
|
|
.link_reset = ax88179_link_reset,
|
|
- .reset = ax88179_reset,
|
|
.stop = ax88179_stop,
|
|
.flags = FLAG_ETHER | FLAG_FRAMING_AX,
|
|
.rx_fixup = ax88179_rx_fixup,
|
|
@@ -1708,7 +1709,6 @@ static const struct driver_info ax88178a_info = {
|
|
.unbind = ax88179_unbind,
|
|
.status = ax88179_status,
|
|
.link_reset = ax88179_link_reset,
|
|
- .reset = ax88179_reset,
|
|
.stop = ax88179_stop,
|
|
.flags = FLAG_ETHER | FLAG_FRAMING_AX,
|
|
.rx_fixup = ax88179_rx_fixup,
|
|
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
|
|
index 7cb0548d17a3f..56cbe00126bb1 100644
|
|
--- a/drivers/net/virtio_net.c
|
|
+++ b/drivers/net/virtio_net.c
|
|
@@ -3570,19 +3570,34 @@ static int virtnet_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, u8 *hfu
|
|
static int virtnet_set_rxfh(struct net_device *dev, const u32 *indir, const u8 *key, const u8 hfunc)
|
|
{
|
|
struct virtnet_info *vi = netdev_priv(dev);
|
|
+ bool update = false;
|
|
int i;
|
|
|
|
if (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (indir) {
|
|
+ if (!vi->has_rss)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
for (i = 0; i < vi->rss_indir_table_size; ++i)
|
|
vi->ctrl->rss.indirection_table[i] = indir[i];
|
|
+ update = true;
|
|
}
|
|
- if (key)
|
|
+ if (key) {
|
|
+ /* If either _F_HASH_REPORT or _F_RSS are negotiated, the
|
|
+ * device provides hash calculation capabilities, that is,
|
|
+ * hash_key is configured.
|
|
+ */
|
|
+ if (!vi->has_rss && !vi->has_rss_hash_report)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
memcpy(vi->ctrl->rss.key, key, vi->rss_key_size);
|
|
+ update = true;
|
|
+ }
|
|
|
|
- virtnet_commit_rss_command(vi);
|
|
+ if (update)
|
|
+ virtnet_commit_rss_command(vi);
|
|
|
|
return 0;
|
|
}
|
|
@@ -4491,13 +4506,15 @@ static int virtnet_probe(struct virtio_device *vdev)
|
|
if (virtio_has_feature(vdev, VIRTIO_NET_F_HASH_REPORT))
|
|
vi->has_rss_hash_report = true;
|
|
|
|
- if (virtio_has_feature(vdev, VIRTIO_NET_F_RSS))
|
|
+ if (virtio_has_feature(vdev, VIRTIO_NET_F_RSS)) {
|
|
vi->has_rss = true;
|
|
|
|
- if (vi->has_rss || vi->has_rss_hash_report) {
|
|
vi->rss_indir_table_size =
|
|
virtio_cread16(vdev, offsetof(struct virtio_net_config,
|
|
rss_max_indirection_table_length));
|
|
+ }
|
|
+
|
|
+ if (vi->has_rss || vi->has_rss_hash_report) {
|
|
vi->rss_key_size =
|
|
virtio_cread8(vdev, offsetof(struct virtio_net_config, rss_max_key_size));
|
|
|
|
diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c
|
|
index 9c2137dae429a..826b5016a1010 100644
|
|
--- a/drivers/pci/bus.c
|
|
+++ b/drivers/pci/bus.c
|
|
@@ -386,21 +386,8 @@ void pci_bus_add_devices(const struct pci_bus *bus)
|
|
}
|
|
EXPORT_SYMBOL(pci_bus_add_devices);
|
|
|
|
-/** pci_walk_bus - walk devices on/under bus, calling callback.
|
|
- * @top bus whose devices should be walked
|
|
- * @cb callback to be called for each device found
|
|
- * @userdata arbitrary pointer to be passed to callback.
|
|
- *
|
|
- * Walk the given bus, including any bridged devices
|
|
- * on buses under this bus. Call the provided callback
|
|
- * on each device found.
|
|
- *
|
|
- * We check the return of @cb each time. If it returns anything
|
|
- * other than 0, we break out.
|
|
- *
|
|
- */
|
|
-void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
- void *userdata)
|
|
+static void __pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
+ void *userdata, bool locked)
|
|
{
|
|
struct pci_dev *dev;
|
|
struct pci_bus *bus;
|
|
@@ -408,7 +395,8 @@ void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
int retval;
|
|
|
|
bus = top;
|
|
- down_read(&pci_bus_sem);
|
|
+ if (!locked)
|
|
+ down_read(&pci_bus_sem);
|
|
next = top->devices.next;
|
|
for (;;) {
|
|
if (next == &bus->devices) {
|
|
@@ -431,10 +419,37 @@ void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
if (retval)
|
|
break;
|
|
}
|
|
- up_read(&pci_bus_sem);
|
|
+ if (!locked)
|
|
+ up_read(&pci_bus_sem);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pci_walk_bus - walk devices on/under bus, calling callback.
|
|
+ * @top: bus whose devices should be walked
|
|
+ * @cb: callback to be called for each device found
|
|
+ * @userdata: arbitrary pointer to be passed to callback
|
|
+ *
|
|
+ * Walk the given bus, including any bridged devices
|
|
+ * on buses under this bus. Call the provided callback
|
|
+ * on each device found.
|
|
+ *
|
|
+ * We check the return of @cb each time. If it returns anything
|
|
+ * other than 0, we break out.
|
|
+ */
|
|
+void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata)
|
|
+{
|
|
+ __pci_walk_bus(top, cb, userdata, false);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_walk_bus);
|
|
|
|
+void pci_walk_bus_locked(struct pci_bus *top, int (*cb)(struct pci_dev *, void *), void *userdata)
|
|
+{
|
|
+ lockdep_assert_held(&pci_bus_sem);
|
|
+
|
|
+ __pci_walk_bus(top, cb, userdata, true);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pci_walk_bus_locked);
|
|
+
|
|
struct pci_bus *pci_bus_get(struct pci_bus *bus)
|
|
{
|
|
if (bus)
|
|
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
|
|
index 06fc6f532d6c4..dddd30deea32b 100644
|
|
--- a/drivers/pci/pci.c
|
|
+++ b/drivers/pci/pci.c
|
|
@@ -1291,6 +1291,7 @@ int pci_power_up(struct pci_dev *dev)
|
|
/**
|
|
* pci_set_full_power_state - Put a PCI device into D0 and update its state
|
|
* @dev: PCI device to power up
|
|
+ * @locked: whether pci_bus_sem is held
|
|
*
|
|
* Call pci_power_up() to put @dev into D0, read from its PCI_PM_CTRL register
|
|
* to confirm the state change, restore its BARs if they might be lost and
|
|
@@ -1300,7 +1301,7 @@ int pci_power_up(struct pci_dev *dev)
|
|
* to D0, it is more efficient to use pci_power_up() directly instead of this
|
|
* function.
|
|
*/
|
|
-static int pci_set_full_power_state(struct pci_dev *dev)
|
|
+static int pci_set_full_power_state(struct pci_dev *dev, bool locked)
|
|
{
|
|
u16 pmcsr;
|
|
int ret;
|
|
@@ -1336,7 +1337,7 @@ static int pci_set_full_power_state(struct pci_dev *dev)
|
|
}
|
|
|
|
if (dev->bus->self)
|
|
- pcie_aspm_pm_state_change(dev->bus->self);
|
|
+ pcie_aspm_pm_state_change(dev->bus->self, locked);
|
|
|
|
return 0;
|
|
}
|
|
@@ -1365,10 +1366,22 @@ void pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state)
|
|
pci_walk_bus(bus, __pci_dev_set_current_state, &state);
|
|
}
|
|
|
|
+static void __pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state, bool locked)
|
|
+{
|
|
+ if (!bus)
|
|
+ return;
|
|
+
|
|
+ if (locked)
|
|
+ pci_walk_bus_locked(bus, __pci_dev_set_current_state, &state);
|
|
+ else
|
|
+ pci_walk_bus(bus, __pci_dev_set_current_state, &state);
|
|
+}
|
|
+
|
|
/**
|
|
* pci_set_low_power_state - Put a PCI device into a low-power state.
|
|
* @dev: PCI device to handle.
|
|
* @state: PCI power state (D1, D2, D3hot) to put the device into.
|
|
+ * @locked: whether pci_bus_sem is held
|
|
*
|
|
* Use the device's PCI_PM_CTRL register to put it into a low-power state.
|
|
*
|
|
@@ -1379,7 +1392,7 @@ void pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state)
|
|
* 0 if device already is in the requested state.
|
|
* 0 if device's power state has been successfully changed.
|
|
*/
|
|
-static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state)
|
|
+static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state, bool locked)
|
|
{
|
|
u16 pmcsr;
|
|
|
|
@@ -1433,29 +1446,12 @@ static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state)
|
|
pci_power_name(state));
|
|
|
|
if (dev->bus->self)
|
|
- pcie_aspm_pm_state_change(dev->bus->self);
|
|
+ pcie_aspm_pm_state_change(dev->bus->self, locked);
|
|
|
|
return 0;
|
|
}
|
|
|
|
-/**
|
|
- * pci_set_power_state - Set the power state of a PCI device
|
|
- * @dev: PCI device to handle.
|
|
- * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
|
|
- *
|
|
- * Transition a device to a new power state, using the platform firmware and/or
|
|
- * the device's PCI PM registers.
|
|
- *
|
|
- * RETURN VALUE:
|
|
- * -EINVAL if the requested state is invalid.
|
|
- * -EIO if device does not support PCI PM or its PM capabilities register has a
|
|
- * wrong version, or device doesn't support the requested state.
|
|
- * 0 if the transition is to D1 or D2 but D1 and D2 are not supported.
|
|
- * 0 if device already is in the requested state.
|
|
- * 0 if the transition is to D3 but D3 is not supported.
|
|
- * 0 if device's power state has been successfully changed.
|
|
- */
|
|
-int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
|
+static int __pci_set_power_state(struct pci_dev *dev, pci_power_t state, bool locked)
|
|
{
|
|
int error;
|
|
|
|
@@ -1479,7 +1475,7 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
|
return 0;
|
|
|
|
if (state == PCI_D0)
|
|
- return pci_set_full_power_state(dev);
|
|
+ return pci_set_full_power_state(dev, locked);
|
|
|
|
/*
|
|
* This device is quirked not to be put into D3, so don't put it in
|
|
@@ -1493,16 +1489,16 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
|
* To put the device in D3cold, put it into D3hot in the native
|
|
* way, then put it into D3cold using platform ops.
|
|
*/
|
|
- error = pci_set_low_power_state(dev, PCI_D3hot);
|
|
+ error = pci_set_low_power_state(dev, PCI_D3hot, locked);
|
|
|
|
if (pci_platform_power_transition(dev, PCI_D3cold))
|
|
return error;
|
|
|
|
/* Powering off a bridge may power off the whole hierarchy */
|
|
if (dev->current_state == PCI_D3cold)
|
|
- pci_bus_set_current_state(dev->subordinate, PCI_D3cold);
|
|
+ __pci_bus_set_current_state(dev->subordinate, PCI_D3cold, locked);
|
|
} else {
|
|
- error = pci_set_low_power_state(dev, state);
|
|
+ error = pci_set_low_power_state(dev, state, locked);
|
|
|
|
if (pci_platform_power_transition(dev, state))
|
|
return error;
|
|
@@ -1510,8 +1506,38 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
|
|
|
return 0;
|
|
}
|
|
+
|
|
+/**
|
|
+ * pci_set_power_state - Set the power state of a PCI device
|
|
+ * @dev: PCI device to handle.
|
|
+ * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
|
|
+ *
|
|
+ * Transition a device to a new power state, using the platform firmware and/or
|
|
+ * the device's PCI PM registers.
|
|
+ *
|
|
+ * RETURN VALUE:
|
|
+ * -EINVAL if the requested state is invalid.
|
|
+ * -EIO if device does not support PCI PM or its PM capabilities register has a
|
|
+ * wrong version, or device doesn't support the requested state.
|
|
+ * 0 if the transition is to D1 or D2 but D1 and D2 are not supported.
|
|
+ * 0 if device already is in the requested state.
|
|
+ * 0 if the transition is to D3 but D3 is not supported.
|
|
+ * 0 if device's power state has been successfully changed.
|
|
+ */
|
|
+int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
|
+{
|
|
+ return __pci_set_power_state(dev, state, false);
|
|
+}
|
|
EXPORT_SYMBOL(pci_set_power_state);
|
|
|
|
+int pci_set_power_state_locked(struct pci_dev *dev, pci_power_t state)
|
|
+{
|
|
+ lockdep_assert_held(&pci_bus_sem);
|
|
+
|
|
+ return __pci_set_power_state(dev, state, true);
|
|
+}
|
|
+EXPORT_SYMBOL(pci_set_power_state_locked);
|
|
+
|
|
#define PCI_EXP_SAVE_REGS 7
|
|
|
|
static struct pci_cap_saved_state *_pci_find_saved_cap(struct pci_dev *pci_dev,
|
|
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
|
|
index 1b4f941829724..2cc032e8cbb92 100644
|
|
--- a/drivers/pci/pci.h
|
|
+++ b/drivers/pci/pci.h
|
|
@@ -561,12 +561,12 @@ int pcie_retrain_link(struct pci_dev *pdev, bool use_lt);
|
|
#ifdef CONFIG_PCIEASPM
|
|
void pcie_aspm_init_link_state(struct pci_dev *pdev);
|
|
void pcie_aspm_exit_link_state(struct pci_dev *pdev);
|
|
-void pcie_aspm_pm_state_change(struct pci_dev *pdev);
|
|
+void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked);
|
|
void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
|
|
#else
|
|
static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { }
|
|
static inline void pcie_aspm_exit_link_state(struct pci_dev *pdev) { }
|
|
-static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev) { }
|
|
+static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked) { }
|
|
static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
|
|
#endif
|
|
|
|
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
|
|
index 7e3b342215e5b..0aef6dc055b92 100644
|
|
--- a/drivers/pci/pcie/aspm.c
|
|
+++ b/drivers/pci/pcie/aspm.c
|
|
@@ -689,10 +689,10 @@ static void pcie_config_aspm_l1ss(struct pcie_link_state *link, u32 state)
|
|
* in pcie_config_aspm_link().
|
|
*/
|
|
if (enable_req & (ASPM_STATE_L1_1 | ASPM_STATE_L1_2)) {
|
|
- pcie_capability_clear_and_set_word(child, PCI_EXP_LNKCTL,
|
|
- PCI_EXP_LNKCTL_ASPM_L1, 0);
|
|
- pcie_capability_clear_and_set_word(parent, PCI_EXP_LNKCTL,
|
|
- PCI_EXP_LNKCTL_ASPM_L1, 0);
|
|
+ pcie_capability_clear_word(child, PCI_EXP_LNKCTL,
|
|
+ PCI_EXP_LNKCTL_ASPM_L1);
|
|
+ pcie_capability_clear_word(parent, PCI_EXP_LNKCTL,
|
|
+ PCI_EXP_LNKCTL_ASPM_L1);
|
|
}
|
|
|
|
val = 0;
|
|
@@ -1001,8 +1001,11 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev)
|
|
up_read(&pci_bus_sem);
|
|
}
|
|
|
|
-/* @pdev: the root port or switch downstream port */
|
|
-void pcie_aspm_pm_state_change(struct pci_dev *pdev)
|
|
+/*
|
|
+ * @pdev: the root port or switch downstream port
|
|
+ * @locked: whether pci_bus_sem is held
|
|
+ */
|
|
+void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked)
|
|
{
|
|
struct pcie_link_state *link = pdev->link_state;
|
|
|
|
@@ -1012,12 +1015,14 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev)
|
|
* Devices changed PM state, we should recheck if latency
|
|
* meets all functions' requirement
|
|
*/
|
|
- down_read(&pci_bus_sem);
|
|
+ if (!locked)
|
|
+ down_read(&pci_bus_sem);
|
|
mutex_lock(&aspm_lock);
|
|
pcie_update_aspm_capable(link->root);
|
|
pcie_config_aspm_path(link);
|
|
mutex_unlock(&aspm_lock);
|
|
- up_read(&pci_bus_sem);
|
|
+ if (!locked)
|
|
+ up_read(&pci_bus_sem);
|
|
}
|
|
|
|
void pcie_aspm_powersave_config_link(struct pci_dev *pdev)
|
|
diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c
|
|
index b4818007788f9..a5cec2a4e057d 100644
|
|
--- a/drivers/pci/pcie/dpc.c
|
|
+++ b/drivers/pci/pcie/dpc.c
|
|
@@ -9,6 +9,7 @@
|
|
#define dev_fmt(fmt) "DPC: " fmt
|
|
|
|
#include <linux/aer.h>
|
|
+#include <linux/bitfield.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/init.h>
|
|
@@ -202,7 +203,7 @@ static void dpc_process_rp_pio_error(struct pci_dev *pdev)
|
|
|
|
/* Get First Error Pointer */
|
|
pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &dpc_status);
|
|
- first_error = (dpc_status & 0x1f00) >> 8;
|
|
+ first_error = FIELD_GET(PCI_EXP_DPC_RP_PIO_FEP, dpc_status);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rp_pio_error_string); i++) {
|
|
if ((status & ~mask) & (1 << i))
|
|
@@ -338,7 +339,7 @@ void pci_dpc_init(struct pci_dev *pdev)
|
|
/* Quirks may set dpc_rp_log_size if device or firmware is buggy */
|
|
if (!pdev->dpc_rp_log_size) {
|
|
pdev->dpc_rp_log_size =
|
|
- (cap & PCI_EXP_DPC_RP_PIO_LOG_SIZE) >> 8;
|
|
+ FIELD_GET(PCI_EXP_DPC_RP_PIO_LOG_SIZE, cap);
|
|
if (pdev->dpc_rp_log_size < 4 || pdev->dpc_rp_log_size > 9) {
|
|
pci_err(pdev, "RP PIO log size %u is invalid\n",
|
|
pdev->dpc_rp_log_size);
|
|
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
|
|
index b3976dcb71f10..ec4277d7835b2 100644
|
|
--- a/drivers/pci/quirks.c
|
|
+++ b/drivers/pci/quirks.c
|
|
@@ -4571,9 +4571,9 @@ static void quirk_disable_root_port_attributes(struct pci_dev *pdev)
|
|
|
|
pci_info(root_port, "Disabling No Snoop/Relaxed Ordering Attributes to avoid PCIe Completion erratum in %s\n",
|
|
dev_name(&pdev->dev));
|
|
- pcie_capability_clear_and_set_word(root_port, PCI_EXP_DEVCTL,
|
|
- PCI_EXP_DEVCTL_RELAX_EN |
|
|
- PCI_EXP_DEVCTL_NOSNOOP_EN, 0);
|
|
+ pcie_capability_clear_word(root_port, PCI_EXP_DEVCTL,
|
|
+ PCI_EXP_DEVCTL_RELAX_EN |
|
|
+ PCI_EXP_DEVCTL_NOSNOOP_EN);
|
|
}
|
|
|
|
/*
|
|
@@ -6198,7 +6198,7 @@ static void dpc_log_size(struct pci_dev *dev)
|
|
if (!(val & PCI_EXP_DPC_CAP_RP_EXT))
|
|
return;
|
|
|
|
- if (!((val & PCI_EXP_DPC_RP_PIO_LOG_SIZE) >> 8)) {
|
|
+ if (FIELD_GET(PCI_EXP_DPC_RP_PIO_LOG_SIZE, val) == 0) {
|
|
pci_info(dev, "Overriding RP PIO Log Size to 4\n");
|
|
dev->dpc_rp_log_size = 4;
|
|
}
|
|
diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
|
|
index b456370166b6b..b4f49720c87f6 100644
|
|
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
|
|
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
|
|
@@ -208,6 +208,15 @@ static const struct dmi_system_id fwbug_list[] = {
|
|
DMI_MATCH(DMI_BIOS_VERSION, "03.03"),
|
|
}
|
|
},
|
|
+ {
|
|
+ .ident = "Framework Laptop 13 (Phoenix)",
|
|
+ .driver_data = &quirk_spurious_8042,
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "Framework"),
|
|
+ DMI_MATCH(DMI_PRODUCT_NAME, "Laptop 13 (AMD Ryzen 7040Series)"),
|
|
+ DMI_MATCH(DMI_BIOS_VERSION, "03.05"),
|
|
+ }
|
|
+ },
|
|
{}
|
|
};
|
|
|
|
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c
|
|
index 4ca5adce91079..57e0050dbaa53 100644
|
|
--- a/drivers/s390/cio/device.c
|
|
+++ b/drivers/s390/cio/device.c
|
|
@@ -363,10 +363,8 @@ int ccw_device_set_online(struct ccw_device *cdev)
|
|
|
|
spin_lock_irq(cdev->ccwlock);
|
|
ret = ccw_device_online(cdev);
|
|
- spin_unlock_irq(cdev->ccwlock);
|
|
- if (ret == 0)
|
|
- wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
|
|
- else {
|
|
+ if (ret) {
|
|
+ spin_unlock_irq(cdev->ccwlock);
|
|
CIO_MSG_EVENT(0, "ccw_device_online returned %d, "
|
|
"device 0.%x.%04x\n",
|
|
ret, cdev->private->dev_id.ssid,
|
|
@@ -375,7 +373,12 @@ int ccw_device_set_online(struct ccw_device *cdev)
|
|
put_device(&cdev->dev);
|
|
return ret;
|
|
}
|
|
- spin_lock_irq(cdev->ccwlock);
|
|
+ /* Wait until a final state is reached */
|
|
+ while (!dev_fsm_final_state(cdev)) {
|
|
+ spin_unlock_irq(cdev->ccwlock);
|
|
+ wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
|
|
+ spin_lock_irq(cdev->ccwlock);
|
|
+ }
|
|
/* Check if online processing was successful */
|
|
if ((cdev->private->state != DEV_STATE_ONLINE) &&
|
|
(cdev->private->state != DEV_STATE_W4SENSE)) {
|
|
diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c
|
|
index 9cde55730b65a..ebcb535809882 100644
|
|
--- a/drivers/s390/cio/qdio_main.c
|
|
+++ b/drivers/s390/cio/qdio_main.c
|
|
@@ -722,8 +722,8 @@ static void qdio_handle_activate_check(struct qdio_irq *irq_ptr,
|
|
lgr_info_log();
|
|
}
|
|
|
|
-static void qdio_establish_handle_irq(struct qdio_irq *irq_ptr, int cstat,
|
|
- int dstat)
|
|
+static int qdio_establish_handle_irq(struct qdio_irq *irq_ptr, int cstat,
|
|
+ int dstat, int dcc)
|
|
{
|
|
DBF_DEV_EVENT(DBF_INFO, irq_ptr, "qest irq");
|
|
|
|
@@ -731,15 +731,18 @@ static void qdio_establish_handle_irq(struct qdio_irq *irq_ptr, int cstat,
|
|
goto error;
|
|
if (dstat & ~(DEV_STAT_DEV_END | DEV_STAT_CHN_END))
|
|
goto error;
|
|
+ if (dcc == 1)
|
|
+ return -EAGAIN;
|
|
if (!(dstat & DEV_STAT_DEV_END))
|
|
goto error;
|
|
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ESTABLISHED);
|
|
- return;
|
|
+ return 0;
|
|
|
|
error:
|
|
DBF_ERROR("%4x EQ:error", irq_ptr->schid.sch_no);
|
|
DBF_ERROR("ds: %2x cs:%2x", dstat, cstat);
|
|
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR);
|
|
+ return -EIO;
|
|
}
|
|
|
|
/* qdio interrupt handler */
|
|
@@ -748,7 +751,7 @@ void qdio_int_handler(struct ccw_device *cdev, unsigned long intparm,
|
|
{
|
|
struct qdio_irq *irq_ptr = cdev->private->qdio_data;
|
|
struct subchannel_id schid;
|
|
- int cstat, dstat;
|
|
+ int cstat, dstat, rc, dcc;
|
|
|
|
if (!intparm || !irq_ptr) {
|
|
ccw_device_get_schid(cdev, &schid);
|
|
@@ -768,10 +771,12 @@ void qdio_int_handler(struct ccw_device *cdev, unsigned long intparm,
|
|
qdio_irq_check_sense(irq_ptr, irb);
|
|
cstat = irb->scsw.cmd.cstat;
|
|
dstat = irb->scsw.cmd.dstat;
|
|
+ dcc = scsw_cmd_is_valid_cc(&irb->scsw) ? irb->scsw.cmd.cc : 0;
|
|
+ rc = 0;
|
|
|
|
switch (irq_ptr->state) {
|
|
case QDIO_IRQ_STATE_INACTIVE:
|
|
- qdio_establish_handle_irq(irq_ptr, cstat, dstat);
|
|
+ rc = qdio_establish_handle_irq(irq_ptr, cstat, dstat, dcc);
|
|
break;
|
|
case QDIO_IRQ_STATE_CLEANUP:
|
|
qdio_set_state(irq_ptr, QDIO_IRQ_STATE_INACTIVE);
|
|
@@ -785,12 +790,25 @@ void qdio_int_handler(struct ccw_device *cdev, unsigned long intparm,
|
|
if (cstat || dstat)
|
|
qdio_handle_activate_check(irq_ptr, intparm, cstat,
|
|
dstat);
|
|
+ else if (dcc == 1)
|
|
+ rc = -EAGAIN;
|
|
break;
|
|
case QDIO_IRQ_STATE_STOPPED:
|
|
break;
|
|
default:
|
|
WARN_ON_ONCE(1);
|
|
}
|
|
+
|
|
+ if (rc == -EAGAIN) {
|
|
+ DBF_DEV_EVENT(DBF_INFO, irq_ptr, "qint retry");
|
|
+ rc = ccw_device_start(cdev, irq_ptr->ccw, intparm, 0, 0);
|
|
+ if (!rc)
|
|
+ return;
|
|
+ DBF_ERROR("%4x RETRY ERR", irq_ptr->schid.sch_no);
|
|
+ DBF_ERROR("rc:%4x", rc);
|
|
+ qdio_set_state(irq_ptr, QDIO_IRQ_STATE_ERR);
|
|
+ }
|
|
+
|
|
wake_up(&cdev->private->wait_q);
|
|
}
|
|
|
|
diff --git a/drivers/s390/net/ism_drv.c b/drivers/s390/net/ism_drv.c
|
|
index 81aabbfbbe2ca..622a61f8a3b84 100644
|
|
--- a/drivers/s390/net/ism_drv.c
|
|
+++ b/drivers/s390/net/ism_drv.c
|
|
@@ -291,13 +291,16 @@ static int ism_read_local_gid(struct ism_dev *ism)
|
|
static void ism_free_dmb(struct ism_dev *ism, struct ism_dmb *dmb)
|
|
{
|
|
clear_bit(dmb->sba_idx, ism->sba_bitmap);
|
|
- dma_free_coherent(&ism->pdev->dev, dmb->dmb_len,
|
|
- dmb->cpu_addr, dmb->dma_addr);
|
|
+ dma_unmap_page(&ism->pdev->dev, dmb->dma_addr, dmb->dmb_len,
|
|
+ DMA_FROM_DEVICE);
|
|
+ folio_put(virt_to_folio(dmb->cpu_addr));
|
|
}
|
|
|
|
static int ism_alloc_dmb(struct ism_dev *ism, struct ism_dmb *dmb)
|
|
{
|
|
+ struct folio *folio;
|
|
unsigned long bit;
|
|
+ int rc;
|
|
|
|
if (PAGE_ALIGN(dmb->dmb_len) > dma_get_max_seg_size(&ism->pdev->dev))
|
|
return -EINVAL;
|
|
@@ -314,14 +317,30 @@ static int ism_alloc_dmb(struct ism_dev *ism, struct ism_dmb *dmb)
|
|
test_and_set_bit(dmb->sba_idx, ism->sba_bitmap))
|
|
return -EINVAL;
|
|
|
|
- dmb->cpu_addr = dma_alloc_coherent(&ism->pdev->dev, dmb->dmb_len,
|
|
- &dmb->dma_addr,
|
|
- GFP_KERNEL | __GFP_NOWARN |
|
|
- __GFP_NOMEMALLOC | __GFP_NORETRY);
|
|
- if (!dmb->cpu_addr)
|
|
- clear_bit(dmb->sba_idx, ism->sba_bitmap);
|
|
+ folio = folio_alloc(GFP_KERNEL | __GFP_NOWARN | __GFP_NOMEMALLOC |
|
|
+ __GFP_NORETRY, get_order(dmb->dmb_len));
|
|
|
|
- return dmb->cpu_addr ? 0 : -ENOMEM;
|
|
+ if (!folio) {
|
|
+ rc = -ENOMEM;
|
|
+ goto out_bit;
|
|
+ }
|
|
+
|
|
+ dmb->cpu_addr = folio_address(folio);
|
|
+ dmb->dma_addr = dma_map_page(&ism->pdev->dev,
|
|
+ virt_to_page(dmb->cpu_addr), 0,
|
|
+ dmb->dmb_len, DMA_FROM_DEVICE);
|
|
+ if (dma_mapping_error(&ism->pdev->dev, dmb->dma_addr)) {
|
|
+ rc = -ENOMEM;
|
|
+ goto out_free;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+out_free:
|
|
+ kfree(dmb->cpu_addr);
|
|
+out_bit:
|
|
+ clear_bit(dmb->sba_idx, ism->sba_bitmap);
|
|
+ return rc;
|
|
}
|
|
|
|
int ism_register_dmb(struct ism_dev *ism, struct ism_dmb *dmb,
|
|
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
|
|
index 552809bca3507..97def2619ecf2 100644
|
|
--- a/drivers/scsi/scsi_lib.c
|
|
+++ b/drivers/scsi/scsi_lib.c
|
|
@@ -543,10 +543,9 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
|
|
if (blk_queue_add_random(q))
|
|
add_disk_randomness(req->q->disk);
|
|
|
|
- if (!blk_rq_is_passthrough(req)) {
|
|
- WARN_ON_ONCE(!(cmd->flags & SCMD_INITIALIZED));
|
|
- cmd->flags &= ~SCMD_INITIALIZED;
|
|
- }
|
|
+ WARN_ON_ONCE(!blk_rq_is_passthrough(req) &&
|
|
+ !(cmd->flags & SCMD_INITIALIZED));
|
|
+ cmd->flags = 0;
|
|
|
|
/*
|
|
* Calling rcu_barrier() is not necessary here because the
|
|
diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c
|
|
index ec7b5f65804e4..31f3da4e6a08d 100644
|
|
--- a/drivers/thunderbolt/domain.c
|
|
+++ b/drivers/thunderbolt/domain.c
|
|
@@ -423,6 +423,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
|
|
/**
|
|
* tb_domain_add() - Add domain to the system
|
|
* @tb: Domain to add
|
|
+ * @reset: Issue reset to the host router
|
|
*
|
|
* Starts the domain and adds it to the system. Hotplugging devices will
|
|
* work after this has been returned successfully. In order to remove
|
|
@@ -431,7 +432,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize
|
|
*
|
|
* Return: %0 in case of success and negative errno in case of error
|
|
*/
|
|
-int tb_domain_add(struct tb *tb)
|
|
+int tb_domain_add(struct tb *tb, bool reset)
|
|
{
|
|
int ret;
|
|
|
|
@@ -460,7 +461,7 @@ int tb_domain_add(struct tb *tb)
|
|
|
|
/* Start the domain */
|
|
if (tb->cm_ops->start) {
|
|
- ret = tb->cm_ops->start(tb);
|
|
+ ret = tb->cm_ops->start(tb, reset);
|
|
if (ret)
|
|
goto err_domain_del;
|
|
}
|
|
diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c
|
|
index d8b9c734abd36..623aa81a88337 100644
|
|
--- a/drivers/thunderbolt/icm.c
|
|
+++ b/drivers/thunderbolt/icm.c
|
|
@@ -2144,7 +2144,7 @@ static int icm_runtime_resume(struct tb *tb)
|
|
return 0;
|
|
}
|
|
|
|
-static int icm_start(struct tb *tb)
|
|
+static int icm_start(struct tb *tb, bool not_used)
|
|
{
|
|
struct icm *icm = tb_priv(tb);
|
|
int ret;
|
|
diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c
|
|
index 633970fbe9b05..63cb4b6afb718 100644
|
|
--- a/drivers/thunderbolt/lc.c
|
|
+++ b/drivers/thunderbolt/lc.c
|
|
@@ -6,6 +6,8 @@
|
|
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
|
*/
|
|
|
|
+#include <linux/delay.h>
|
|
+
|
|
#include "tb.h"
|
|
|
|
/**
|
|
@@ -45,6 +47,49 @@ static int find_port_lc_cap(struct tb_port *port)
|
|
return sw->cap_lc + start + phys * size;
|
|
}
|
|
|
|
+/**
|
|
+ * tb_lc_reset_port() - Trigger downstream port reset through LC
|
|
+ * @port: Port that is reset
|
|
+ *
|
|
+ * Triggers downstream port reset through link controller registers.
|
|
+ * Returns %0 in case of success negative errno otherwise. Only supports
|
|
+ * non-USB4 routers with link controller (that's Thunderbolt 2 and
|
|
+ * Thunderbolt 3).
|
|
+ */
|
|
+int tb_lc_reset_port(struct tb_port *port)
|
|
+{
|
|
+ struct tb_switch *sw = port->sw;
|
|
+ int cap, ret;
|
|
+ u32 mode;
|
|
+
|
|
+ if (sw->generation < 2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ cap = find_port_lc_cap(port);
|
|
+ if (cap < 0)
|
|
+ return cap;
|
|
+
|
|
+ ret = tb_sw_read(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mode |= TB_LC_PORT_MODE_DPR;
|
|
+
|
|
+ ret = tb_sw_write(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ fsleep(10000);
|
|
+
|
|
+ ret = tb_sw_read(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ mode &= ~TB_LC_PORT_MODE_DPR;
|
|
+
|
|
+ return tb_sw_write(sw, &mode, TB_CFG_SWITCH, cap + TB_LC_PORT_MODE, 1);
|
|
+}
|
|
+
|
|
static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
|
|
{
|
|
bool upstream = tb_is_upstream_port(port);
|
|
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
|
|
index 4b7bec74e89fb..1ec6f9c82aef0 100644
|
|
--- a/drivers/thunderbolt/nhi.c
|
|
+++ b/drivers/thunderbolt/nhi.c
|
|
@@ -1221,7 +1221,7 @@ static void nhi_check_iommu(struct tb_nhi *nhi)
|
|
str_enabled_disabled(port_ok));
|
|
}
|
|
|
|
-static void nhi_reset(struct tb_nhi *nhi)
|
|
+static bool nhi_reset(struct tb_nhi *nhi)
|
|
{
|
|
ktime_t timeout;
|
|
u32 val;
|
|
@@ -1229,11 +1229,11 @@ static void nhi_reset(struct tb_nhi *nhi)
|
|
val = ioread32(nhi->iobase + REG_CAPS);
|
|
/* Reset only v2 and later routers */
|
|
if (FIELD_GET(REG_CAPS_VERSION_MASK, val) < REG_CAPS_VERSION_2)
|
|
- return;
|
|
+ return false;
|
|
|
|
if (!host_reset) {
|
|
dev_dbg(&nhi->pdev->dev, "skipping host router reset\n");
|
|
- return;
|
|
+ return false;
|
|
}
|
|
|
|
iowrite32(REG_RESET_HRR, nhi->iobase + REG_RESET);
|
|
@@ -1244,12 +1244,14 @@ static void nhi_reset(struct tb_nhi *nhi)
|
|
val = ioread32(nhi->iobase + REG_RESET);
|
|
if (!(val & REG_RESET_HRR)) {
|
|
dev_warn(&nhi->pdev->dev, "host router reset successful\n");
|
|
- return;
|
|
+ return true;
|
|
}
|
|
usleep_range(10, 20);
|
|
} while (ktime_before(ktime_get(), timeout));
|
|
|
|
dev_warn(&nhi->pdev->dev, "timeout resetting host router\n");
|
|
+
|
|
+ return false;
|
|
}
|
|
|
|
static int nhi_init_msi(struct tb_nhi *nhi)
|
|
@@ -1331,6 +1333,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
struct device *dev = &pdev->dev;
|
|
struct tb_nhi *nhi;
|
|
struct tb *tb;
|
|
+ bool reset;
|
|
int res;
|
|
|
|
if (!nhi_imr_valid(pdev))
|
|
@@ -1365,7 +1368,11 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
nhi_check_quirks(nhi);
|
|
nhi_check_iommu(nhi);
|
|
|
|
- nhi_reset(nhi);
|
|
+ /*
|
|
+ * Only USB4 v2 hosts support host reset so if we already did
|
|
+ * that then don't do it again when the domain is initialized.
|
|
+ */
|
|
+ reset = nhi_reset(nhi) ? false : host_reset;
|
|
|
|
res = nhi_init_msi(nhi);
|
|
if (res)
|
|
@@ -1392,7 +1399,7 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
|
|
dev_dbg(dev, "NHI initialized, starting thunderbolt\n");
|
|
|
|
- res = tb_domain_add(tb);
|
|
+ res = tb_domain_add(tb, reset);
|
|
if (res) {
|
|
/*
|
|
* At this point the RX/TX rings might already have been
|
|
diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c
|
|
index ee03fd75a4728..17f7647e6a722 100644
|
|
--- a/drivers/thunderbolt/path.c
|
|
+++ b/drivers/thunderbolt/path.c
|
|
@@ -446,6 +446,19 @@ static int __tb_path_deactivate_hop(struct tb_port *port, int hop_index,
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
+/**
|
|
+ * tb_path_deactivate_hop() - Deactivate one path in path config space
|
|
+ * @port: Lane or protocol adapter
|
|
+ * @hop_index: HopID of the path to be cleared
|
|
+ *
|
|
+ * This deactivates or clears a single path config space entry at
|
|
+ * @hop_index. Returns %0 in success and negative errno otherwise.
|
|
+ */
|
|
+int tb_path_deactivate_hop(struct tb_port *port, int hop_index)
|
|
+{
|
|
+ return __tb_path_deactivate_hop(port, hop_index, true);
|
|
+}
|
|
+
|
|
static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop)
|
|
{
|
|
int i, res;
|
|
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
|
|
index 509b99af5087b..de0f9cd44c231 100644
|
|
--- a/drivers/thunderbolt/switch.c
|
|
+++ b/drivers/thunderbolt/switch.c
|
|
@@ -675,6 +675,13 @@ int tb_port_disable(struct tb_port *port)
|
|
return __tb_port_enable(port, false);
|
|
}
|
|
|
|
+static int tb_port_reset(struct tb_port *port)
|
|
+{
|
|
+ if (tb_switch_is_usb4(port->sw))
|
|
+ return port->cap_usb4 ? usb4_port_reset(port) : 0;
|
|
+ return tb_lc_reset_port(port);
|
|
+}
|
|
+
|
|
/*
|
|
* tb_init_port() - initialize a port
|
|
*
|
|
@@ -1485,29 +1492,124 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
|
|
regs->__unknown1, regs->__unknown4);
|
|
}
|
|
|
|
+static int tb_switch_reset_host(struct tb_switch *sw)
|
|
+{
|
|
+ if (sw->generation > 1) {
|
|
+ struct tb_port *port;
|
|
+
|
|
+ tb_switch_for_each_port(sw, port) {
|
|
+ int i, ret;
|
|
+
|
|
+ /*
|
|
+ * For lane adapters we issue downstream port
|
|
+ * reset and clear up path config spaces.
|
|
+ *
|
|
+ * For protocol adapters we disable the path and
|
|
+ * clear path config space one by one (from 8 to
|
|
+ * Max Input HopID of the adapter).
|
|
+ */
|
|
+ if (tb_port_is_null(port) && !tb_is_upstream_port(port)) {
|
|
+ ret = tb_port_reset(port);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ } else if (tb_port_is_usb3_down(port) ||
|
|
+ tb_port_is_usb3_up(port)) {
|
|
+ tb_usb3_port_enable(port, false);
|
|
+ } else if (tb_port_is_dpin(port) ||
|
|
+ tb_port_is_dpout(port)) {
|
|
+ tb_dp_port_enable(port, false);
|
|
+ } else if (tb_port_is_pcie_down(port) ||
|
|
+ tb_port_is_pcie_up(port)) {
|
|
+ tb_pci_port_enable(port, false);
|
|
+ } else {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* Cleanup path config space of protocol adapter */
|
|
+ for (i = TB_PATH_MIN_HOPID;
|
|
+ i <= port->config.max_in_hop_id; i++) {
|
|
+ ret = tb_path_deactivate_hop(port, i);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ struct tb_cfg_result res;
|
|
+
|
|
+ /* Thunderbolt 1 uses the "reset" config space packet */
|
|
+ res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
|
|
+ TB_CFG_SWITCH, 2, 2);
|
|
+ if (res.err)
|
|
+ return res.err;
|
|
+ res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
|
|
+ if (res.err > 0)
|
|
+ return -EIO;
|
|
+ else if (res.err < 0)
|
|
+ return res.err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int tb_switch_reset_device(struct tb_switch *sw)
|
|
+{
|
|
+ return tb_port_reset(tb_switch_downstream_port(sw));
|
|
+}
|
|
+
|
|
+static bool tb_switch_enumerated(struct tb_switch *sw)
|
|
+{
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * Read directly from the hardware because we use this also
|
|
+ * during system sleep where sw->config.enabled is already set
|
|
+ * by us.
|
|
+ */
|
|
+ ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_3, 1);
|
|
+ if (ret)
|
|
+ return false;
|
|
+
|
|
+ return !!(val & ROUTER_CS_3_V);
|
|
+}
|
|
+
|
|
/**
|
|
- * tb_switch_reset() - reconfigure route, enable and send TB_CFG_PKG_RESET
|
|
- * @sw: Switch to reset
|
|
+ * tb_switch_reset() - Perform reset to the router
|
|
+ * @sw: Router to reset
|
|
*
|
|
- * Return: Returns 0 on success or an error code on failure.
|
|
+ * Issues reset to the router @sw. Can be used for any router. For host
|
|
+ * routers, resets all the downstream ports and cleans up path config
|
|
+ * spaces accordingly. For device routers issues downstream port reset
|
|
+ * through the parent router, so as side effect there will be unplug
|
|
+ * soon after this is finished.
|
|
+ *
|
|
+ * If the router is not enumerated does nothing.
|
|
+ *
|
|
+ * Returns %0 on success or negative errno in case of failure.
|
|
*/
|
|
int tb_switch_reset(struct tb_switch *sw)
|
|
{
|
|
- struct tb_cfg_result res;
|
|
+ int ret;
|
|
|
|
- if (sw->generation > 1)
|
|
+ /*
|
|
+ * We cannot access the port config spaces unless the router is
|
|
+ * already enumerated. If the router is not enumerated it is
|
|
+ * equal to being reset so we can skip that here.
|
|
+ */
|
|
+ if (!tb_switch_enumerated(sw))
|
|
return 0;
|
|
|
|
- tb_sw_dbg(sw, "resetting switch\n");
|
|
+ tb_sw_dbg(sw, "resetting\n");
|
|
+
|
|
+ if (tb_route(sw))
|
|
+ ret = tb_switch_reset_device(sw);
|
|
+ else
|
|
+ ret = tb_switch_reset_host(sw);
|
|
+
|
|
+ if (ret)
|
|
+ tb_sw_warn(sw, "failed to reset\n");
|
|
|
|
- res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
|
|
- TB_CFG_SWITCH, 2, 2);
|
|
- if (res.err)
|
|
- return res.err;
|
|
- res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
|
|
- if (res.err > 0)
|
|
- return -EIO;
|
|
- return res.err;
|
|
+ return ret;
|
|
}
|
|
|
|
/**
|
|
@@ -2847,22 +2949,29 @@ void tb_switch_unconfigure_link(struct tb_switch *sw)
|
|
{
|
|
struct tb_port *up, *down;
|
|
|
|
- if (sw->is_unplugged)
|
|
- return;
|
|
if (!tb_route(sw) || tb_switch_is_icm(sw))
|
|
return;
|
|
|
|
+ /*
|
|
+ * Unconfigure downstream port so that wake-on-connect can be
|
|
+ * configured after router unplug. No need to unconfigure upstream port
|
|
+ * since its router is unplugged.
|
|
+ */
|
|
up = tb_upstream_port(sw);
|
|
- if (tb_switch_is_usb4(up->sw))
|
|
- usb4_port_unconfigure(up);
|
|
- else
|
|
- tb_lc_unconfigure_port(up);
|
|
-
|
|
down = up->remote;
|
|
if (tb_switch_is_usb4(down->sw))
|
|
usb4_port_unconfigure(down);
|
|
else
|
|
tb_lc_unconfigure_port(down);
|
|
+
|
|
+ if (sw->is_unplugged)
|
|
+ return;
|
|
+
|
|
+ up = tb_upstream_port(sw);
|
|
+ if (tb_switch_is_usb4(up->sw))
|
|
+ usb4_port_unconfigure(up);
|
|
+ else
|
|
+ tb_lc_unconfigure_port(up);
|
|
}
|
|
|
|
static void tb_switch_credits_init(struct tb_switch *sw)
|
|
@@ -3106,7 +3215,26 @@ static int tb_switch_set_wake(struct tb_switch *sw, unsigned int flags)
|
|
return tb_lc_set_wake(sw, flags);
|
|
}
|
|
|
|
-int tb_switch_resume(struct tb_switch *sw)
|
|
+static void tb_switch_check_wakes(struct tb_switch *sw)
|
|
+{
|
|
+ if (device_may_wakeup(&sw->dev)) {
|
|
+ if (tb_switch_is_usb4(sw))
|
|
+ usb4_switch_check_wakes(sw);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * tb_switch_resume() - Resume a switch after sleep
|
|
+ * @sw: Switch to resume
|
|
+ * @runtime: Is this resume from runtime suspend or system sleep
|
|
+ *
|
|
+ * Resumes and re-enumerates router (and all its children), if still plugged
|
|
+ * after suspend. Don't enumerate device router whose UID was changed during
|
|
+ * suspend. If this is resume from system sleep, notifies PM core about the
|
|
+ * wakes occurred during suspend. Disables all wakes, except USB4 wake of
|
|
+ * upstream port for USB4 routers that shall be always enabled.
|
|
+ */
|
|
+int tb_switch_resume(struct tb_switch *sw, bool runtime)
|
|
{
|
|
struct tb_port *port;
|
|
int err;
|
|
@@ -3155,6 +3283,9 @@ int tb_switch_resume(struct tb_switch *sw)
|
|
if (err)
|
|
return err;
|
|
|
|
+ if (!runtime)
|
|
+ tb_switch_check_wakes(sw);
|
|
+
|
|
/* Disable wakes */
|
|
tb_switch_set_wake(sw, 0);
|
|
|
|
@@ -3184,7 +3315,8 @@ int tb_switch_resume(struct tb_switch *sw)
|
|
*/
|
|
if (tb_port_unlock(port))
|
|
tb_port_warn(port, "failed to unlock port\n");
|
|
- if (port->remote && tb_switch_resume(port->remote->sw)) {
|
|
+ if (port->remote &&
|
|
+ tb_switch_resume(port->remote->sw, runtime)) {
|
|
tb_port_warn(port,
|
|
"lost during suspend, disconnecting\n");
|
|
tb_sw_set_unplugged(port->remote->sw);
|
|
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
|
|
index ecfb5714e822d..c5e10c1d4c383 100644
|
|
--- a/drivers/thunderbolt/tb.c
|
|
+++ b/drivers/thunderbolt/tb.c
|
|
@@ -2131,7 +2131,7 @@ static int tb_scan_finalize_switch(struct device *dev, void *data)
|
|
return 0;
|
|
}
|
|
|
|
-static int tb_start(struct tb *tb)
|
|
+static int tb_start(struct tb *tb, bool reset)
|
|
{
|
|
struct tb_cm *tcm = tb_priv(tb);
|
|
int ret;
|
|
@@ -2172,12 +2172,24 @@ static int tb_start(struct tb *tb)
|
|
tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_MODE_LOWRES);
|
|
/* Enable TMU if it is off */
|
|
tb_switch_tmu_enable(tb->root_switch);
|
|
- /* Full scan to discover devices added before the driver was loaded. */
|
|
- tb_scan_switch(tb->root_switch);
|
|
- /* Find out tunnels created by the boot firmware */
|
|
- tb_discover_tunnels(tb);
|
|
- /* Add DP resources from the DP tunnels created by the boot firmware */
|
|
- tb_discover_dp_resources(tb);
|
|
+
|
|
+ /*
|
|
+ * Boot firmware might have created tunnels of its own. Since we
|
|
+ * cannot be sure they are usable for us, tear them down and
|
|
+ * reset the ports to handle it as new hotplug for USB4 v1
|
|
+ * routers (for USB4 v2 and beyond we already do host reset).
|
|
+ */
|
|
+ if (reset && usb4_switch_version(tb->root_switch) == 1) {
|
|
+ tb_switch_reset(tb->root_switch);
|
|
+ } else {
|
|
+ /* Full scan to discover devices added before the driver was loaded. */
|
|
+ tb_scan_switch(tb->root_switch);
|
|
+ /* Find out tunnels created by the boot firmware */
|
|
+ tb_discover_tunnels(tb);
|
|
+ /* Add DP resources from the DP tunnels created by the boot firmware */
|
|
+ tb_discover_dp_resources(tb);
|
|
+ }
|
|
+
|
|
/*
|
|
* If the boot firmware did not create USB 3.x tunnels create them
|
|
* now for the whole topology.
|
|
@@ -2247,10 +2259,14 @@ static int tb_resume_noirq(struct tb *tb)
|
|
|
|
tb_dbg(tb, "resuming...\n");
|
|
|
|
- /* remove any pci devices the firmware might have setup */
|
|
- tb_switch_reset(tb->root_switch);
|
|
+ /*
|
|
+ * For non-USB4 hosts (Apple systems) remove any PCIe devices
|
|
+ * the firmware might have setup.
|
|
+ */
|
|
+ if (!tb_switch_is_usb4(tb->root_switch))
|
|
+ tb_switch_reset(tb->root_switch);
|
|
|
|
- tb_switch_resume(tb->root_switch);
|
|
+ tb_switch_resume(tb->root_switch, false);
|
|
tb_free_invalid_tunnels(tb);
|
|
tb_free_unplugged_children(tb->root_switch);
|
|
tb_restore_children(tb->root_switch);
|
|
@@ -2376,7 +2392,7 @@ static int tb_runtime_resume(struct tb *tb)
|
|
struct tb_tunnel *tunnel, *n;
|
|
|
|
mutex_lock(&tb->lock);
|
|
- tb_switch_resume(tb->root_switch);
|
|
+ tb_switch_resume(tb->root_switch, true);
|
|
tb_free_invalid_tunnels(tb);
|
|
tb_restore_children(tb->root_switch);
|
|
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list)
|
|
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
|
|
index 4893d2c7ac968..8a75aabb9ce8e 100644
|
|
--- a/drivers/thunderbolt/tb.h
|
|
+++ b/drivers/thunderbolt/tb.h
|
|
@@ -488,7 +488,7 @@ struct tb_path {
|
|
*/
|
|
struct tb_cm_ops {
|
|
int (*driver_ready)(struct tb *tb);
|
|
- int (*start)(struct tb *tb);
|
|
+ int (*start)(struct tb *tb, bool reset);
|
|
void (*stop)(struct tb *tb);
|
|
int (*suspend_noirq)(struct tb *tb);
|
|
int (*resume_noirq)(struct tb *tb);
|
|
@@ -735,7 +735,7 @@ int tb_xdomain_init(void);
|
|
void tb_xdomain_exit(void);
|
|
|
|
struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize);
|
|
-int tb_domain_add(struct tb *tb);
|
|
+int tb_domain_add(struct tb *tb, bool reset);
|
|
void tb_domain_remove(struct tb *tb);
|
|
int tb_domain_suspend_noirq(struct tb *tb);
|
|
int tb_domain_resume_noirq(struct tb *tb);
|
|
@@ -802,7 +802,7 @@ int tb_switch_configuration_valid(struct tb_switch *sw);
|
|
int tb_switch_add(struct tb_switch *sw);
|
|
void tb_switch_remove(struct tb_switch *sw);
|
|
void tb_switch_suspend(struct tb_switch *sw, bool runtime);
|
|
-int tb_switch_resume(struct tb_switch *sw);
|
|
+int tb_switch_resume(struct tb_switch *sw, bool runtime);
|
|
int tb_switch_reset(struct tb_switch *sw);
|
|
int tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
|
|
u32 value, int timeout_msec);
|
|
@@ -1100,6 +1100,7 @@ struct tb_path *tb_path_alloc(struct tb *tb, struct tb_port *src, int src_hopid,
|
|
void tb_path_free(struct tb_path *path);
|
|
int tb_path_activate(struct tb_path *path);
|
|
void tb_path_deactivate(struct tb_path *path);
|
|
+int tb_path_deactivate_hop(struct tb_port *port, int hop_index);
|
|
bool tb_path_is_invalid(struct tb_path *path);
|
|
bool tb_path_port_on_path(const struct tb_path *path,
|
|
const struct tb_port *port);
|
|
@@ -1119,6 +1120,7 @@ int tb_drom_read(struct tb_switch *sw);
|
|
int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid);
|
|
|
|
int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid);
|
|
+int tb_lc_reset_port(struct tb_port *port);
|
|
int tb_lc_configure_port(struct tb_port *port);
|
|
void tb_lc_unconfigure_port(struct tb_port *port);
|
|
int tb_lc_configure_xdomain(struct tb_port *port);
|
|
@@ -1222,6 +1224,7 @@ static inline bool tb_switch_is_usb4(const struct tb_switch *sw)
|
|
return usb4_switch_version(sw) > 0;
|
|
}
|
|
|
|
+void usb4_switch_check_wakes(struct tb_switch *sw);
|
|
int usb4_switch_setup(struct tb_switch *sw);
|
|
int usb4_switch_configuration_valid(struct tb_switch *sw);
|
|
int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid);
|
|
@@ -1251,6 +1254,7 @@ void usb4_switch_remove_ports(struct tb_switch *sw);
|
|
|
|
int usb4_port_unlock(struct tb_port *port);
|
|
int usb4_port_hotplug_enable(struct tb_port *port);
|
|
+int usb4_port_reset(struct tb_port *port);
|
|
int usb4_port_configure(struct tb_port *port);
|
|
void usb4_port_unconfigure(struct tb_port *port);
|
|
int usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd);
|
|
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
|
|
index 580277dc91152..736e28beac119 100644
|
|
--- a/drivers/thunderbolt/tb_regs.h
|
|
+++ b/drivers/thunderbolt/tb_regs.h
|
|
@@ -194,6 +194,8 @@ struct tb_regs_switch_header {
|
|
#define USB4_VERSION_MAJOR_MASK GENMASK(7, 5)
|
|
|
|
#define ROUTER_CS_1 0x01
|
|
+#define ROUTER_CS_3 0x03
|
|
+#define ROUTER_CS_3_V BIT(31)
|
|
#define ROUTER_CS_4 0x04
|
|
/* Used with the router cmuv field */
|
|
#define ROUTER_CS_4_CMUV_V1 0x10
|
|
@@ -383,6 +385,7 @@ struct tb_regs_port_header {
|
|
#define PORT_CS_18_WODS BIT(17)
|
|
#define PORT_CS_18_WOU4S BIT(18)
|
|
#define PORT_CS_19 0x13
|
|
+#define PORT_CS_19_DPR BIT(0)
|
|
#define PORT_CS_19_PC BIT(3)
|
|
#define PORT_CS_19_PID BIT(4)
|
|
#define PORT_CS_19_WOC BIT(16)
|
|
@@ -579,6 +582,9 @@ struct tb_regs_hop {
|
|
#define TB_LC_POWER 0x740
|
|
|
|
/* Link controller registers */
|
|
+#define TB_LC_PORT_MODE 0x26
|
|
+#define TB_LC_PORT_MODE_DPR BIT(0)
|
|
+
|
|
#define TB_LC_CS_42 0x2a
|
|
#define TB_LC_CS_42_USB_PLUGGED BIT(31)
|
|
|
|
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
|
|
index 13c779e23011b..3aa32d7f9f6a1 100644
|
|
--- a/drivers/thunderbolt/usb4.c
|
|
+++ b/drivers/thunderbolt/usb4.c
|
|
@@ -155,7 +155,13 @@ static inline int usb4_switch_op_data(struct tb_switch *sw, u16 opcode,
|
|
tx_dwords, rx_data, rx_dwords);
|
|
}
|
|
|
|
-static void usb4_switch_check_wakes(struct tb_switch *sw)
|
|
+/**
|
|
+ * usb4_switch_check_wakes() - Check for wakes and notify PM core about them
|
|
+ * @sw: Router whose wakes to check
|
|
+ *
|
|
+ * Checks wakes occurred during suspend and notify the PM core about them.
|
|
+ */
|
|
+void usb4_switch_check_wakes(struct tb_switch *sw)
|
|
{
|
|
bool wakeup_usb4 = false;
|
|
struct usb4_port *usb4;
|
|
@@ -163,9 +169,6 @@ static void usb4_switch_check_wakes(struct tb_switch *sw)
|
|
bool wakeup = false;
|
|
u32 val;
|
|
|
|
- if (!device_may_wakeup(&sw->dev))
|
|
- return;
|
|
-
|
|
if (tb_route(sw)) {
|
|
if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1))
|
|
return;
|
|
@@ -244,8 +247,6 @@ int usb4_switch_setup(struct tb_switch *sw)
|
|
u32 val = 0;
|
|
int ret;
|
|
|
|
- usb4_switch_check_wakes(sw);
|
|
-
|
|
if (!tb_route(sw))
|
|
return 0;
|
|
|
|
@@ -1113,6 +1114,45 @@ int usb4_port_hotplug_enable(struct tb_port *port)
|
|
return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_5, 1);
|
|
}
|
|
|
|
+/**
|
|
+ * usb4_port_reset() - Issue downstream port reset
|
|
+ * @port: USB4 port to reset
|
|
+ *
|
|
+ * Issues downstream port reset to @port.
|
|
+ */
|
|
+int usb4_port_reset(struct tb_port *port)
|
|
+{
|
|
+ int ret;
|
|
+ u32 val;
|
|
+
|
|
+ if (!port->cap_usb4)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = tb_port_read(port, &val, TB_CFG_PORT,
|
|
+ port->cap_usb4 + PORT_CS_19, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ val |= PORT_CS_19_DPR;
|
|
+
|
|
+ ret = tb_port_write(port, &val, TB_CFG_PORT,
|
|
+ port->cap_usb4 + PORT_CS_19, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ fsleep(10000);
|
|
+
|
|
+ ret = tb_port_read(port, &val, TB_CFG_PORT,
|
|
+ port->cap_usb4 + PORT_CS_19, 1);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ val &= ~PORT_CS_19_DPR;
|
|
+
|
|
+ return tb_port_write(port, &val, TB_CFG_PORT,
|
|
+ port->cap_usb4 + PORT_CS_19, 1);
|
|
+}
|
|
+
|
|
static int usb4_port_set_configured(struct tb_port *port, bool configured)
|
|
{
|
|
int ret;
|
|
diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c
|
|
index 380a8b0590e34..777dc8a0aa835 100644
|
|
--- a/drivers/tty/serial/mxs-auart.c
|
|
+++ b/drivers/tty/serial/mxs-auart.c
|
|
@@ -1080,11 +1080,13 @@ static void mxs_auart_set_ldisc(struct uart_port *port,
|
|
|
|
static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
|
|
{
|
|
- u32 istat;
|
|
+ u32 istat, stat;
|
|
struct mxs_auart_port *s = context;
|
|
u32 mctrl_temp = s->mctrl_prev;
|
|
- u32 stat = mxs_read(s, REG_STAT);
|
|
|
|
+ uart_port_lock(&s->port);
|
|
+
|
|
+ stat = mxs_read(s, REG_STAT);
|
|
istat = mxs_read(s, REG_INTR);
|
|
|
|
/* ack irq */
|
|
@@ -1120,6 +1122,8 @@ static irqreturn_t mxs_auart_irq_handle(int irq, void *context)
|
|
istat &= ~AUART_INTR_TXIS;
|
|
}
|
|
|
|
+ uart_port_unlock(&s->port);
|
|
+
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
diff --git a/drivers/tty/serial/pmac_zilog.c b/drivers/tty/serial/pmac_zilog.c
|
|
index 13668ffdb1e7d..29bc80d39e8b7 100644
|
|
--- a/drivers/tty/serial/pmac_zilog.c
|
|
+++ b/drivers/tty/serial/pmac_zilog.c
|
|
@@ -210,7 +210,6 @@ static bool pmz_receive_chars(struct uart_pmac_port *uap)
|
|
{
|
|
struct tty_port *port;
|
|
unsigned char ch, r1, drop, flag;
|
|
- int loops = 0;
|
|
|
|
/* Sanity check, make sure the old bug is no longer happening */
|
|
if (uap->port.state == NULL) {
|
|
@@ -291,24 +290,11 @@ static bool pmz_receive_chars(struct uart_pmac_port *uap)
|
|
if (r1 & Rx_OVR)
|
|
tty_insert_flip_char(port, 0, TTY_OVERRUN);
|
|
next_char:
|
|
- /* We can get stuck in an infinite loop getting char 0 when the
|
|
- * line is in a wrong HW state, we break that here.
|
|
- * When that happens, I disable the receive side of the driver.
|
|
- * Note that what I've been experiencing is a real irq loop where
|
|
- * I'm getting flooded regardless of the actual port speed.
|
|
- * Something strange is going on with the HW
|
|
- */
|
|
- if ((++loops) > 1000)
|
|
- goto flood;
|
|
ch = read_zsreg(uap, R0);
|
|
if (!(ch & Rx_CH_AV))
|
|
break;
|
|
}
|
|
|
|
- return true;
|
|
- flood:
|
|
- pmz_interrupt_control(uap, 0);
|
|
- pmz_error("pmz: rx irq flood !\n");
|
|
return true;
|
|
}
|
|
|
|
diff --git a/drivers/tty/serial/serial_base.h b/drivers/tty/serial/serial_base.h
|
|
index c74c548f0db62..b6c38d2edfd40 100644
|
|
--- a/drivers/tty/serial/serial_base.h
|
|
+++ b/drivers/tty/serial/serial_base.h
|
|
@@ -22,6 +22,7 @@ struct serial_ctrl_device {
|
|
struct serial_port_device {
|
|
struct device dev;
|
|
struct uart_port *port;
|
|
+ unsigned int tx_enabled:1;
|
|
};
|
|
|
|
int serial_base_ctrl_init(void);
|
|
@@ -30,6 +31,9 @@ void serial_base_ctrl_exit(void);
|
|
int serial_base_port_init(void);
|
|
void serial_base_port_exit(void);
|
|
|
|
+void serial_base_port_startup(struct uart_port *port);
|
|
+void serial_base_port_shutdown(struct uart_port *port);
|
|
+
|
|
int serial_base_driver_register(struct device_driver *driver);
|
|
void serial_base_driver_unregister(struct device_driver *driver);
|
|
|
|
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
|
|
index 4c81210ad9b3a..2eceef54e0b30 100644
|
|
--- a/drivers/tty/serial/serial_core.c
|
|
+++ b/drivers/tty/serial/serial_core.c
|
|
@@ -323,16 +323,26 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state,
|
|
bool init_hw)
|
|
{
|
|
struct tty_port *port = &state->port;
|
|
+ struct uart_port *uport;
|
|
int retval;
|
|
|
|
if (tty_port_initialized(port))
|
|
- return 0;
|
|
+ goto out_base_port_startup;
|
|
|
|
retval = uart_port_startup(tty, state, init_hw);
|
|
- if (retval)
|
|
+ if (retval) {
|
|
set_bit(TTY_IO_ERROR, &tty->flags);
|
|
+ return retval;
|
|
+ }
|
|
|
|
- return retval;
|
|
+out_base_port_startup:
|
|
+ uport = uart_port_check(state);
|
|
+ if (!uport)
|
|
+ return -EIO;
|
|
+
|
|
+ serial_base_port_startup(uport);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
/*
|
|
@@ -355,6 +365,9 @@ static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
|
|
if (tty)
|
|
set_bit(TTY_IO_ERROR, &tty->flags);
|
|
|
|
+ if (uport)
|
|
+ serial_base_port_shutdown(uport);
|
|
+
|
|
if (tty_port_initialized(port)) {
|
|
tty_port_set_initialized(port, false);
|
|
|
|
@@ -1769,6 +1782,7 @@ static void uart_tty_port_shutdown(struct tty_port *port)
|
|
uport->ops->stop_rx(uport);
|
|
spin_unlock_irq(&uport->lock);
|
|
|
|
+ serial_base_port_shutdown(uport);
|
|
uart_port_shutdown(port);
|
|
|
|
/*
|
|
@@ -1782,6 +1796,7 @@ static void uart_tty_port_shutdown(struct tty_port *port)
|
|
* Free the transmit buffer.
|
|
*/
|
|
spin_lock_irq(&uport->lock);
|
|
+ uart_circ_clear(&state->xmit);
|
|
buf = state->xmit.buf;
|
|
state->xmit.buf = NULL;
|
|
spin_unlock_irq(&uport->lock);
|
|
diff --git a/drivers/tty/serial/serial_port.c b/drivers/tty/serial/serial_port.c
|
|
index 0b3cef3c323b8..d622a9297f651 100644
|
|
--- a/drivers/tty/serial/serial_port.c
|
|
+++ b/drivers/tty/serial/serial_port.c
|
|
@@ -36,8 +36,12 @@ static int serial_port_runtime_resume(struct device *dev)
|
|
|
|
/* Flush any pending TX for the port */
|
|
spin_lock_irqsave(&port->lock, flags);
|
|
+ if (!port_dev->tx_enabled)
|
|
+ goto unlock;
|
|
if (__serial_port_busy(port))
|
|
port->ops->start_tx(port);
|
|
+
|
|
+unlock:
|
|
spin_unlock_irqrestore(&port->lock, flags);
|
|
|
|
out:
|
|
@@ -57,6 +61,11 @@ static int serial_port_runtime_suspend(struct device *dev)
|
|
return 0;
|
|
|
|
uart_port_lock_irqsave(port, &flags);
|
|
+ if (!port_dev->tx_enabled) {
|
|
+ uart_port_unlock_irqrestore(port, flags);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
busy = __serial_port_busy(port);
|
|
if (busy)
|
|
port->ops->start_tx(port);
|
|
@@ -68,6 +77,31 @@ static int serial_port_runtime_suspend(struct device *dev)
|
|
return busy ? -EBUSY : 0;
|
|
}
|
|
|
|
+static void serial_base_port_set_tx(struct uart_port *port,
|
|
+ struct serial_port_device *port_dev,
|
|
+ bool enabled)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ uart_port_lock_irqsave(port, &flags);
|
|
+ port_dev->tx_enabled = enabled;
|
|
+ uart_port_unlock_irqrestore(port, flags);
|
|
+}
|
|
+
|
|
+void serial_base_port_startup(struct uart_port *port)
|
|
+{
|
|
+ struct serial_port_device *port_dev = port->port_dev;
|
|
+
|
|
+ serial_base_port_set_tx(port, port_dev, true);
|
|
+}
|
|
+
|
|
+void serial_base_port_shutdown(struct uart_port *port)
|
|
+{
|
|
+ struct serial_port_device *port_dev = port->port_dev;
|
|
+
|
|
+ serial_base_port_set_tx(port, port_dev, false);
|
|
+}
|
|
+
|
|
static DEFINE_RUNTIME_DEV_PM_OPS(serial_port_pm,
|
|
serial_port_runtime_suspend,
|
|
serial_port_runtime_resume, NULL);
|
|
diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c
|
|
index e5f933beb6c05..9ef90bb30a47e 100644
|
|
--- a/drivers/tty/serial/stm32-usart.c
|
|
+++ b/drivers/tty/serial/stm32-usart.c
|
|
@@ -857,6 +857,7 @@ static irqreturn_t stm32_usart_interrupt(int irq, void *ptr)
|
|
const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
|
|
u32 sr;
|
|
unsigned int size;
|
|
+ irqreturn_t ret = IRQ_NONE;
|
|
|
|
sr = readl_relaxed(port->membase + ofs->isr);
|
|
|
|
@@ -865,11 +866,14 @@ static irqreturn_t stm32_usart_interrupt(int irq, void *ptr)
|
|
(sr & USART_SR_TC)) {
|
|
stm32_usart_tc_interrupt_disable(port);
|
|
stm32_usart_rs485_rts_disable(port);
|
|
+ ret = IRQ_HANDLED;
|
|
}
|
|
|
|
- if ((sr & USART_SR_RTOF) && ofs->icr != UNDEF_REG)
|
|
+ if ((sr & USART_SR_RTOF) && ofs->icr != UNDEF_REG) {
|
|
writel_relaxed(USART_ICR_RTOCF,
|
|
port->membase + ofs->icr);
|
|
+ ret = IRQ_HANDLED;
|
|
+ }
|
|
|
|
if ((sr & USART_SR_WUF) && ofs->icr != UNDEF_REG) {
|
|
/* Clear wake up flag and disable wake up interrupt */
|
|
@@ -878,6 +882,7 @@ static irqreturn_t stm32_usart_interrupt(int irq, void *ptr)
|
|
stm32_usart_clr_bits(port, ofs->cr3, USART_CR3_WUFIE);
|
|
if (irqd_is_wakeup_set(irq_get_irq_data(port->irq)))
|
|
pm_wakeup_event(tport->tty->dev, 0);
|
|
+ ret = IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
@@ -892,6 +897,7 @@ static irqreturn_t stm32_usart_interrupt(int irq, void *ptr)
|
|
uart_unlock_and_check_sysrq(port);
|
|
if (size)
|
|
tty_flip_buffer_push(tport);
|
|
+ ret = IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
@@ -899,6 +905,7 @@ static irqreturn_t stm32_usart_interrupt(int irq, void *ptr)
|
|
spin_lock(&port->lock);
|
|
stm32_usart_transmit_chars(port);
|
|
spin_unlock(&port->lock);
|
|
+ ret = IRQ_HANDLED;
|
|
}
|
|
|
|
/* Receiver timeout irq for DMA RX */
|
|
@@ -908,9 +915,10 @@ static irqreturn_t stm32_usart_interrupt(int irq, void *ptr)
|
|
uart_unlock_and_check_sysrq(port);
|
|
if (size)
|
|
tty_flip_buffer_push(tport);
|
|
+ ret = IRQ_HANDLED;
|
|
}
|
|
|
|
- return IRQ_HANDLED;
|
|
+ return ret;
|
|
}
|
|
|
|
static void stm32_usart_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
@@ -1069,6 +1077,7 @@ static int stm32_usart_startup(struct uart_port *port)
|
|
val |= USART_CR2_SWAP;
|
|
writel_relaxed(val, port->membase + ofs->cr2);
|
|
}
|
|
+ stm32_port->throttled = false;
|
|
|
|
/* RX FIFO Flush */
|
|
if (ofs->rqr != UNDEF_REG)
|
|
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
|
|
index 0cbe14aca8774..797219db026bc 100644
|
|
--- a/drivers/ufs/host/ufs-qcom.c
|
|
+++ b/drivers/ufs/host/ufs-qcom.c
|
|
@@ -47,7 +47,7 @@ enum {
|
|
TSTBUS_MAX,
|
|
};
|
|
|
|
-#define QCOM_UFS_MAX_GEAR 4
|
|
+#define QCOM_UFS_MAX_GEAR 5
|
|
#define QCOM_UFS_MAX_LANE 2
|
|
|
|
enum {
|
|
@@ -67,26 +67,32 @@ static const struct __ufs_qcom_bw_table {
|
|
[MODE_PWM][UFS_PWM_G2][UFS_LANE_1] = { 1844, 1000 },
|
|
[MODE_PWM][UFS_PWM_G3][UFS_LANE_1] = { 3688, 1000 },
|
|
[MODE_PWM][UFS_PWM_G4][UFS_LANE_1] = { 7376, 1000 },
|
|
+ [MODE_PWM][UFS_PWM_G5][UFS_LANE_1] = { 14752, 1000 },
|
|
[MODE_PWM][UFS_PWM_G1][UFS_LANE_2] = { 1844, 1000 },
|
|
[MODE_PWM][UFS_PWM_G2][UFS_LANE_2] = { 3688, 1000 },
|
|
[MODE_PWM][UFS_PWM_G3][UFS_LANE_2] = { 7376, 1000 },
|
|
[MODE_PWM][UFS_PWM_G4][UFS_LANE_2] = { 14752, 1000 },
|
|
+ [MODE_PWM][UFS_PWM_G5][UFS_LANE_2] = { 29504, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G1][UFS_LANE_1] = { 127796, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G2][UFS_LANE_1] = { 255591, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G3][UFS_LANE_1] = { 1492582, 102400 },
|
|
[MODE_HS_RA][UFS_HS_G4][UFS_LANE_1] = { 2915200, 204800 },
|
|
+ [MODE_HS_RA][UFS_HS_G5][UFS_LANE_1] = { 5836800, 409600 },
|
|
[MODE_HS_RA][UFS_HS_G1][UFS_LANE_2] = { 255591, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G2][UFS_LANE_2] = { 511181, 1000 },
|
|
[MODE_HS_RA][UFS_HS_G3][UFS_LANE_2] = { 1492582, 204800 },
|
|
[MODE_HS_RA][UFS_HS_G4][UFS_LANE_2] = { 2915200, 409600 },
|
|
+ [MODE_HS_RA][UFS_HS_G5][UFS_LANE_2] = { 5836800, 819200 },
|
|
[MODE_HS_RB][UFS_HS_G1][UFS_LANE_1] = { 149422, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G2][UFS_LANE_1] = { 298189, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G3][UFS_LANE_1] = { 1492582, 102400 },
|
|
[MODE_HS_RB][UFS_HS_G4][UFS_LANE_1] = { 2915200, 204800 },
|
|
+ [MODE_HS_RB][UFS_HS_G5][UFS_LANE_1] = { 5836800, 409600 },
|
|
[MODE_HS_RB][UFS_HS_G1][UFS_LANE_2] = { 298189, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G2][UFS_LANE_2] = { 596378, 1000 },
|
|
[MODE_HS_RB][UFS_HS_G3][UFS_LANE_2] = { 1492582, 204800 },
|
|
[MODE_HS_RB][UFS_HS_G4][UFS_LANE_2] = { 2915200, 409600 },
|
|
+ [MODE_HS_RB][UFS_HS_G5][UFS_LANE_2] = { 5836800, 819200 },
|
|
[MODE_MAX][0][0] = { 7643136, 307200 },
|
|
};
|
|
|
|
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
|
|
index c8262e2f29177..c553decb54610 100644
|
|
--- a/drivers/usb/class/cdc-wdm.c
|
|
+++ b/drivers/usb/class/cdc-wdm.c
|
|
@@ -485,7 +485,6 @@ static ssize_t wdm_write
|
|
static int service_outstanding_interrupt(struct wdm_device *desc)
|
|
{
|
|
int rv = 0;
|
|
- int used;
|
|
|
|
/* submit read urb only if the device is waiting for it */
|
|
if (!desc->resp_count || !--desc->resp_count)
|
|
@@ -500,10 +499,7 @@ static int service_outstanding_interrupt(struct wdm_device *desc)
|
|
goto out;
|
|
}
|
|
|
|
- used = test_and_set_bit(WDM_RESPONDING, &desc->flags);
|
|
- if (used)
|
|
- goto out;
|
|
-
|
|
+ set_bit(WDM_RESPONDING, &desc->flags);
|
|
spin_unlock_irq(&desc->iuspin);
|
|
rv = usb_submit_urb(desc->response, GFP_KERNEL);
|
|
spin_lock_irq(&desc->iuspin);
|
|
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
|
|
index 9a3da5d7fe1bc..7417972202b8b 100644
|
|
--- a/drivers/usb/core/hub.c
|
|
+++ b/drivers/usb/core/hub.c
|
|
@@ -60,6 +60,12 @@
|
|
#define USB_PING_RESPONSE_TIME 400 /* ns */
|
|
#define USB_REDUCE_FRAME_INTR_BINTERVAL 9
|
|
|
|
+/*
|
|
+ * The SET_ADDRESS request timeout will be 500 ms when
|
|
+ * USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT quirk flag is set.
|
|
+ */
|
|
+#define USB_SHORT_SET_ADDRESS_REQ_TIMEOUT 500 /* ms */
|
|
+
|
|
/* Protect struct usb_device->state and ->children members
|
|
* Note: Both are also protected by ->dev.sem, except that ->state can
|
|
* change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
|
|
@@ -4663,7 +4669,12 @@ EXPORT_SYMBOL_GPL(usb_ep0_reinit);
|
|
static int hub_set_address(struct usb_device *udev, int devnum)
|
|
{
|
|
int retval;
|
|
+ unsigned int timeout_ms = USB_CTRL_SET_TIMEOUT;
|
|
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
|
|
+ struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
|
|
+
|
|
+ if (hub->hdev->quirks & USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT)
|
|
+ timeout_ms = USB_SHORT_SET_ADDRESS_REQ_TIMEOUT;
|
|
|
|
/*
|
|
* The host controller will choose the device address,
|
|
@@ -4676,11 +4687,11 @@ static int hub_set_address(struct usb_device *udev, int devnum)
|
|
if (udev->state != USB_STATE_DEFAULT)
|
|
return -EINVAL;
|
|
if (hcd->driver->address_device)
|
|
- retval = hcd->driver->address_device(hcd, udev);
|
|
+ retval = hcd->driver->address_device(hcd, udev, timeout_ms);
|
|
else
|
|
retval = usb_control_msg(udev, usb_sndaddr0pipe(),
|
|
USB_REQ_SET_ADDRESS, 0, devnum, 0,
|
|
- NULL, 0, USB_CTRL_SET_TIMEOUT);
|
|
+ NULL, 0, timeout_ms);
|
|
if (retval == 0) {
|
|
update_devnum(udev, devnum);
|
|
/* Device now using proper address. */
|
|
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
|
|
index 10f2b1e499b8a..76e00bfedc25c 100644
|
|
--- a/drivers/usb/core/port.c
|
|
+++ b/drivers/usb/core/port.c
|
|
@@ -448,8 +448,10 @@ static void usb_port_shutdown(struct device *dev)
|
|
{
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
- if (port_dev->child)
|
|
+ if (port_dev->child) {
|
|
usb_disable_usb2_hardware_lpm(port_dev->child);
|
|
+ usb_unlocked_disable_lpm(port_dev->child);
|
|
+ }
|
|
}
|
|
|
|
static const struct dev_pm_ops usb_port_pm_ops = {
|
|
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
|
|
index 15e9bd180a1d2..b4783574b8e66 100644
|
|
--- a/drivers/usb/core/quirks.c
|
|
+++ b/drivers/usb/core/quirks.c
|
|
@@ -138,6 +138,9 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp)
|
|
case 'o':
|
|
flags |= USB_QUIRK_HUB_SLOW_RESET;
|
|
break;
|
|
+ case 'p':
|
|
+ flags |= USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT;
|
|
+ break;
|
|
/* Ignore unrecognized flag characters */
|
|
}
|
|
}
|
|
@@ -527,6 +530,10 @@ static const struct usb_device_id usb_quirk_list[] = {
|
|
|
|
{ USB_DEVICE(0x2386, 0x350e), .driver_info = USB_QUIRK_NO_LPM },
|
|
|
|
+ /* APTIV AUTOMOTIVE HUB */
|
|
+ { USB_DEVICE(0x2c48, 0x0132), .driver_info =
|
|
+ USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT },
|
|
+
|
|
/* DJI CineSSD */
|
|
{ USB_DEVICE(0x2ca3, 0x0031), .driver_info = USB_QUIRK_NO_LPM },
|
|
|
|
diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c
|
|
index 79582b102c7ed..994a78ad084b1 100644
|
|
--- a/drivers/usb/dwc2/hcd_ddma.c
|
|
+++ b/drivers/usb/dwc2/hcd_ddma.c
|
|
@@ -867,13 +867,15 @@ static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg,
|
|
struct dwc2_dma_desc *dma_desc;
|
|
struct dwc2_hcd_iso_packet_desc *frame_desc;
|
|
u16 frame_desc_idx;
|
|
- struct urb *usb_urb = qtd->urb->priv;
|
|
+ struct urb *usb_urb;
|
|
u16 remain = 0;
|
|
int rc = 0;
|
|
|
|
if (!qtd->urb)
|
|
return -EINVAL;
|
|
|
|
+ usb_urb = qtd->urb->priv;
|
|
+
|
|
dma_sync_single_for_cpu(hsotg->dev, qh->desc_list_dma + (idx *
|
|
sizeof(struct dwc2_dma_desc)),
|
|
sizeof(struct dwc2_dma_desc),
|
|
diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c
|
|
index f3456b8bf4152..f5731d465cd7b 100644
|
|
--- a/drivers/usb/gadget/function/f_ncm.c
|
|
+++ b/drivers/usb/gadget/function/f_ncm.c
|
|
@@ -869,7 +869,7 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
|
if (alt > 1)
|
|
goto fail;
|
|
|
|
- if (ncm->port.in_ep->enabled) {
|
|
+ if (ncm->netdev) {
|
|
DBG(cdev, "reset ncm\n");
|
|
ncm->netdev = NULL;
|
|
gether_disconnect(&ncm->port);
|
|
@@ -1354,7 +1354,7 @@ static void ncm_disable(struct usb_function *f)
|
|
|
|
DBG(cdev, "ncm deactivated\n");
|
|
|
|
- if (ncm->port.in_ep->enabled) {
|
|
+ if (ncm->netdev) {
|
|
ncm->netdev = NULL;
|
|
gether_disconnect(&ncm->port);
|
|
}
|
|
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
|
|
index 0a37f0d511cf5..b1e3fa54c6397 100644
|
|
--- a/drivers/usb/host/xhci-mem.c
|
|
+++ b/drivers/usb/host/xhci-mem.c
|
|
@@ -1729,6 +1729,8 @@ struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci,
|
|
}
|
|
|
|
command->status = 0;
|
|
+ /* set default timeout to 5000 ms */
|
|
+ command->timeout_ms = XHCI_CMD_DEFAULT_TIMEOUT;
|
|
INIT_LIST_HEAD(&command->cmd_list);
|
|
return command;
|
|
}
|
|
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
|
|
index 3ec1a2617ed7e..c959d9144baa5 100644
|
|
--- a/drivers/usb/host/xhci-ring.c
|
|
+++ b/drivers/usb/host/xhci-ring.c
|
|
@@ -372,9 +372,10 @@ void xhci_ring_cmd_db(struct xhci_hcd *xhci)
|
|
readl(&xhci->dba->doorbell[0]);
|
|
}
|
|
|
|
-static bool xhci_mod_cmd_timer(struct xhci_hcd *xhci, unsigned long delay)
|
|
+static bool xhci_mod_cmd_timer(struct xhci_hcd *xhci)
|
|
{
|
|
- return mod_delayed_work(system_wq, &xhci->cmd_timer, delay);
|
|
+ return mod_delayed_work(system_wq, &xhci->cmd_timer,
|
|
+ msecs_to_jiffies(xhci->current_cmd->timeout_ms));
|
|
}
|
|
|
|
static struct xhci_command *xhci_next_queued_cmd(struct xhci_hcd *xhci)
|
|
@@ -418,7 +419,7 @@ static void xhci_handle_stopped_cmd_ring(struct xhci_hcd *xhci,
|
|
if ((xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue) &&
|
|
!(xhci->xhc_state & XHCI_STATE_DYING)) {
|
|
xhci->current_cmd = cur_cmd;
|
|
- xhci_mod_cmd_timer(xhci, XHCI_CMD_DEFAULT_TIMEOUT);
|
|
+ xhci_mod_cmd_timer(xhci);
|
|
xhci_ring_cmd_db(xhci);
|
|
}
|
|
}
|
|
@@ -1792,7 +1793,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
|
|
if (!list_is_singular(&xhci->cmd_list)) {
|
|
xhci->current_cmd = list_first_entry(&cmd->cmd_list,
|
|
struct xhci_command, cmd_list);
|
|
- xhci_mod_cmd_timer(xhci, XHCI_CMD_DEFAULT_TIMEOUT);
|
|
+ xhci_mod_cmd_timer(xhci);
|
|
} else if (xhci->current_cmd == cmd) {
|
|
xhci->current_cmd = NULL;
|
|
}
|
|
@@ -4359,7 +4360,7 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
|
|
/* if there are no other commands queued we start the timeout timer */
|
|
if (list_empty(&xhci->cmd_list)) {
|
|
xhci->current_cmd = cmd;
|
|
- xhci_mod_cmd_timer(xhci, XHCI_CMD_DEFAULT_TIMEOUT);
|
|
+ xhci_mod_cmd_timer(xhci);
|
|
}
|
|
|
|
list_add_tail(&cmd->cmd_list, &xhci->cmd_list);
|
|
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
|
|
index c4c733d724bd8..573b5784d1c3d 100644
|
|
--- a/drivers/usb/host/xhci.c
|
|
+++ b/drivers/usb/host/xhci.c
|
|
@@ -4007,12 +4007,18 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev)
|
|
return 0;
|
|
}
|
|
|
|
-/*
|
|
- * Issue an Address Device command and optionally send a corresponding
|
|
- * SetAddress request to the device.
|
|
+/**
|
|
+ * xhci_setup_device - issues an Address Device command to assign a unique
|
|
+ * USB bus address.
|
|
+ * @hcd: USB host controller data structure.
|
|
+ * @udev: USB dev structure representing the connected device.
|
|
+ * @setup: Enum specifying setup mode: address only or with context.
|
|
+ * @timeout_ms: Max wait time (ms) for the command operation to complete.
|
|
+ *
|
|
+ * Return: 0 if successful; otherwise, negative error code.
|
|
*/
|
|
static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
|
|
- enum xhci_setup_dev setup)
|
|
+ enum xhci_setup_dev setup, unsigned int timeout_ms)
|
|
{
|
|
const char *act = setup == SETUP_CONTEXT_ONLY ? "context" : "address";
|
|
unsigned long flags;
|
|
@@ -4069,6 +4075,7 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
|
|
}
|
|
|
|
command->in_ctx = virt_dev->in_ctx;
|
|
+ command->timeout_ms = timeout_ms;
|
|
|
|
slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx);
|
|
ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx);
|
|
@@ -4195,14 +4202,16 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
|
|
return ret;
|
|
}
|
|
|
|
-static int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev)
|
|
+static int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev,
|
|
+ unsigned int timeout_ms)
|
|
{
|
|
- return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ADDRESS);
|
|
+ return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ADDRESS, timeout_ms);
|
|
}
|
|
|
|
static int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev)
|
|
{
|
|
- return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ONLY);
|
|
+ return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ONLY,
|
|
+ XHCI_CMD_DEFAULT_TIMEOUT);
|
|
}
|
|
|
|
/*
|
|
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
|
|
index 31088602c0708..be480d6ac8586 100644
|
|
--- a/drivers/usb/host/xhci.h
|
|
+++ b/drivers/usb/host/xhci.h
|
|
@@ -818,6 +818,8 @@ struct xhci_command {
|
|
struct completion *completion;
|
|
union xhci_trb *command_trb;
|
|
struct list_head cmd_list;
|
|
+ /* xHCI command response timeout in milliseconds */
|
|
+ unsigned int timeout_ms;
|
|
};
|
|
|
|
/* drop context bitmasks */
|
|
@@ -1577,8 +1579,11 @@ struct xhci_td {
|
|
unsigned int num_trbs;
|
|
};
|
|
|
|
-/* xHCI command default timeout value */
|
|
-#define XHCI_CMD_DEFAULT_TIMEOUT (5 * HZ)
|
|
+/*
|
|
+ * xHCI command default timeout value in milliseconds.
|
|
+ * USB 3.2 spec, section 9.2.6.1
|
|
+ */
|
|
+#define XHCI_CMD_DEFAULT_TIMEOUT 5000
|
|
|
|
/* command descriptor */
|
|
struct xhci_cd {
|
|
diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c
|
|
index 55a65d941ccbf..8a5846d4adf67 100644
|
|
--- a/drivers/usb/serial/option.c
|
|
+++ b/drivers/usb/serial/option.c
|
|
@@ -255,6 +255,10 @@ static void option_instat_callback(struct urb *urb);
|
|
#define QUECTEL_PRODUCT_EM061K_LMS 0x0124
|
|
#define QUECTEL_PRODUCT_EC25 0x0125
|
|
#define QUECTEL_PRODUCT_EM060K_128 0x0128
|
|
+#define QUECTEL_PRODUCT_EM060K_129 0x0129
|
|
+#define QUECTEL_PRODUCT_EM060K_12a 0x012a
|
|
+#define QUECTEL_PRODUCT_EM060K_12b 0x012b
|
|
+#define QUECTEL_PRODUCT_EM060K_12c 0x012c
|
|
#define QUECTEL_PRODUCT_EG91 0x0191
|
|
#define QUECTEL_PRODUCT_EG95 0x0195
|
|
#define QUECTEL_PRODUCT_BG96 0x0296
|
|
@@ -1218,6 +1222,18 @@ static const struct usb_device_id option_ids[] = {
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0xff, 0x30) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0x00, 0x40) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_128, 0xff, 0xff, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0xff, 0x30) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0x00, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_129, 0xff, 0xff, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0xff, 0x30) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0x00, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12a, 0xff, 0xff, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0xff, 0x30) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0x00, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12b, 0xff, 0xff, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0xff, 0x30) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0x00, 0x40) },
|
|
+ { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM060K_12c, 0xff, 0xff, 0x40) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0xff, 0x30) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0x00, 0x40) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EM061K_LCN, 0xff, 0xff, 0x40) },
|
|
@@ -1360,6 +1376,12 @@ static const struct usb_device_id option_ids[] = {
|
|
.driver_info = NCTRL(2) | RSVD(3) },
|
|
{ USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x1083, 0xff), /* Telit FE990 (ECM) */
|
|
.driver_info = NCTRL(0) | RSVD(1) },
|
|
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a0, 0xff), /* Telit FN20C04 (rmnet) */
|
|
+ .driver_info = RSVD(0) | NCTRL(3) },
|
|
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a4, 0xff), /* Telit FN20C04 (rmnet) */
|
|
+ .driver_info = RSVD(0) | NCTRL(3) },
|
|
+ { USB_DEVICE_INTERFACE_CLASS(TELIT_VENDOR_ID, 0x10a9, 0xff), /* Telit FN20C04 (rmnet) */
|
|
+ .driver_info = RSVD(0) | NCTRL(2) | RSVD(3) | RSVD(4) },
|
|
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910),
|
|
.driver_info = NCTRL(0) | RSVD(1) | RSVD(3) },
|
|
{ USB_DEVICE(TELIT_VENDOR_ID, TELIT_PRODUCT_ME910_DUAL_MODEM),
|
|
@@ -2052,6 +2074,10 @@ static const struct usb_device_id option_ids[] = {
|
|
.driver_info = RSVD(3) },
|
|
{ USB_DEVICE_INTERFACE_CLASS(LONGCHEER_VENDOR_ID, 0x9803, 0xff),
|
|
.driver_info = RSVD(4) },
|
|
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, 0x9b05), /* Longsung U8300 */
|
|
+ .driver_info = RSVD(4) | RSVD(5) },
|
|
+ { USB_DEVICE(LONGCHEER_VENDOR_ID, 0x9b3c), /* Longsung U9300 */
|
|
+ .driver_info = RSVD(0) | RSVD(4) },
|
|
{ USB_DEVICE(LONGCHEER_VENDOR_ID, ZOOM_PRODUCT_4597) },
|
|
{ USB_DEVICE(LONGCHEER_VENDOR_ID, IBALL_3_5G_CONNECT) },
|
|
{ USB_DEVICE(HAIER_VENDOR_ID, HAIER_PRODUCT_CE100) },
|
|
@@ -2272,15 +2298,29 @@ static const struct usb_device_id option_ids[] = {
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x010b, 0xff, 0xff, 0x30) }, /* Fibocom FG150 Diag */
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(0x2cb7, 0x010b, 0xff, 0, 0) }, /* Fibocom FG150 AT */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0111, 0xff) }, /* Fibocom FM160 (MBIM mode) */
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0115, 0xff), /* Fibocom FM135 (laptop MBIM) */
|
|
+ .driver_info = RSVD(5) },
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a0, 0xff) }, /* Fibocom NL668-AM/NL652-EU (laptop MBIM) */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a2, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a3, 0xff) }, /* Fibocom FM101-GL (laptop MBIM) */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x01a4, 0xff), /* Fibocom FM101-GL (laptop MBIM) */
|
|
.driver_info = RSVD(4) },
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a04, 0xff) }, /* Fibocom FM650-CN (ECM mode) */
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a05, 0xff) }, /* Fibocom FM650-CN (NCM mode) */
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a06, 0xff) }, /* Fibocom FM650-CN (RNDIS mode) */
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x2cb7, 0x0a07, 0xff) }, /* Fibocom FM650-CN (MBIM mode) */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x2df3, 0x9d03, 0xff) }, /* LongSung M5710 */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1404, 0xff) }, /* GosunCn GM500 RNDIS */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1405, 0xff) }, /* GosunCn GM500 MBIM */
|
|
{ USB_DEVICE_INTERFACE_CLASS(0x305a, 0x1406, 0xff) }, /* GosunCn GM500 ECM/NCM */
|
|
+ { USB_DEVICE(0x33f8, 0x0104), /* Rolling RW101-GL (laptop RMNET) */
|
|
+ .driver_info = RSVD(4) | RSVD(5) },
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a2, 0xff) }, /* Rolling RW101-GL (laptop MBIM) */
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a3, 0xff) }, /* Rolling RW101-GL (laptop MBIM) */
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x01a4, 0xff), /* Rolling RW101-GL (laptop MBIM) */
|
|
+ .driver_info = RSVD(4) },
|
|
+ { USB_DEVICE_INTERFACE_CLASS(0x33f8, 0x0115, 0xff), /* Rolling RW135-GL (laptop MBIM) */
|
|
+ .driver_info = RSVD(5) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(OPPO_VENDOR_ID, OPPO_PRODUCT_R11, 0xff, 0xff, 0x30) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0xff, 0x30) },
|
|
{ USB_DEVICE_AND_INTERFACE_INFO(SIERRA_VENDOR_ID, SIERRA_PRODUCT_EM9191, 0xff, 0xff, 0x40) },
|
|
diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
|
|
index f4863078f7fe5..da64bb7325dbc 100644
|
|
--- a/fs/ceph/addr.c
|
|
+++ b/fs/ceph/addr.c
|
|
@@ -229,7 +229,7 @@ static void ceph_netfs_expand_readahead(struct netfs_io_request *rreq)
|
|
static bool ceph_netfs_clamp_length(struct netfs_io_subrequest *subreq)
|
|
{
|
|
struct inode *inode = subreq->rreq->inode;
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
u64 objno, objoff;
|
|
u32 xlen;
|
|
@@ -244,7 +244,7 @@ static bool ceph_netfs_clamp_length(struct netfs_io_subrequest *subreq)
|
|
static void finish_netfs_read(struct ceph_osd_request *req)
|
|
{
|
|
struct inode *inode = req->r_inode;
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_data *osd_data = osd_req_op_extent_osd_data(req, 0);
|
|
struct netfs_io_subrequest *subreq = req->r_priv;
|
|
struct ceph_osd_req_op *op = &req->r_ops[0];
|
|
@@ -348,7 +348,7 @@ static void ceph_netfs_issue_read(struct netfs_io_subrequest *subreq)
|
|
struct netfs_io_request *rreq = subreq->rreq;
|
|
struct inode *inode = rreq->inode;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_request *req = NULL;
|
|
struct ceph_vino vino = ceph_vino(inode);
|
|
struct iov_iter iter;
|
|
@@ -658,7 +658,7 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)
|
|
struct folio *folio = page_folio(page);
|
|
struct inode *inode = page->mapping->host;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_snap_context *snapc, *oldest;
|
|
loff_t page_off = page_offset(page);
|
|
int err;
|
|
@@ -803,8 +803,10 @@ static int ceph_writepage(struct page *page, struct writeback_control *wbc)
|
|
ihold(inode);
|
|
|
|
if (wbc->sync_mode == WB_SYNC_NONE &&
|
|
- ceph_inode_to_client(inode)->write_congested)
|
|
+ ceph_inode_to_fs_client(inode)->write_congested) {
|
|
+ redirty_page_for_writepage(wbc, page);
|
|
return AOP_WRITEPAGE_ACTIVATE;
|
|
+ }
|
|
|
|
wait_on_page_fscache(page);
|
|
|
|
@@ -836,7 +838,7 @@ static void writepages_finish(struct ceph_osd_request *req)
|
|
int rc = req->r_result;
|
|
struct ceph_snap_context *snapc = req->r_snapc;
|
|
struct address_space *mapping = inode->i_mapping;
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
unsigned int len = 0;
|
|
bool remove_page;
|
|
|
|
@@ -926,7 +928,7 @@ static int ceph_writepages_start(struct address_space *mapping,
|
|
{
|
|
struct inode *inode = mapping->host;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_vino vino = ceph_vino(inode);
|
|
pgoff_t index, start_index, end = -1;
|
|
struct ceph_snap_context *snapc = NULL, *last_snapc = NULL, *pgsnapc;
|
|
@@ -1823,7 +1825,7 @@ int ceph_uninline_data(struct file *file)
|
|
{
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_request *req = NULL;
|
|
struct ceph_cap_flush *prealloc_cf = NULL;
|
|
struct folio *folio = NULL;
|
|
@@ -1977,7 +1979,7 @@ enum {
|
|
static int __ceph_pool_perm_get(struct ceph_inode_info *ci,
|
|
s64 pool, struct ceph_string *pool_ns)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(&ci->netfs.inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(&ci->netfs.inode);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct ceph_osd_request *rd_req = NULL, *wr_req = NULL;
|
|
struct rb_node **p, *parent;
|
|
@@ -2168,7 +2170,7 @@ int ceph_pool_perm_check(struct inode *inode, int need)
|
|
return 0;
|
|
}
|
|
|
|
- if (ceph_test_mount_opt(ceph_inode_to_client(inode),
|
|
+ if (ceph_test_mount_opt(ceph_inode_to_fs_client(inode),
|
|
NOPOOLPERM))
|
|
return 0;
|
|
|
|
diff --git a/fs/ceph/cache.c b/fs/ceph/cache.c
|
|
index de1dee46d3df7..930fbd54d2c8c 100644
|
|
--- a/fs/ceph/cache.c
|
|
+++ b/fs/ceph/cache.c
|
|
@@ -15,7 +15,7 @@
|
|
void ceph_fscache_register_inode_cookie(struct inode *inode)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
|
|
/* No caching for filesystem? */
|
|
if (!fsc->fscache)
|
|
diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
|
|
index fc9f8f1a9036d..00045b8eadd14 100644
|
|
--- a/fs/ceph/caps.c
|
|
+++ b/fs/ceph/caps.c
|
|
@@ -635,7 +635,7 @@ void ceph_add_cap(struct inode *inode,
|
|
unsigned seq, unsigned mseq, u64 realmino, int flags,
|
|
struct ceph_cap **new_cap)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
struct ceph_cap *cap;
|
|
int mds = session->s_mds;
|
|
@@ -922,7 +922,7 @@ int __ceph_caps_issued_mask(struct ceph_inode_info *ci, int mask, int touch)
|
|
int __ceph_caps_issued_mask_metric(struct ceph_inode_info *ci, int mask,
|
|
int touch)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(ci->netfs.inode.i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(ci->netfs.inode.i_sb);
|
|
int r;
|
|
|
|
r = __ceph_caps_issued_mask(ci, mask, touch);
|
|
@@ -996,7 +996,7 @@ int __ceph_caps_file_wanted(struct ceph_inode_info *ci)
|
|
const int WR_SHIFT = ffs(CEPH_FILE_MODE_WR);
|
|
const int LAZY_SHIFT = ffs(CEPH_FILE_MODE_LAZY);
|
|
struct ceph_mount_options *opt =
|
|
- ceph_inode_to_client(&ci->netfs.inode)->mount_options;
|
|
+ ceph_inode_to_fs_client(&ci->netfs.inode)->mount_options;
|
|
unsigned long used_cutoff = jiffies - opt->caps_wanted_delay_max * HZ;
|
|
unsigned long idle_cutoff = jiffies - opt->caps_wanted_delay_min * HZ;
|
|
|
|
@@ -1121,7 +1121,7 @@ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
|
|
|
|
dout("__ceph_remove_cap %p from %p\n", cap, &ci->netfs.inode);
|
|
|
|
- mdsc = ceph_inode_to_client(&ci->netfs.inode)->mdsc;
|
|
+ mdsc = ceph_inode_to_fs_client(&ci->netfs.inode)->mdsc;
|
|
|
|
/* remove from inode's cap rbtree, and clear auth cap */
|
|
rb_erase(&cap->ci_node, &ci->i_caps);
|
|
@@ -1178,7 +1178,8 @@ void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
|
|
}
|
|
}
|
|
|
|
-void ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
|
|
+void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap,
|
|
+ bool queue_release)
|
|
{
|
|
struct ceph_inode_info *ci = cap->ci;
|
|
struct ceph_fs_client *fsc;
|
|
@@ -1191,7 +1192,7 @@ void ceph_remove_cap(struct ceph_cap *cap, bool queue_release)
|
|
|
|
lockdep_assert_held(&ci->i_ceph_lock);
|
|
|
|
- fsc = ceph_inode_to_client(&ci->netfs.inode);
|
|
+ fsc = ceph_inode_to_fs_client(&ci->netfs.inode);
|
|
WARN_ON_ONCE(ci->i_auth_cap == cap &&
|
|
!list_empty(&ci->i_dirty_item) &&
|
|
!fsc->blocklisted &&
|
|
@@ -1342,6 +1343,8 @@ static void encode_cap_msg(struct ceph_msg *msg, struct cap_msg_args *arg)
|
|
*/
|
|
void __ceph_remove_caps(struct ceph_inode_info *ci)
|
|
{
|
|
+ struct inode *inode = &ci->netfs.inode;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
struct rb_node *p;
|
|
|
|
/* lock i_ceph_lock, because ceph_d_revalidate(..., LOOKUP_RCU)
|
|
@@ -1351,7 +1354,7 @@ void __ceph_remove_caps(struct ceph_inode_info *ci)
|
|
while (p) {
|
|
struct ceph_cap *cap = rb_entry(p, struct ceph_cap, ci_node);
|
|
p = rb_next(p);
|
|
- ceph_remove_cap(cap, true);
|
|
+ ceph_remove_cap(mdsc, cap, true);
|
|
}
|
|
spin_unlock(&ci->i_ceph_lock);
|
|
}
|
|
@@ -1686,7 +1689,7 @@ void ceph_flush_snaps(struct ceph_inode_info *ci,
|
|
struct ceph_mds_session **psession)
|
|
{
|
|
struct inode *inode = &ci->netfs.inode;
|
|
- struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
struct ceph_mds_session *session = NULL;
|
|
bool need_put = false;
|
|
int mds;
|
|
@@ -1751,7 +1754,7 @@ int __ceph_mark_dirty_caps(struct ceph_inode_info *ci, int mask,
|
|
struct ceph_cap_flush **pcf)
|
|
{
|
|
struct ceph_mds_client *mdsc =
|
|
- ceph_sb_to_client(ci->netfs.inode.i_sb)->mdsc;
|
|
+ ceph_sb_to_fs_client(ci->netfs.inode.i_sb)->mdsc;
|
|
struct inode *inode = &ci->netfs.inode;
|
|
int was = ci->i_dirty_caps;
|
|
int dirty = 0;
|
|
@@ -1874,7 +1877,7 @@ static u64 __mark_caps_flushing(struct inode *inode,
|
|
struct ceph_mds_session *session, bool wake,
|
|
u64 *oldest_flush_tid)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
struct ceph_cap_flush *cf = NULL;
|
|
int flushing;
|
|
@@ -2233,7 +2236,7 @@ void ceph_check_caps(struct ceph_inode_info *ci, int flags)
|
|
*/
|
|
static int try_flush_caps(struct inode *inode, u64 *ptid)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
int flushing = 0;
|
|
u64 flush_tid = 0, oldest_flush_tid = 0;
|
|
@@ -2311,7 +2314,7 @@ static int caps_are_flushed(struct inode *inode, u64 flush_tid)
|
|
*/
|
|
static int flush_mdlog_and_wait_inode_unsafe_requests(struct inode *inode)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
struct ceph_mds_request *req1 = NULL, *req2 = NULL;
|
|
int ret, err = 0;
|
|
@@ -2494,7 +2497,7 @@ int ceph_write_inode(struct inode *inode, struct writeback_control *wbc)
|
|
caps_are_flushed(inode, flush_tid));
|
|
} else {
|
|
struct ceph_mds_client *mdsc =
|
|
- ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
|
|
spin_lock(&ci->i_ceph_lock);
|
|
if (__ceph_caps_dirty(ci))
|
|
@@ -2747,7 +2750,7 @@ static int try_get_cap_refs(struct inode *inode, int need, int want,
|
|
loff_t endoff, int flags, int *got)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
int ret = 0;
|
|
int have, implemented;
|
|
bool snap_rwsem_locked = false;
|
|
@@ -2965,7 +2968,7 @@ int __ceph_get_caps(struct inode *inode, struct ceph_file_info *fi, int need,
|
|
int want, loff_t endoff, int *got)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
int ret, _got, flags;
|
|
|
|
ret = ceph_pool_perm_check(inode, need);
|
|
@@ -3728,7 +3731,7 @@ static void handle_cap_flush_ack(struct inode *inode, u64 flush_tid,
|
|
__releases(ci->i_ceph_lock)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_cap_flush *cf, *tmp_cf;
|
|
LIST_HEAD(to_remove);
|
|
unsigned seq = le32_to_cpu(m->seq);
|
|
@@ -3834,7 +3837,7 @@ void __ceph_remove_capsnap(struct inode *inode, struct ceph_cap_snap *capsnap,
|
|
bool *wake_ci, bool *wake_mdsc)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
bool ret;
|
|
|
|
lockdep_assert_held(&ci->i_ceph_lock);
|
|
@@ -3878,7 +3881,7 @@ static void handle_cap_flushsnap_ack(struct inode *inode, u64 flush_tid,
|
|
struct ceph_mds_session *session)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
u64 follows = le64_to_cpu(m->snap_follows);
|
|
struct ceph_cap_snap *capsnap = NULL, *iter;
|
|
bool wake_ci = false;
|
|
@@ -3970,7 +3973,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
|
struct ceph_mds_cap_peer *ph,
|
|
struct ceph_mds_session *session)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
struct ceph_mds_session *tsession = NULL;
|
|
struct ceph_cap *cap, *tcap, *new_cap = NULL;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
@@ -4000,7 +4003,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
|
goto out_unlock;
|
|
|
|
if (target < 0) {
|
|
- ceph_remove_cap(cap, false);
|
|
+ ceph_remove_cap(mdsc, cap, false);
|
|
goto out_unlock;
|
|
}
|
|
|
|
@@ -4035,7 +4038,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
|
change_auth_cap_ses(ci, tcap->session);
|
|
}
|
|
}
|
|
- ceph_remove_cap(cap, false);
|
|
+ ceph_remove_cap(mdsc, cap, false);
|
|
goto out_unlock;
|
|
} else if (tsession) {
|
|
/* add placeholder for the export tagert */
|
|
@@ -4052,7 +4055,7 @@ static void handle_cap_export(struct inode *inode, struct ceph_mds_caps *ex,
|
|
spin_unlock(&mdsc->cap_dirty_lock);
|
|
}
|
|
|
|
- ceph_remove_cap(cap, false);
|
|
+ ceph_remove_cap(mdsc, cap, false);
|
|
goto out_unlock;
|
|
}
|
|
|
|
@@ -4165,7 +4168,7 @@ static void handle_cap_import(struct ceph_mds_client *mdsc,
|
|
ocap->mseq, mds, le32_to_cpu(ph->seq),
|
|
le32_to_cpu(ph->mseq));
|
|
}
|
|
- ceph_remove_cap(ocap, (ph->flags & CEPH_CAP_FLAG_RELEASE));
|
|
+ ceph_remove_cap(mdsc, ocap, (ph->flags & CEPH_CAP_FLAG_RELEASE));
|
|
}
|
|
|
|
*old_issued = issued;
|
|
@@ -4673,7 +4676,7 @@ int ceph_drop_caps_for_unlink(struct inode *inode)
|
|
|
|
if (__ceph_caps_dirty(ci)) {
|
|
struct ceph_mds_client *mdsc =
|
|
- ceph_inode_to_client(inode)->mdsc;
|
|
+ ceph_inode_to_fs_client(inode)->mdsc;
|
|
__cap_delay_requeue_front(mdsc, ci);
|
|
}
|
|
}
|
|
@@ -4853,7 +4856,7 @@ static int remove_capsnaps(struct ceph_mds_client *mdsc, struct inode *inode)
|
|
|
|
int ceph_purge_inode_cap(struct inode *inode, struct ceph_cap *cap, bool *invalidate)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
bool is_auth;
|
|
diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c
|
|
index 5b5112c784629..08c3856107316 100644
|
|
--- a/fs/ceph/crypto.c
|
|
+++ b/fs/ceph/crypto.c
|
|
@@ -129,7 +129,7 @@ static bool ceph_crypt_empty_dir(struct inode *inode)
|
|
|
|
static const union fscrypt_policy *ceph_get_dummy_policy(struct super_block *sb)
|
|
{
|
|
- return ceph_sb_to_client(sb)->fsc_dummy_enc_policy.policy;
|
|
+ return ceph_sb_to_fs_client(sb)->fsc_dummy_enc_policy.policy;
|
|
}
|
|
|
|
static struct fscrypt_operations ceph_fscrypt_ops = {
|
|
diff --git a/fs/ceph/debugfs.c b/fs/ceph/debugfs.c
|
|
index 3904333fa6c38..2f1e7498cd745 100644
|
|
--- a/fs/ceph/debugfs.c
|
|
+++ b/fs/ceph/debugfs.c
|
|
@@ -81,7 +81,7 @@ static int mdsc_show(struct seq_file *s, void *p)
|
|
if (req->r_inode) {
|
|
seq_printf(s, " #%llx", ceph_ino(req->r_inode));
|
|
} else if (req->r_dentry) {
|
|
- path = ceph_mdsc_build_path(req->r_dentry, &pathlen,
|
|
+ path = ceph_mdsc_build_path(mdsc, req->r_dentry, &pathlen,
|
|
&pathbase, 0);
|
|
if (IS_ERR(path))
|
|
path = NULL;
|
|
@@ -100,7 +100,7 @@ static int mdsc_show(struct seq_file *s, void *p)
|
|
}
|
|
|
|
if (req->r_old_dentry) {
|
|
- path = ceph_mdsc_build_path(req->r_old_dentry, &pathlen,
|
|
+ path = ceph_mdsc_build_path(mdsc, req->r_old_dentry, &pathlen,
|
|
&pathbase, 0);
|
|
if (IS_ERR(path))
|
|
path = NULL;
|
|
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
|
|
index 854cbdd666619..1395b71df5ccc 100644
|
|
--- a/fs/ceph/dir.c
|
|
+++ b/fs/ceph/dir.c
|
|
@@ -310,7 +310,7 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
|
|
struct ceph_dir_file_info *dfi = file->private_data;
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
int i;
|
|
int err;
|
|
@@ -703,7 +703,7 @@ static loff_t ceph_dir_llseek(struct file *file, loff_t offset, int whence)
|
|
struct dentry *ceph_handle_snapdir(struct ceph_mds_request *req,
|
|
struct dentry *dentry)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dentry->d_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dentry->d_sb);
|
|
struct inode *parent = d_inode(dentry->d_parent); /* we hold i_rwsem */
|
|
|
|
/* .snap dir? */
|
|
@@ -771,7 +771,7 @@ static bool is_root_ceph_dentry(struct inode *inode, struct dentry *dentry)
|
|
static struct dentry *ceph_lookup(struct inode *dir, struct dentry *dentry,
|
|
unsigned int flags)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dir->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dir->i_sb);
|
|
struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(dir->i_sb);
|
|
struct ceph_mds_request *req;
|
|
int op;
|
|
@@ -1199,7 +1199,7 @@ static void ceph_async_unlink_cb(struct ceph_mds_client *mdsc,
|
|
struct ceph_mds_request *req)
|
|
{
|
|
struct dentry *dentry = req->r_dentry;
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dentry->d_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dentry->d_sb);
|
|
struct ceph_dentry_info *di = ceph_dentry(dentry);
|
|
int result = req->r_err ? req->r_err :
|
|
le32_to_cpu(req->r_reply_info.head->result);
|
|
@@ -1226,7 +1226,7 @@ static void ceph_async_unlink_cb(struct ceph_mds_client *mdsc,
|
|
if (result) {
|
|
int pathlen = 0;
|
|
u64 base = 0;
|
|
- char *path = ceph_mdsc_build_path(dentry, &pathlen,
|
|
+ char *path = ceph_mdsc_build_path(mdsc, dentry, &pathlen,
|
|
&base, 0);
|
|
|
|
/* mark error on parent + clear complete */
|
|
@@ -1290,7 +1290,7 @@ static int get_caps_for_async_unlink(struct inode *dir, struct dentry *dentry)
|
|
*/
|
|
static int ceph_unlink(struct inode *dir, struct dentry *dentry)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dir->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dir->i_sb);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct inode *inode = d_inode(dentry);
|
|
struct ceph_mds_request *req;
|
|
@@ -1469,7 +1469,7 @@ void __ceph_dentry_lease_touch(struct ceph_dentry_info *di)
|
|
return;
|
|
}
|
|
|
|
- mdsc = ceph_sb_to_client(dn->d_sb)->mdsc;
|
|
+ mdsc = ceph_sb_to_fs_client(dn->d_sb)->mdsc;
|
|
spin_lock(&mdsc->dentry_list_lock);
|
|
list_move_tail(&di->lease_list, &mdsc->dentry_leases);
|
|
spin_unlock(&mdsc->dentry_list_lock);
|
|
@@ -1516,7 +1516,7 @@ void __ceph_dentry_dir_lease_touch(struct ceph_dentry_info *di)
|
|
return;
|
|
}
|
|
|
|
- mdsc = ceph_sb_to_client(dn->d_sb)->mdsc;
|
|
+ mdsc = ceph_sb_to_fs_client(dn->d_sb)->mdsc;
|
|
spin_lock(&mdsc->dentry_list_lock);
|
|
__dentry_dir_lease_touch(mdsc, di),
|
|
spin_unlock(&mdsc->dentry_list_lock);
|
|
@@ -1530,7 +1530,7 @@ static void __dentry_lease_unlist(struct ceph_dentry_info *di)
|
|
if (list_empty(&di->lease_list))
|
|
return;
|
|
|
|
- mdsc = ceph_sb_to_client(di->dentry->d_sb)->mdsc;
|
|
+ mdsc = ceph_sb_to_fs_client(di->dentry->d_sb)->mdsc;
|
|
spin_lock(&mdsc->dentry_list_lock);
|
|
list_del_init(&di->lease_list);
|
|
spin_unlock(&mdsc->dentry_list_lock);
|
|
@@ -1888,7 +1888,7 @@ static int ceph_d_revalidate(struct dentry *dentry, unsigned int flags)
|
|
dentry, inode, ceph_dentry(dentry)->offset,
|
|
!!(dentry->d_flags & DCACHE_NOKEY_NAME));
|
|
|
|
- mdsc = ceph_sb_to_client(dir->i_sb)->mdsc;
|
|
+ mdsc = ceph_sb_to_fs_client(dir->i_sb)->mdsc;
|
|
|
|
/* always trust cached snapped dentries, snapdir dentry */
|
|
if (ceph_snap(dir) != CEPH_NOSNAP) {
|
|
@@ -1995,7 +1995,7 @@ static int ceph_d_delete(const struct dentry *dentry)
|
|
static void ceph_d_release(struct dentry *dentry)
|
|
{
|
|
struct ceph_dentry_info *di = ceph_dentry(dentry);
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dentry->d_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dentry->d_sb);
|
|
|
|
dout("d_release %p\n", dentry);
|
|
|
|
@@ -2064,7 +2064,7 @@ static ssize_t ceph_read_dir(struct file *file, char __user *buf, size_t size,
|
|
int left;
|
|
const int bufsize = 1024;
|
|
|
|
- if (!ceph_test_mount_opt(ceph_sb_to_client(inode->i_sb), DIRSTAT))
|
|
+ if (!ceph_test_mount_opt(ceph_sb_to_fs_client(inode->i_sb), DIRSTAT))
|
|
return -EISDIR;
|
|
|
|
if (!dfi->dir_info) {
|
|
diff --git a/fs/ceph/export.c b/fs/ceph/export.c
|
|
index 8559990a59a5c..52c4daf2447d3 100644
|
|
--- a/fs/ceph/export.c
|
|
+++ b/fs/ceph/export.c
|
|
@@ -123,7 +123,7 @@ static int ceph_encode_fh(struct inode *inode, u32 *rawfh, int *max_len,
|
|
|
|
static struct inode *__lookup_inode(struct super_block *sb, u64 ino)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc;
|
|
struct inode *inode;
|
|
struct ceph_vino vino;
|
|
int err;
|
|
@@ -205,7 +205,7 @@ static struct dentry *__snapfh_to_dentry(struct super_block *sb,
|
|
struct ceph_nfs_snapfh *sfh,
|
|
bool want_parent)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc;
|
|
struct ceph_mds_request *req;
|
|
struct inode *inode;
|
|
struct ceph_vino vino;
|
|
@@ -317,7 +317,7 @@ static struct dentry *ceph_fh_to_dentry(struct super_block *sb,
|
|
static struct dentry *__get_parent(struct super_block *sb,
|
|
struct dentry *child, u64 ino)
|
|
{
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(sb)->mdsc;
|
|
struct ceph_mds_request *req;
|
|
struct inode *inode;
|
|
int mask;
|
|
@@ -439,7 +439,7 @@ static int __get_snap_name(struct dentry *parent, char *name,
|
|
{
|
|
struct inode *inode = d_inode(child);
|
|
struct inode *dir = d_inode(parent);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_mds_request *req = NULL;
|
|
char *last_name = NULL;
|
|
unsigned next_offset = 2;
|
|
@@ -544,7 +544,7 @@ static int ceph_get_name(struct dentry *parent, char *name,
|
|
if (ceph_snap(inode) != CEPH_NOSNAP)
|
|
return __get_snap_name(parent, name, child);
|
|
|
|
- mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
req = ceph_mdsc_create_request(mdsc, CEPH_MDS_OP_LOOKUPNAME,
|
|
USE_ANY_MDS);
|
|
if (IS_ERR(req))
|
|
diff --git a/fs/ceph/file.c b/fs/ceph/file.c
|
|
index bdd0a3b894b7b..1e0497295662a 100644
|
|
--- a/fs/ceph/file.c
|
|
+++ b/fs/ceph/file.c
|
|
@@ -200,7 +200,7 @@ static int ceph_init_file_info(struct inode *inode, struct file *file,
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
struct ceph_mount_options *opt =
|
|
- ceph_inode_to_client(&ci->netfs.inode)->mount_options;
|
|
+ ceph_inode_to_fs_client(&ci->netfs.inode)->mount_options;
|
|
struct ceph_file_info *fi;
|
|
int ret;
|
|
|
|
@@ -234,7 +234,7 @@ static int ceph_init_file_info(struct inode *inode, struct file *file,
|
|
|
|
spin_lock_init(&fi->rw_contexts_lock);
|
|
INIT_LIST_HEAD(&fi->rw_contexts);
|
|
- fi->filp_gen = READ_ONCE(ceph_inode_to_client(inode)->filp_gen);
|
|
+ fi->filp_gen = READ_ONCE(ceph_inode_to_fs_client(inode)->filp_gen);
|
|
|
|
if ((file->f_mode & FMODE_WRITE) && ceph_has_inline_data(ci)) {
|
|
ret = ceph_uninline_data(file);
|
|
@@ -352,7 +352,7 @@ int ceph_renew_caps(struct inode *inode, int fmode)
|
|
int ceph_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(inode->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(inode->i_sb);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct ceph_mds_request *req;
|
|
struct ceph_file_info *fi = file->private_data;
|
|
@@ -574,7 +574,7 @@ static void ceph_async_create_cb(struct ceph_mds_client *mdsc,
|
|
if (result) {
|
|
int pathlen = 0;
|
|
u64 base = 0;
|
|
- char *path = ceph_mdsc_build_path(req->r_dentry, &pathlen,
|
|
+ char *path = ceph_mdsc_build_path(mdsc, req->r_dentry, &pathlen,
|
|
&base, 0);
|
|
|
|
pr_warn("async create failure path=(%llx)%s result=%d!\n",
|
|
@@ -730,7 +730,7 @@ static int ceph_finish_async_create(struct inode *dir, struct inode *inode,
|
|
int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
|
|
struct file *file, unsigned flags, umode_t mode)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dir->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dir->i_sb);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct ceph_mds_request *req;
|
|
struct inode *new_inode = NULL;
|
|
@@ -962,7 +962,7 @@ ssize_t __ceph_sync_read(struct inode *inode, loff_t *ki_pos,
|
|
u64 *last_objver)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_client *osdc = &fsc->client->osdc;
|
|
ssize_t ret;
|
|
u64 off = *ki_pos;
|
|
@@ -1259,7 +1259,7 @@ static void ceph_aio_complete_req(struct ceph_osd_request *req)
|
|
if (aio_work) {
|
|
INIT_WORK(&aio_work->work, ceph_aio_retry_work);
|
|
aio_work->req = req;
|
|
- queue_work(ceph_inode_to_client(inode)->inode_wq,
|
|
+ queue_work(ceph_inode_to_fs_client(inode)->inode_wq,
|
|
&aio_work->work);
|
|
return;
|
|
}
|
|
@@ -1389,7 +1389,7 @@ ceph_direct_read_write(struct kiocb *iocb, struct iov_iter *iter,
|
|
struct file *file = iocb->ki_filp;
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_client_metric *metric = &fsc->mdsc->metric;
|
|
struct ceph_vino vino;
|
|
struct ceph_osd_request *req;
|
|
@@ -1613,7 +1613,7 @@ ceph_sync_write(struct kiocb *iocb, struct iov_iter *from, loff_t pos,
|
|
struct file *file = iocb->ki_filp;
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_client *osdc = &fsc->client->osdc;
|
|
struct ceph_osd_request *req;
|
|
struct page **pages;
|
|
@@ -2231,7 +2231,7 @@ static ssize_t ceph_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
|
struct ceph_file_info *fi = file->private_data;
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_client *osdc = &fsc->client->osdc;
|
|
struct ceph_cap_flush *prealloc_cf;
|
|
ssize_t count, written = 0;
|
|
@@ -2465,7 +2465,7 @@ static int ceph_zero_partial_object(struct inode *inode,
|
|
loff_t offset, loff_t *length)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_osd_request *req;
|
|
int ret = 0;
|
|
loff_t zero = 0;
|
|
@@ -2848,7 +2848,7 @@ static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off,
|
|
struct ceph_inode_info *src_ci = ceph_inode(src_inode);
|
|
struct ceph_inode_info *dst_ci = ceph_inode(dst_inode);
|
|
struct ceph_cap_flush *prealloc_cf;
|
|
- struct ceph_fs_client *src_fsc = ceph_inode_to_client(src_inode);
|
|
+ struct ceph_fs_client *src_fsc = ceph_inode_to_fs_client(src_inode);
|
|
loff_t size;
|
|
ssize_t ret = -EIO, bytes;
|
|
u64 src_objnum, dst_objnum, src_objoff, dst_objoff;
|
|
@@ -2856,7 +2856,7 @@ static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off,
|
|
int src_got = 0, dst_got = 0, err, dirty;
|
|
|
|
if (src_inode->i_sb != dst_inode->i_sb) {
|
|
- struct ceph_fs_client *dst_fsc = ceph_inode_to_client(dst_inode);
|
|
+ struct ceph_fs_client *dst_fsc = ceph_inode_to_fs_client(dst_inode);
|
|
|
|
if (ceph_fsid_compare(&src_fsc->client->fsid,
|
|
&dst_fsc->client->fsid)) {
|
|
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
|
|
index b79100f720b38..db6977c15c282 100644
|
|
--- a/fs/ceph/inode.c
|
|
+++ b/fs/ceph/inode.c
|
|
@@ -1489,7 +1489,7 @@ int ceph_fill_trace(struct super_block *sb, struct ceph_mds_request *req)
|
|
struct ceph_mds_reply_info_parsed *rinfo = &req->r_reply_info;
|
|
struct inode *in = NULL;
|
|
struct ceph_vino tvino, dvino;
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
|
|
int err = 0;
|
|
|
|
dout("fill_trace %p is_dentry %d is_target %d\n", req,
|
|
@@ -2079,7 +2079,7 @@ bool ceph_inode_set_size(struct inode *inode, loff_t size)
|
|
|
|
void ceph_queue_inode_work(struct inode *inode, int work_bit)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
set_bit(work_bit, &ci->i_work_mask);
|
|
|
|
@@ -2427,7 +2427,7 @@ int __ceph_setattr(struct inode *inode, struct iattr *attr,
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
unsigned int ia_valid = attr->ia_valid;
|
|
struct ceph_mds_request *req;
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_cap_flush *prealloc_cf;
|
|
loff_t isize = i_size_read(inode);
|
|
int issued;
|
|
@@ -2740,7 +2740,7 @@ int ceph_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
|
|
struct iattr *attr)
|
|
{
|
|
struct inode *inode = d_inode(dentry);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
int err;
|
|
|
|
if (ceph_snap(inode) != CEPH_NOSNAP)
|
|
@@ -2810,7 +2810,7 @@ int ceph_try_to_choose_auth_mds(struct inode *inode, int mask)
|
|
int __ceph_do_getattr(struct inode *inode, struct page *locked_page,
|
|
int mask, bool force)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(inode->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(inode->i_sb);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct ceph_mds_request *req;
|
|
int mode;
|
|
@@ -2856,7 +2856,7 @@ int __ceph_do_getattr(struct inode *inode, struct page *locked_page,
|
|
int ceph_do_getvxattr(struct inode *inode, const char *name, void *value,
|
|
size_t size)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(inode->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(inode->i_sb);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
struct ceph_mds_request *req;
|
|
int mode = USE_AUTH_MDS;
|
|
@@ -3001,7 +3001,7 @@ int ceph_getattr(struct mnt_idmap *idmap, const struct path *path,
|
|
stat->dev = ci->i_snapid_map ? ci->i_snapid_map->dev : 0;
|
|
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
- if (ceph_test_mount_opt(ceph_sb_to_client(sb), RBYTES)) {
|
|
+ if (ceph_test_mount_opt(ceph_sb_to_fs_client(sb), RBYTES)) {
|
|
stat->size = ci->i_rbytes;
|
|
} else if (ceph_snap(inode) == CEPH_SNAPDIR) {
|
|
struct ceph_inode_info *pci;
|
|
diff --git a/fs/ceph/ioctl.c b/fs/ceph/ioctl.c
|
|
index 91a84917d203c..3f617146e4ad3 100644
|
|
--- a/fs/ceph/ioctl.c
|
|
+++ b/fs/ceph/ioctl.c
|
|
@@ -65,7 +65,7 @@ static long __validate_layout(struct ceph_mds_client *mdsc,
|
|
static long ceph_ioctl_set_layout(struct file *file, void __user *arg)
|
|
{
|
|
struct inode *inode = file_inode(file);
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_mds_request *req;
|
|
struct ceph_ioctl_layout l;
|
|
struct ceph_inode_info *ci = ceph_inode(file_inode(file));
|
|
@@ -140,7 +140,7 @@ static long ceph_ioctl_set_layout_policy (struct file *file, void __user *arg)
|
|
struct ceph_mds_request *req;
|
|
struct ceph_ioctl_layout l;
|
|
int err;
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
|
|
/* copy and validate */
|
|
if (copy_from_user(&l, arg, sizeof(l)))
|
|
@@ -183,7 +183,7 @@ static long ceph_ioctl_get_dataloc(struct file *file, void __user *arg)
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
struct ceph_osd_client *osdc =
|
|
- &ceph_sb_to_client(inode->i_sb)->client->osdc;
|
|
+ &ceph_sb_to_fs_client(inode->i_sb)->client->osdc;
|
|
struct ceph_object_locator oloc;
|
|
CEPH_DEFINE_OID_ONSTACK(oid);
|
|
u32 xlen;
|
|
@@ -244,7 +244,7 @@ static long ceph_ioctl_lazyio(struct file *file)
|
|
struct ceph_file_info *fi = file->private_data;
|
|
struct inode *inode = file_inode(file);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
|
|
if ((fi->fmode & CEPH_FILE_MODE_LAZY) == 0) {
|
|
spin_lock(&ci->i_ceph_lock);
|
|
diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
|
|
index 6d76fd0f704a6..11289ce8a8cc8 100644
|
|
--- a/fs/ceph/mds_client.c
|
|
+++ b/fs/ceph/mds_client.c
|
|
@@ -830,7 +830,7 @@ static void destroy_reply_info(struct ceph_mds_reply_info_parsed *info)
|
|
*/
|
|
int ceph_wait_on_conflict_unlink(struct dentry *dentry)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(dentry->d_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dentry->d_sb);
|
|
struct dentry *pdentry = dentry->d_parent;
|
|
struct dentry *udentry, *found = NULL;
|
|
struct ceph_dentry_info *di;
|
|
@@ -2126,6 +2126,7 @@ static bool drop_negative_children(struct dentry *dentry)
|
|
*/
|
|
static int trim_caps_cb(struct inode *inode, int mds, void *arg)
|
|
{
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
|
|
int *remaining = arg;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
int used, wanted, oissued, mine;
|
|
@@ -2173,7 +2174,7 @@ static int trim_caps_cb(struct inode *inode, int mds, void *arg)
|
|
|
|
if (oissued) {
|
|
/* we aren't the only cap.. just remove us */
|
|
- ceph_remove_cap(cap, true);
|
|
+ ceph_remove_cap(mdsc, cap, true);
|
|
(*remaining)--;
|
|
} else {
|
|
struct dentry *dentry;
|
|
@@ -2588,6 +2589,7 @@ static u8 *get_fscrypt_altname(const struct ceph_mds_request *req, u32 *plen)
|
|
|
|
/**
|
|
* ceph_mdsc_build_path - build a path string to a given dentry
|
|
+ * @mdsc: mds client
|
|
* @dentry: dentry to which path should be built
|
|
* @plen: returned length of string
|
|
* @pbase: returned base inode number
|
|
@@ -2607,8 +2609,8 @@ static u8 *get_fscrypt_altname(const struct ceph_mds_request *req, u32 *plen)
|
|
* Encode hidden .snap dirs as a double /, i.e.
|
|
* foo/.snap/bar -> foo//bar
|
|
*/
|
|
-char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *pbase,
|
|
- int for_wire)
|
|
+char *ceph_mdsc_build_path(struct ceph_mds_client *mdsc, struct dentry *dentry,
|
|
+ int *plen, u64 *pbase, int for_wire)
|
|
{
|
|
struct dentry *cur;
|
|
struct inode *inode;
|
|
@@ -2726,9 +2728,9 @@ char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *pbase,
|
|
return path + pos;
|
|
}
|
|
|
|
-static int build_dentry_path(struct dentry *dentry, struct inode *dir,
|
|
- const char **ppath, int *ppathlen, u64 *pino,
|
|
- bool *pfreepath, bool parent_locked)
|
|
+static int build_dentry_path(struct ceph_mds_client *mdsc, struct dentry *dentry,
|
|
+ struct inode *dir, const char **ppath, int *ppathlen,
|
|
+ u64 *pino, bool *pfreepath, bool parent_locked)
|
|
{
|
|
char *path;
|
|
|
|
@@ -2744,7 +2746,7 @@ static int build_dentry_path(struct dentry *dentry, struct inode *dir,
|
|
return 0;
|
|
}
|
|
rcu_read_unlock();
|
|
- path = ceph_mdsc_build_path(dentry, ppathlen, pino, 1);
|
|
+ path = ceph_mdsc_build_path(mdsc, dentry, ppathlen, pino, 1);
|
|
if (IS_ERR(path))
|
|
return PTR_ERR(path);
|
|
*ppath = path;
|
|
@@ -2756,6 +2758,7 @@ static int build_inode_path(struct inode *inode,
|
|
const char **ppath, int *ppathlen, u64 *pino,
|
|
bool *pfreepath)
|
|
{
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
|
|
struct dentry *dentry;
|
|
char *path;
|
|
|
|
@@ -2765,7 +2768,7 @@ static int build_inode_path(struct inode *inode,
|
|
return 0;
|
|
}
|
|
dentry = d_find_alias(inode);
|
|
- path = ceph_mdsc_build_path(dentry, ppathlen, pino, 1);
|
|
+ path = ceph_mdsc_build_path(mdsc, dentry, ppathlen, pino, 1);
|
|
dput(dentry);
|
|
if (IS_ERR(path))
|
|
return PTR_ERR(path);
|
|
@@ -2778,10 +2781,11 @@ static int build_inode_path(struct inode *inode,
|
|
* request arguments may be specified via an inode *, a dentry *, or
|
|
* an explicit ino+path.
|
|
*/
|
|
-static int set_request_path_attr(struct inode *rinode, struct dentry *rdentry,
|
|
- struct inode *rdiri, const char *rpath,
|
|
- u64 rino, const char **ppath, int *pathlen,
|
|
- u64 *ino, bool *freepath, bool parent_locked)
|
|
+static int set_request_path_attr(struct ceph_mds_client *mdsc, struct inode *rinode,
|
|
+ struct dentry *rdentry, struct inode *rdiri,
|
|
+ const char *rpath, u64 rino, const char **ppath,
|
|
+ int *pathlen, u64 *ino, bool *freepath,
|
|
+ bool parent_locked)
|
|
{
|
|
int r = 0;
|
|
|
|
@@ -2790,7 +2794,7 @@ static int set_request_path_attr(struct inode *rinode, struct dentry *rdentry,
|
|
dout(" inode %p %llx.%llx\n", rinode, ceph_ino(rinode),
|
|
ceph_snap(rinode));
|
|
} else if (rdentry) {
|
|
- r = build_dentry_path(rdentry, rdiri, ppath, pathlen, ino,
|
|
+ r = build_dentry_path(mdsc, rdentry, rdiri, ppath, pathlen, ino,
|
|
freepath, parent_locked);
|
|
dout(" dentry %p %llx/%.*s\n", rdentry, *ino, *pathlen,
|
|
*ppath);
|
|
@@ -2877,7 +2881,7 @@ static struct ceph_msg *create_request_message(struct ceph_mds_session *session,
|
|
bool old_version = !test_bit(CEPHFS_FEATURE_32BITS_RETRY_FWD,
|
|
&session->s_features);
|
|
|
|
- ret = set_request_path_attr(req->r_inode, req->r_dentry,
|
|
+ ret = set_request_path_attr(mdsc, req->r_inode, req->r_dentry,
|
|
req->r_parent, req->r_path1, req->r_ino1.ino,
|
|
&path1, &pathlen1, &ino1, &freepath1,
|
|
test_bit(CEPH_MDS_R_PARENT_LOCKED,
|
|
@@ -2891,7 +2895,7 @@ static struct ceph_msg *create_request_message(struct ceph_mds_session *session,
|
|
if (req->r_old_dentry &&
|
|
!(req->r_old_dentry->d_flags & DCACHE_DISCONNECTED))
|
|
old_dentry = req->r_old_dentry;
|
|
- ret = set_request_path_attr(NULL, old_dentry,
|
|
+ ret = set_request_path_attr(mdsc, NULL, old_dentry,
|
|
req->r_old_dentry_dir,
|
|
req->r_path2, req->r_ino2.ino,
|
|
&path2, &pathlen2, &ino2, &freepath2, true);
|
|
@@ -4290,6 +4294,7 @@ static struct dentry* d_find_primary(struct inode *inode)
|
|
*/
|
|
static int reconnect_caps_cb(struct inode *inode, int mds, void *arg)
|
|
{
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(inode->i_sb);
|
|
union {
|
|
struct ceph_mds_cap_reconnect v2;
|
|
struct ceph_mds_cap_reconnect_v1 v1;
|
|
@@ -4307,7 +4312,7 @@ static int reconnect_caps_cb(struct inode *inode, int mds, void *arg)
|
|
dentry = d_find_primary(inode);
|
|
if (dentry) {
|
|
/* set pathbase to parent dir when msg_version >= 2 */
|
|
- path = ceph_mdsc_build_path(dentry, &pathlen, &pathbase,
|
|
+ path = ceph_mdsc_build_path(mdsc, dentry, &pathlen, &pathbase,
|
|
recon_state->msg_version >= 2);
|
|
dput(dentry);
|
|
if (IS_ERR(path)) {
|
|
@@ -5662,7 +5667,7 @@ void ceph_mdsc_handle_mdsmap(struct ceph_mds_client *mdsc, struct ceph_msg *msg)
|
|
return;
|
|
}
|
|
|
|
- newmap = ceph_mdsmap_decode(&p, end, ceph_msgr2(mdsc->fsc->client));
|
|
+ newmap = ceph_mdsmap_decode(mdsc, &p, end, ceph_msgr2(mdsc->fsc->client));
|
|
if (IS_ERR(newmap)) {
|
|
err = PTR_ERR(newmap);
|
|
goto bad_unlock;
|
|
diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h
|
|
index 5a3714bdd64a8..d930eb79dc380 100644
|
|
--- a/fs/ceph/mds_client.h
|
|
+++ b/fs/ceph/mds_client.h
|
|
@@ -581,7 +581,8 @@ static inline void ceph_mdsc_free_path(char *path, int len)
|
|
__putname(path - (PATH_MAX - 1 - len));
|
|
}
|
|
|
|
-extern char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *base,
|
|
+extern char *ceph_mdsc_build_path(struct ceph_mds_client *mdsc,
|
|
+ struct dentry *dentry, int *plen, u64 *base,
|
|
int for_wire);
|
|
|
|
extern void __ceph_mdsc_drop_dentry_lease(struct dentry *dentry);
|
|
diff --git a/fs/ceph/mdsmap.c b/fs/ceph/mdsmap.c
|
|
index 3bb3b610d403e..66afb18df76b2 100644
|
|
--- a/fs/ceph/mdsmap.c
|
|
+++ b/fs/ceph/mdsmap.c
|
|
@@ -114,7 +114,8 @@ static int __decode_and_drop_compat_set(void **p, void* end)
|
|
* Ignore any fields we don't care about (there are quite a few of
|
|
* them).
|
|
*/
|
|
-struct ceph_mdsmap *ceph_mdsmap_decode(void **p, void *end, bool msgr2)
|
|
+struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
|
|
+ void *end, bool msgr2)
|
|
{
|
|
struct ceph_mdsmap *m;
|
|
const void *start = *p;
|
|
diff --git a/fs/ceph/snap.c b/fs/ceph/snap.c
|
|
index 813f21add992c..d0d3612f28f0e 100644
|
|
--- a/fs/ceph/snap.c
|
|
+++ b/fs/ceph/snap.c
|
|
@@ -329,7 +329,8 @@ static int cmpu64_rev(const void *a, const void *b)
|
|
/*
|
|
* build the snap context for a given realm.
|
|
*/
|
|
-static int build_snap_context(struct ceph_snap_realm *realm,
|
|
+static int build_snap_context(struct ceph_mds_client *mdsc,
|
|
+ struct ceph_snap_realm *realm,
|
|
struct list_head *realm_queue,
|
|
struct list_head *dirty_realms)
|
|
{
|
|
@@ -425,7 +426,8 @@ static int build_snap_context(struct ceph_snap_realm *realm,
|
|
/*
|
|
* rebuild snap context for the given realm and all of its children.
|
|
*/
|
|
-static void rebuild_snap_realms(struct ceph_snap_realm *realm,
|
|
+static void rebuild_snap_realms(struct ceph_mds_client *mdsc,
|
|
+ struct ceph_snap_realm *realm,
|
|
struct list_head *dirty_realms)
|
|
{
|
|
LIST_HEAD(realm_queue);
|
|
@@ -451,7 +453,8 @@ static void rebuild_snap_realms(struct ceph_snap_realm *realm,
|
|
continue;
|
|
}
|
|
|
|
- last = build_snap_context(_realm, &realm_queue, dirty_realms);
|
|
+ last = build_snap_context(mdsc, _realm, &realm_queue,
|
|
+ dirty_realms);
|
|
dout("%s %llx %p, %s\n", __func__, _realm->ino, _realm,
|
|
last > 0 ? "is deferred" : !last ? "succeeded" : "failed");
|
|
|
|
@@ -708,7 +711,8 @@ int __ceph_finish_cap_snap(struct ceph_inode_info *ci,
|
|
* Queue cap_snaps for snap writeback for this realm and its children.
|
|
* Called under snap_rwsem, so realm topology won't change.
|
|
*/
|
|
-static void queue_realm_cap_snaps(struct ceph_snap_realm *realm)
|
|
+static void queue_realm_cap_snaps(struct ceph_mds_client *mdsc,
|
|
+ struct ceph_snap_realm *realm)
|
|
{
|
|
struct ceph_inode_info *ci;
|
|
struct inode *lastinode = NULL;
|
|
@@ -855,7 +859,7 @@ int ceph_update_snap_trace(struct ceph_mds_client *mdsc,
|
|
|
|
/* rebuild_snapcs when we reach the _end_ (root) of the trace */
|
|
if (realm_to_rebuild && p >= e)
|
|
- rebuild_snap_realms(realm_to_rebuild, &dirty_realms);
|
|
+ rebuild_snap_realms(mdsc, realm_to_rebuild, &dirty_realms);
|
|
|
|
if (!first_realm)
|
|
first_realm = realm;
|
|
@@ -873,7 +877,7 @@ int ceph_update_snap_trace(struct ceph_mds_client *mdsc,
|
|
realm = list_first_entry(&dirty_realms, struct ceph_snap_realm,
|
|
dirty_item);
|
|
list_del_init(&realm->dirty_item);
|
|
- queue_realm_cap_snaps(realm);
|
|
+ queue_realm_cap_snaps(mdsc, realm);
|
|
}
|
|
|
|
if (realm_ret)
|
|
@@ -960,7 +964,7 @@ static void flush_snaps(struct ceph_mds_client *mdsc)
|
|
void ceph_change_snap_realm(struct inode *inode, struct ceph_snap_realm *realm)
|
|
{
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_inode_to_fs_client(inode)->mdsc;
|
|
struct ceph_snap_realm *oldrealm = ci->i_snap_realm;
|
|
|
|
lockdep_assert_held(&ci->i_ceph_lock);
|
|
diff --git a/fs/ceph/super.c b/fs/ceph/super.c
|
|
index 2d7f5a8d4a926..52af90beab000 100644
|
|
--- a/fs/ceph/super.c
|
|
+++ b/fs/ceph/super.c
|
|
@@ -44,7 +44,7 @@ static LIST_HEAD(ceph_fsc_list);
|
|
*/
|
|
static void ceph_put_super(struct super_block *s)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(s);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(s);
|
|
|
|
dout("put_super\n");
|
|
ceph_fscrypt_free_dummy_policy(fsc);
|
|
@@ -53,7 +53,7 @@ static void ceph_put_super(struct super_block *s)
|
|
|
|
static int ceph_statfs(struct dentry *dentry, struct kstatfs *buf)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(d_inode(dentry));
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(d_inode(dentry));
|
|
struct ceph_mon_client *monc = &fsc->client->monc;
|
|
struct ceph_statfs st;
|
|
int i, err;
|
|
@@ -118,7 +118,7 @@ static int ceph_statfs(struct dentry *dentry, struct kstatfs *buf)
|
|
|
|
static int ceph_sync_fs(struct super_block *sb, int wait)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
|
|
|
|
if (!wait) {
|
|
dout("sync_fs (non-blocking)\n");
|
|
@@ -684,7 +684,7 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
|
|
*/
|
|
static int ceph_show_options(struct seq_file *m, struct dentry *root)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(root->d_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(root->d_sb);
|
|
struct ceph_mount_options *fsopt = fsc->mount_options;
|
|
size_t pos;
|
|
int ret;
|
|
@@ -1015,7 +1015,7 @@ static void __ceph_umount_begin(struct ceph_fs_client *fsc)
|
|
*/
|
|
void ceph_umount_begin(struct super_block *sb)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
|
|
|
|
dout("ceph_umount_begin - starting forced umount\n");
|
|
if (!fsc)
|
|
@@ -1226,7 +1226,7 @@ static int ceph_compare_super(struct super_block *sb, struct fs_context *fc)
|
|
struct ceph_fs_client *new = fc->s_fs_info;
|
|
struct ceph_mount_options *fsopt = new->mount_options;
|
|
struct ceph_options *opt = new->client->options;
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
|
|
|
|
dout("ceph_compare_super %p\n", sb);
|
|
|
|
@@ -1322,9 +1322,9 @@ static int ceph_get_tree(struct fs_context *fc)
|
|
goto out;
|
|
}
|
|
|
|
- if (ceph_sb_to_client(sb) != fsc) {
|
|
+ if (ceph_sb_to_fs_client(sb) != fsc) {
|
|
destroy_fs_client(fsc);
|
|
- fsc = ceph_sb_to_client(sb);
|
|
+ fsc = ceph_sb_to_fs_client(sb);
|
|
dout("get_sb got existing client %p\n", fsc);
|
|
} else {
|
|
dout("get_sb using new client %p\n", fsc);
|
|
@@ -1377,7 +1377,7 @@ static int ceph_reconfigure_fc(struct fs_context *fc)
|
|
struct ceph_parse_opts_ctx *pctx = fc->fs_private;
|
|
struct ceph_mount_options *fsopt = pctx->opts;
|
|
struct super_block *sb = fc->root->d_sb;
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
|
|
|
|
err = ceph_apply_test_dummy_encryption(sb, fc, fsopt);
|
|
if (err)
|
|
@@ -1516,7 +1516,7 @@ void ceph_dec_osd_stopping_blocker(struct ceph_mds_client *mdsc)
|
|
|
|
static void ceph_kill_sb(struct super_block *s)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(s);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(s);
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
bool wait;
|
|
|
|
@@ -1578,7 +1578,7 @@ MODULE_ALIAS_FS("ceph");
|
|
|
|
int ceph_force_reconnect(struct super_block *sb)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(sb);
|
|
int err = 0;
|
|
|
|
fsc->mount_state = CEPH_MOUNT_RECOVER;
|
|
diff --git a/fs/ceph/super.h b/fs/ceph/super.h
|
|
index 51c7f2b14f6f8..8efd4ba607744 100644
|
|
--- a/fs/ceph/super.h
|
|
+++ b/fs/ceph/super.h
|
|
@@ -488,13 +488,13 @@ ceph_inode(const struct inode *inode)
|
|
}
|
|
|
|
static inline struct ceph_fs_client *
|
|
-ceph_inode_to_client(const struct inode *inode)
|
|
+ceph_inode_to_fs_client(const struct inode *inode)
|
|
{
|
|
return (struct ceph_fs_client *)inode->i_sb->s_fs_info;
|
|
}
|
|
|
|
static inline struct ceph_fs_client *
|
|
-ceph_sb_to_client(const struct super_block *sb)
|
|
+ceph_sb_to_fs_client(const struct super_block *sb)
|
|
{
|
|
return (struct ceph_fs_client *)sb->s_fs_info;
|
|
}
|
|
@@ -502,7 +502,7 @@ ceph_sb_to_client(const struct super_block *sb)
|
|
static inline struct ceph_mds_client *
|
|
ceph_sb_to_mdsc(const struct super_block *sb)
|
|
{
|
|
- return (struct ceph_mds_client *)ceph_sb_to_client(sb)->mdsc;
|
|
+ return (struct ceph_mds_client *)ceph_sb_to_fs_client(sb)->mdsc;
|
|
}
|
|
|
|
static inline struct ceph_vino
|
|
@@ -558,7 +558,7 @@ static inline u64 ceph_snap(struct inode *inode)
|
|
*/
|
|
static inline u64 ceph_present_ino(struct super_block *sb, u64 ino)
|
|
{
|
|
- if (unlikely(ceph_test_mount_opt(ceph_sb_to_client(sb), INO32)))
|
|
+ if (unlikely(ceph_test_mount_opt(ceph_sb_to_fs_client(sb), INO32)))
|
|
return ceph_ino_to_ino32(ino);
|
|
return ino;
|
|
}
|
|
@@ -1106,7 +1106,7 @@ void ceph_inode_shutdown(struct inode *inode);
|
|
static inline bool ceph_inode_is_shutdown(struct inode *inode)
|
|
{
|
|
unsigned long flags = READ_ONCE(ceph_inode(inode)->i_ceph_flags);
|
|
- struct ceph_fs_client *fsc = ceph_inode_to_client(inode);
|
|
+ struct ceph_fs_client *fsc = ceph_inode_to_fs_client(inode);
|
|
int state = READ_ONCE(fsc->mount_state);
|
|
|
|
return (flags & CEPH_I_SHUTDOWN) || state >= CEPH_MOUNT_SHUTDOWN;
|
|
@@ -1223,7 +1223,8 @@ extern void ceph_add_cap(struct inode *inode,
|
|
unsigned cap, unsigned seq, u64 realmino, int flags,
|
|
struct ceph_cap **new_cap);
|
|
extern void __ceph_remove_cap(struct ceph_cap *cap, bool queue_release);
|
|
-extern void ceph_remove_cap(struct ceph_cap *cap, bool queue_release);
|
|
+extern void ceph_remove_cap(struct ceph_mds_client *mdsc, struct ceph_cap *cap,
|
|
+ bool queue_release);
|
|
extern void __ceph_remove_caps(struct ceph_inode_info *ci);
|
|
extern void ceph_put_cap(struct ceph_mds_client *mdsc,
|
|
struct ceph_cap *cap);
|
|
diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c
|
|
index 0deae4a0f5f16..558f64554b591 100644
|
|
--- a/fs/ceph/xattr.c
|
|
+++ b/fs/ceph/xattr.c
|
|
@@ -57,7 +57,7 @@ static bool ceph_vxattrcb_layout_exists(struct ceph_inode_info *ci)
|
|
static ssize_t ceph_vxattrcb_layout(struct ceph_inode_info *ci, char *val,
|
|
size_t size)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(ci->netfs.inode.i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(ci->netfs.inode.i_sb);
|
|
struct ceph_osd_client *osdc = &fsc->client->osdc;
|
|
struct ceph_string *pool_ns;
|
|
s64 pool = ci->i_layout.pool_id;
|
|
@@ -161,7 +161,7 @@ static ssize_t ceph_vxattrcb_layout_pool(struct ceph_inode_info *ci,
|
|
char *val, size_t size)
|
|
{
|
|
ssize_t ret;
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(ci->netfs.inode.i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(ci->netfs.inode.i_sb);
|
|
struct ceph_osd_client *osdc = &fsc->client->osdc;
|
|
s64 pool = ci->i_layout.pool_id;
|
|
const char *pool_name;
|
|
@@ -313,7 +313,7 @@ static ssize_t ceph_vxattrcb_snap_btime(struct ceph_inode_info *ci, char *val,
|
|
static ssize_t ceph_vxattrcb_cluster_fsid(struct ceph_inode_info *ci,
|
|
char *val, size_t size)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(ci->netfs.inode.i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(ci->netfs.inode.i_sb);
|
|
|
|
return ceph_fmt_xattr(val, size, "%pU", &fsc->client->fsid);
|
|
}
|
|
@@ -321,7 +321,7 @@ static ssize_t ceph_vxattrcb_cluster_fsid(struct ceph_inode_info *ci,
|
|
static ssize_t ceph_vxattrcb_client_id(struct ceph_inode_info *ci,
|
|
char *val, size_t size)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(ci->netfs.inode.i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(ci->netfs.inode.i_sb);
|
|
|
|
return ceph_fmt_xattr(val, size, "client%lld",
|
|
ceph_client_gid(fsc->client));
|
|
@@ -1094,7 +1094,7 @@ ssize_t ceph_listxattr(struct dentry *dentry, char *names, size_t size)
|
|
static int ceph_sync_setxattr(struct inode *inode, const char *name,
|
|
const char *value, size_t size, int flags)
|
|
{
|
|
- struct ceph_fs_client *fsc = ceph_sb_to_client(inode->i_sb);
|
|
+ struct ceph_fs_client *fsc = ceph_sb_to_fs_client(inode->i_sb);
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
struct ceph_mds_request *req;
|
|
struct ceph_mds_client *mdsc = fsc->mdsc;
|
|
@@ -1164,7 +1164,7 @@ int __ceph_setxattr(struct inode *inode, const char *name,
|
|
{
|
|
struct ceph_vxattr *vxattr;
|
|
struct ceph_inode_info *ci = ceph_inode(inode);
|
|
- struct ceph_mds_client *mdsc = ceph_sb_to_client(inode->i_sb)->mdsc;
|
|
+ struct ceph_mds_client *mdsc = ceph_sb_to_fs_client(inode->i_sb)->mdsc;
|
|
struct ceph_cap_flush *prealloc_cf = NULL;
|
|
struct ceph_buffer *old_blob = NULL;
|
|
int issued;
|
|
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
|
|
index a8a7fc0e17547..c1703d2c4bf9a 100644
|
|
--- a/fs/fuse/dir.c
|
|
+++ b/fs/fuse/dir.c
|
|
@@ -1317,6 +1317,7 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
|
|
err = fuse_do_statx(inode, file, stat);
|
|
if (err == -ENOSYS) {
|
|
fc->no_statx = 1;
|
|
+ err = 0;
|
|
goto retry;
|
|
}
|
|
} else {
|
|
diff --git a/fs/nilfs2/dir.c b/fs/nilfs2/dir.c
|
|
index bce734b68f08e..929edc0b101a0 100644
|
|
--- a/fs/nilfs2/dir.c
|
|
+++ b/fs/nilfs2/dir.c
|
|
@@ -243,7 +243,7 @@ nilfs_filetype_table[NILFS_FT_MAX] = {
|
|
|
|
#define S_SHIFT 12
|
|
static unsigned char
|
|
-nilfs_type_by_mode[S_IFMT >> S_SHIFT] = {
|
|
+nilfs_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = {
|
|
[S_IFREG >> S_SHIFT] = NILFS_FT_REG_FILE,
|
|
[S_IFDIR >> S_SHIFT] = NILFS_FT_DIR,
|
|
[S_IFCHR >> S_SHIFT] = NILFS_FT_CHRDEV,
|
|
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
|
|
index 6c85edb8635d0..c53d516459fc4 100644
|
|
--- a/fs/smb/client/cifs_debug.c
|
|
+++ b/fs/smb/client/cifs_debug.c
|
|
@@ -663,6 +663,7 @@ static ssize_t cifs_stats_proc_write(struct file *file,
|
|
spin_lock(&tcon->stat_lock);
|
|
tcon->bytes_read = 0;
|
|
tcon->bytes_written = 0;
|
|
+ tcon->stats_from_time = ktime_get_real_seconds();
|
|
spin_unlock(&tcon->stat_lock);
|
|
if (server->ops->clear_stats)
|
|
server->ops->clear_stats(tcon);
|
|
@@ -743,8 +744,9 @@ static int cifs_stats_proc_show(struct seq_file *m, void *v)
|
|
seq_printf(m, "\n%d) %s", i, tcon->tree_name);
|
|
if (tcon->need_reconnect)
|
|
seq_puts(m, "\tDISCONNECTED ");
|
|
- seq_printf(m, "\nSMBs: %d",
|
|
- atomic_read(&tcon->num_smbs_sent));
|
|
+ seq_printf(m, "\nSMBs: %d since %ptTs UTC",
|
|
+ atomic_read(&tcon->num_smbs_sent),
|
|
+ &tcon->stats_from_time);
|
|
if (server->ops->print_stats)
|
|
server->ops->print_stats(m, tcon);
|
|
}
|
|
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
|
|
index a878b1e5aa313..68fd61a564089 100644
|
|
--- a/fs/smb/client/cifsglob.h
|
|
+++ b/fs/smb/client/cifsglob.h
|
|
@@ -1208,6 +1208,7 @@ struct cifs_tcon {
|
|
__u64 bytes_read;
|
|
__u64 bytes_written;
|
|
spinlock_t stat_lock; /* protects the two fields above */
|
|
+ time64_t stats_from_time;
|
|
FILE_SYSTEM_DEVICE_INFO fsDevInfo;
|
|
FILE_SYSTEM_ATTRIBUTE_INFO fsAttrInfo; /* ok if fs name truncated */
|
|
FILE_SYSTEM_UNIX_INFO fsUnixInfo;
|
|
@@ -1252,7 +1253,6 @@ struct cifs_tcon {
|
|
struct cached_fids *cfids;
|
|
/* BB add field for back pointer to sb struct(s)? */
|
|
#ifdef CONFIG_CIFS_DFS_UPCALL
|
|
- struct list_head dfs_ses_list;
|
|
struct delayed_work dfs_cache_work;
|
|
#endif
|
|
struct delayed_work query_interfaces; /* query interfaces workqueue job */
|
|
@@ -1774,7 +1774,6 @@ struct cifs_mount_ctx {
|
|
struct TCP_Server_Info *server;
|
|
struct cifs_ses *ses;
|
|
struct cifs_tcon *tcon;
|
|
- struct list_head dfs_ses_list;
|
|
};
|
|
|
|
static inline void __free_dfs_info_param(struct dfs_info3_param *param)
|
|
diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
|
|
index ed257612bf0bc..1bdad33580b57 100644
|
|
--- a/fs/smb/client/cifsproto.h
|
|
+++ b/fs/smb/client/cifsproto.h
|
|
@@ -716,31 +716,31 @@ struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon);
|
|
void cifs_put_tcon_super(struct super_block *sb);
|
|
int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
|
|
|
|
-/* Put references of @ses and @ses->dfs_root_ses */
|
|
+/* Put references of @ses and its children */
|
|
static inline void cifs_put_smb_ses(struct cifs_ses *ses)
|
|
{
|
|
- struct cifs_ses *rses = ses->dfs_root_ses;
|
|
+ struct cifs_ses *next;
|
|
|
|
- __cifs_put_smb_ses(ses);
|
|
- if (rses)
|
|
- __cifs_put_smb_ses(rses);
|
|
+ do {
|
|
+ next = ses->dfs_root_ses;
|
|
+ __cifs_put_smb_ses(ses);
|
|
+ } while ((ses = next));
|
|
}
|
|
|
|
-/* Get an active reference of @ses and @ses->dfs_root_ses.
|
|
+/* Get an active reference of @ses and its children.
|
|
*
|
|
* NOTE: make sure to call this function when incrementing reference count of
|
|
* @ses to ensure that any DFS root session attached to it (@ses->dfs_root_ses)
|
|
* will also get its reference count incremented.
|
|
*
|
|
- * cifs_put_smb_ses() will put both references, so call it when you're done.
|
|
+ * cifs_put_smb_ses() will put all references, so call it when you're done.
|
|
*/
|
|
static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
|
|
{
|
|
lockdep_assert_held(&cifs_tcp_ses_lock);
|
|
|
|
- ses->ses_count++;
|
|
- if (ses->dfs_root_ses)
|
|
- ses->dfs_root_ses->ses_count++;
|
|
+ for (; ses; ses = ses->dfs_root_ses)
|
|
+ ses->ses_count++;
|
|
}
|
|
|
|
static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
|
|
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
|
|
index 97776dd12b6b8..c5705de7f9de2 100644
|
|
--- a/fs/smb/client/connect.c
|
|
+++ b/fs/smb/client/connect.c
|
|
@@ -237,7 +237,13 @@ cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
|
|
|
|
spin_lock(&cifs_tcp_ses_lock);
|
|
list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
|
|
- /* check if iface is still active */
|
|
+ spin_lock(&ses->ses_lock);
|
|
+ if (ses->ses_status == SES_EXITING) {
|
|
+ spin_unlock(&ses->ses_lock);
|
|
+ continue;
|
|
+ }
|
|
+ spin_unlock(&ses->ses_lock);
|
|
+
|
|
spin_lock(&ses->chan_lock);
|
|
if (cifs_ses_get_chan_index(ses, server) ==
|
|
CIFS_INVAL_CHAN_INDEX) {
|
|
@@ -1857,6 +1863,9 @@ static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
|
|
ctx->sectype != ses->sectype)
|
|
return 0;
|
|
|
|
+ if (ctx->dfs_root_ses != ses->dfs_root_ses)
|
|
+ return 0;
|
|
+
|
|
/*
|
|
* If an existing session is limited to less channels than
|
|
* requested, it should not be reused
|
|
@@ -1960,31 +1969,6 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx)
|
|
return rc;
|
|
}
|
|
|
|
-/**
|
|
- * cifs_free_ipc - helper to release the session IPC tcon
|
|
- * @ses: smb session to unmount the IPC from
|
|
- *
|
|
- * Needs to be called everytime a session is destroyed.
|
|
- *
|
|
- * On session close, the IPC is closed and the server must release all tcons of the session.
|
|
- * No need to send a tree disconnect here.
|
|
- *
|
|
- * Besides, it will make the server to not close durable and resilient files on session close, as
|
|
- * specified in MS-SMB2 3.3.5.6 Receiving an SMB2 LOGOFF Request.
|
|
- */
|
|
-static int
|
|
-cifs_free_ipc(struct cifs_ses *ses)
|
|
-{
|
|
- struct cifs_tcon *tcon = ses->tcon_ipc;
|
|
-
|
|
- if (tcon == NULL)
|
|
- return 0;
|
|
-
|
|
- tconInfoFree(tcon);
|
|
- ses->tcon_ipc = NULL;
|
|
- return 0;
|
|
-}
|
|
-
|
|
static struct cifs_ses *
|
|
cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
|
|
{
|
|
@@ -2015,68 +1999,67 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
|
|
|
|
void __cifs_put_smb_ses(struct cifs_ses *ses)
|
|
{
|
|
- unsigned int rc, xid;
|
|
- unsigned int chan_count;
|
|
struct TCP_Server_Info *server = ses->server;
|
|
+ struct cifs_tcon *tcon;
|
|
+ unsigned int xid;
|
|
+ size_t i;
|
|
+ bool do_logoff;
|
|
+ int rc;
|
|
|
|
+ spin_lock(&cifs_tcp_ses_lock);
|
|
spin_lock(&ses->ses_lock);
|
|
- if (ses->ses_status == SES_EXITING) {
|
|
+ cifs_dbg(FYI, "%s: id=0x%llx ses_count=%d ses_status=%u ipc=%s\n",
|
|
+ __func__, ses->Suid, ses->ses_count, ses->ses_status,
|
|
+ ses->tcon_ipc ? ses->tcon_ipc->tree_name : "none");
|
|
+ if (ses->ses_status == SES_EXITING || --ses->ses_count > 0) {
|
|
spin_unlock(&ses->ses_lock);
|
|
+ spin_unlock(&cifs_tcp_ses_lock);
|
|
return;
|
|
}
|
|
- spin_unlock(&ses->ses_lock);
|
|
+ /* ses_count can never go negative */
|
|
+ WARN_ON(ses->ses_count < 0);
|
|
|
|
- cifs_dbg(FYI, "%s: ses_count=%d\n", __func__, ses->ses_count);
|
|
- cifs_dbg(FYI,
|
|
- "%s: ses ipc: %s\n", __func__, ses->tcon_ipc ? ses->tcon_ipc->tree_name : "NONE");
|
|
+ spin_lock(&ses->chan_lock);
|
|
+ cifs_chan_clear_need_reconnect(ses, server);
|
|
+ spin_unlock(&ses->chan_lock);
|
|
|
|
- spin_lock(&cifs_tcp_ses_lock);
|
|
- if (--ses->ses_count > 0) {
|
|
- spin_unlock(&cifs_tcp_ses_lock);
|
|
- return;
|
|
- }
|
|
- spin_lock(&ses->ses_lock);
|
|
- if (ses->ses_status == SES_GOOD)
|
|
- ses->ses_status = SES_EXITING;
|
|
+ do_logoff = ses->ses_status == SES_GOOD && server->ops->logoff;
|
|
+ ses->ses_status = SES_EXITING;
|
|
+ tcon = ses->tcon_ipc;
|
|
+ ses->tcon_ipc = NULL;
|
|
spin_unlock(&ses->ses_lock);
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
|
|
- /* ses_count can never go negative */
|
|
- WARN_ON(ses->ses_count < 0);
|
|
-
|
|
- spin_lock(&ses->ses_lock);
|
|
- if (ses->ses_status == SES_EXITING && server->ops->logoff) {
|
|
- spin_unlock(&ses->ses_lock);
|
|
- cifs_free_ipc(ses);
|
|
+ /*
|
|
+ * On session close, the IPC is closed and the server must release all
|
|
+ * tcons of the session. No need to send a tree disconnect here.
|
|
+ *
|
|
+ * Besides, it will make the server to not close durable and resilient
|
|
+ * files on session close, as specified in MS-SMB2 3.3.5.6 Receiving an
|
|
+ * SMB2 LOGOFF Request.
|
|
+ */
|
|
+ tconInfoFree(tcon);
|
|
+ if (do_logoff) {
|
|
xid = get_xid();
|
|
rc = server->ops->logoff(xid, ses);
|
|
if (rc)
|
|
cifs_server_dbg(VFS, "%s: Session Logoff failure rc=%d\n",
|
|
__func__, rc);
|
|
_free_xid(xid);
|
|
- } else {
|
|
- spin_unlock(&ses->ses_lock);
|
|
- cifs_free_ipc(ses);
|
|
}
|
|
|
|
spin_lock(&cifs_tcp_ses_lock);
|
|
list_del_init(&ses->smb_ses_list);
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
|
|
- chan_count = ses->chan_count;
|
|
-
|
|
/* close any extra channels */
|
|
- if (chan_count > 1) {
|
|
- int i;
|
|
-
|
|
- for (i = 1; i < chan_count; i++) {
|
|
- if (ses->chans[i].iface) {
|
|
- kref_put(&ses->chans[i].iface->refcount, release_iface);
|
|
- ses->chans[i].iface = NULL;
|
|
- }
|
|
- cifs_put_tcp_session(ses->chans[i].server, 0);
|
|
- ses->chans[i].server = NULL;
|
|
+ for (i = 1; i < ses->chan_count; i++) {
|
|
+ if (ses->chans[i].iface) {
|
|
+ kref_put(&ses->chans[i].iface->refcount, release_iface);
|
|
+ ses->chans[i].iface = NULL;
|
|
}
|
|
+ cifs_put_tcp_session(ses->chans[i].server, 0);
|
|
+ ses->chans[i].server = NULL;
|
|
}
|
|
|
|
/* we now account for primary channel in iface->refcount */
|
|
@@ -2375,9 +2358,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
|
|
* need to lock before changing something in the session.
|
|
*/
|
|
spin_lock(&cifs_tcp_ses_lock);
|
|
+ if (ctx->dfs_root_ses)
|
|
+ cifs_smb_ses_inc_refcount(ctx->dfs_root_ses);
|
|
ses->dfs_root_ses = ctx->dfs_root_ses;
|
|
- if (ses->dfs_root_ses)
|
|
- ses->dfs_root_ses->ses_count++;
|
|
list_add(&ses->smb_ses_list, &server->smb_ses_list);
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
|
|
@@ -3321,6 +3304,9 @@ void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx)
|
|
cifs_put_smb_ses(mnt_ctx->ses);
|
|
else if (mnt_ctx->server)
|
|
cifs_put_tcp_session(mnt_ctx->server, 0);
|
|
+ mnt_ctx->ses = NULL;
|
|
+ mnt_ctx->tcon = NULL;
|
|
+ mnt_ctx->server = NULL;
|
|
mnt_ctx->cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
|
|
free_xid(mnt_ctx->xid);
|
|
}
|
|
@@ -3599,8 +3585,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
|
|
bool isdfs;
|
|
int rc;
|
|
|
|
- INIT_LIST_HEAD(&mnt_ctx.dfs_ses_list);
|
|
-
|
|
rc = dfs_mount_share(&mnt_ctx, &isdfs);
|
|
if (rc)
|
|
goto error;
|
|
@@ -3631,7 +3615,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
|
|
return rc;
|
|
|
|
error:
|
|
- dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
|
|
cifs_mount_put_conns(&mnt_ctx);
|
|
return rc;
|
|
}
|
|
@@ -3646,6 +3629,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
|
|
goto error;
|
|
|
|
rc = cifs_mount_get_tcon(&mnt_ctx);
|
|
+ if (!rc) {
|
|
+ /*
|
|
+ * Prevent superblock from being created with any missing
|
|
+ * connections.
|
|
+ */
|
|
+ if (WARN_ON(!mnt_ctx.server))
|
|
+ rc = -EHOSTDOWN;
|
|
+ else if (WARN_ON(!mnt_ctx.ses))
|
|
+ rc = -EACCES;
|
|
+ else if (WARN_ON(!mnt_ctx.tcon))
|
|
+ rc = -ENOENT;
|
|
+ }
|
|
if (rc)
|
|
goto error;
|
|
|
|
diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c
|
|
index 449c59830039b..3ec965547e3d4 100644
|
|
--- a/fs/smb/client/dfs.c
|
|
+++ b/fs/smb/client/dfs.c
|
|
@@ -66,33 +66,20 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
|
|
}
|
|
|
|
/*
|
|
- * Track individual DFS referral servers used by new DFS mount.
|
|
- *
|
|
- * On success, their lifetime will be shared by final tcon (dfs_ses_list).
|
|
- * Otherwise, they will be put by dfs_put_root_smb_sessions() in cifs_mount().
|
|
+ * Get an active reference of @ses so that next call to cifs_put_tcon() won't
|
|
+ * release it as any new DFS referrals must go through its IPC tcon.
|
|
*/
|
|
-static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
|
|
+static void add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
|
|
{
|
|
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
|
- struct dfs_root_ses *root_ses;
|
|
struct cifs_ses *ses = mnt_ctx->ses;
|
|
|
|
if (ses) {
|
|
- root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
|
|
- if (!root_ses)
|
|
- return -ENOMEM;
|
|
-
|
|
- INIT_LIST_HEAD(&root_ses->list);
|
|
-
|
|
spin_lock(&cifs_tcp_ses_lock);
|
|
cifs_smb_ses_inc_refcount(ses);
|
|
spin_unlock(&cifs_tcp_ses_lock);
|
|
- root_ses->ses = ses;
|
|
- list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
|
|
}
|
|
- /* Select new DFS referral server so that new referrals go through it */
|
|
ctx->dfs_root_ses = ses;
|
|
- return 0;
|
|
}
|
|
|
|
static inline int parse_dfs_target(struct smb3_fs_context *ctx,
|
|
@@ -185,11 +172,8 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
|
|
continue;
|
|
}
|
|
|
|
- if (is_refsrv) {
|
|
- rc = add_root_smb_session(mnt_ctx);
|
|
- if (rc)
|
|
- goto out;
|
|
- }
|
|
+ if (is_refsrv)
|
|
+ add_root_smb_session(mnt_ctx);
|
|
|
|
rc = ref_walk_advance(rw);
|
|
if (!rc) {
|
|
@@ -232,6 +216,7 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
|
|
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
|
struct cifs_tcon *tcon;
|
|
char *origin_fullpath;
|
|
+ bool new_tcon = true;
|
|
int rc;
|
|
|
|
origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
|
|
@@ -239,6 +224,18 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
|
|
return PTR_ERR(origin_fullpath);
|
|
|
|
rc = dfs_referral_walk(mnt_ctx);
|
|
+ if (!rc) {
|
|
+ /*
|
|
+ * Prevent superblock from being created with any missing
|
|
+ * connections.
|
|
+ */
|
|
+ if (WARN_ON(!mnt_ctx->server))
|
|
+ rc = -EHOSTDOWN;
|
|
+ else if (WARN_ON(!mnt_ctx->ses))
|
|
+ rc = -EACCES;
|
|
+ else if (WARN_ON(!mnt_ctx->tcon))
|
|
+ rc = -ENOENT;
|
|
+ }
|
|
if (rc)
|
|
goto out;
|
|
|
|
@@ -247,15 +244,14 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
|
|
if (!tcon->origin_fullpath) {
|
|
tcon->origin_fullpath = origin_fullpath;
|
|
origin_fullpath = NULL;
|
|
+ } else {
|
|
+ new_tcon = false;
|
|
}
|
|
spin_unlock(&tcon->tc_lock);
|
|
|
|
- if (list_empty(&tcon->dfs_ses_list)) {
|
|
- list_replace_init(&mnt_ctx->dfs_ses_list, &tcon->dfs_ses_list);
|
|
+ if (new_tcon) {
|
|
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
|
|
dfs_cache_get_ttl() * HZ);
|
|
- } else {
|
|
- dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
|
|
}
|
|
|
|
out:
|
|
@@ -298,7 +294,6 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
|
|
if (rc)
|
|
return rc;
|
|
|
|
- ctx->dfs_root_ses = mnt_ctx->ses;
|
|
/*
|
|
* If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
|
|
* try to get an DFS referral (even cached) to determine whether it is an DFS mount.
|
|
@@ -324,7 +319,9 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
|
|
|
|
*isdfs = true;
|
|
add_root_smb_session(mnt_ctx);
|
|
- return __dfs_mount_share(mnt_ctx);
|
|
+ rc = __dfs_mount_share(mnt_ctx);
|
|
+ dfs_put_root_smb_sessions(mnt_ctx);
|
|
+ return rc;
|
|
}
|
|
|
|
/* Update dfs referral path of superblock */
|
|
diff --git a/fs/smb/client/dfs.h b/fs/smb/client/dfs.h
|
|
index 875ab7ae57fcd..e5c4dcf837503 100644
|
|
--- a/fs/smb/client/dfs.h
|
|
+++ b/fs/smb/client/dfs.h
|
|
@@ -7,7 +7,9 @@
|
|
#define _CIFS_DFS_H
|
|
|
|
#include "cifsglob.h"
|
|
+#include "cifsproto.h"
|
|
#include "fs_context.h"
|
|
+#include "dfs_cache.h"
|
|
#include "cifs_unicode.h"
|
|
#include <linux/namei.h>
|
|
|
|
@@ -114,11 +116,6 @@ static inline void ref_walk_set_tgt_hint(struct dfs_ref_walk *rw)
|
|
ref_walk_tit(rw));
|
|
}
|
|
|
|
-struct dfs_root_ses {
|
|
- struct list_head list;
|
|
- struct cifs_ses *ses;
|
|
-};
|
|
-
|
|
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
|
|
struct smb3_fs_context *ctx);
|
|
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
|
|
@@ -133,20 +130,32 @@ static inline int dfs_get_referral(struct cifs_mount_ctx *mnt_ctx, const char *p
|
|
{
|
|
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
|
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
|
|
+ struct cifs_ses *rses = ctx->dfs_root_ses ?: mnt_ctx->ses;
|
|
|
|
- return dfs_cache_find(mnt_ctx->xid, ctx->dfs_root_ses, cifs_sb->local_nls,
|
|
+ return dfs_cache_find(mnt_ctx->xid, rses, cifs_sb->local_nls,
|
|
cifs_remap(cifs_sb), path, ref, tl);
|
|
}
|
|
|
|
-static inline void dfs_put_root_smb_sessions(struct list_head *head)
|
|
+/*
|
|
+ * cifs_get_smb_ses() already guarantees an active reference of
|
|
+ * @ses->dfs_root_ses when a new session is created, so we need to put extra
|
|
+ * references of all DFS root sessions that were used across the mount process
|
|
+ * in dfs_mount_share().
|
|
+ */
|
|
+static inline void dfs_put_root_smb_sessions(struct cifs_mount_ctx *mnt_ctx)
|
|
{
|
|
- struct dfs_root_ses *root, *tmp;
|
|
+ const struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
|
|
+ struct cifs_ses *ses = ctx->dfs_root_ses;
|
|
+ struct cifs_ses *cur;
|
|
+
|
|
+ if (!ses)
|
|
+ return;
|
|
|
|
- list_for_each_entry_safe(root, tmp, head, list) {
|
|
- list_del_init(&root->list);
|
|
- cifs_put_smb_ses(root->ses);
|
|
- kfree(root);
|
|
+ for (cur = ses; cur; cur = cur->dfs_root_ses) {
|
|
+ if (cur->dfs_root_ses)
|
|
+ cifs_put_smb_ses(cur->dfs_root_ses);
|
|
}
|
|
+ cifs_put_smb_ses(ses);
|
|
}
|
|
|
|
#endif /* _CIFS_DFS_H */
|
|
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
|
|
index 508d831fabe37..11c8efecf7aa1 100644
|
|
--- a/fs/smb/client/dfs_cache.c
|
|
+++ b/fs/smb/client/dfs_cache.c
|
|
@@ -1172,8 +1172,8 @@ static bool is_ses_good(struct cifs_ses *ses)
|
|
return ret;
|
|
}
|
|
|
|
-/* Refresh dfs referral of tcon and mark it for reconnect if needed */
|
|
-static int __refresh_tcon(const char *path, struct cifs_ses *ses, bool force_refresh)
|
|
+/* Refresh dfs referral of @ses and mark it for reconnect if needed */
|
|
+static void __refresh_ses_referral(struct cifs_ses *ses, bool force_refresh)
|
|
{
|
|
struct TCP_Server_Info *server = ses->server;
|
|
DFS_CACHE_TGT_LIST(old_tl);
|
|
@@ -1181,10 +1181,21 @@ static int __refresh_tcon(const char *path, struct cifs_ses *ses, bool force_ref
|
|
bool needs_refresh = false;
|
|
struct cache_entry *ce;
|
|
unsigned int xid;
|
|
+ char *path = NULL;
|
|
int rc = 0;
|
|
|
|
xid = get_xid();
|
|
|
|
+ mutex_lock(&server->refpath_lock);
|
|
+ if (server->leaf_fullpath) {
|
|
+ path = kstrdup(server->leaf_fullpath + 1, GFP_ATOMIC);
|
|
+ if (!path)
|
|
+ rc = -ENOMEM;
|
|
+ }
|
|
+ mutex_unlock(&server->refpath_lock);
|
|
+ if (!path)
|
|
+ goto out;
|
|
+
|
|
down_read(&htable_rw_lock);
|
|
ce = lookup_cache_entry(path);
|
|
needs_refresh = force_refresh || IS_ERR(ce) || cache_entry_expired(ce);
|
|
@@ -1218,19 +1229,17 @@ static int __refresh_tcon(const char *path, struct cifs_ses *ses, bool force_ref
|
|
free_xid(xid);
|
|
dfs_cache_free_tgts(&old_tl);
|
|
dfs_cache_free_tgts(&new_tl);
|
|
- return rc;
|
|
+ kfree(path);
|
|
}
|
|
|
|
-static int refresh_tcon(struct cifs_tcon *tcon, bool force_refresh)
|
|
+static inline void refresh_ses_referral(struct cifs_ses *ses)
|
|
{
|
|
- struct TCP_Server_Info *server = tcon->ses->server;
|
|
- struct cifs_ses *ses = tcon->ses;
|
|
+ __refresh_ses_referral(ses, false);
|
|
+}
|
|
|
|
- mutex_lock(&server->refpath_lock);
|
|
- if (server->leaf_fullpath)
|
|
- __refresh_tcon(server->leaf_fullpath + 1, ses, force_refresh);
|
|
- mutex_unlock(&server->refpath_lock);
|
|
- return 0;
|
|
+static inline void force_refresh_ses_referral(struct cifs_ses *ses)
|
|
+{
|
|
+ __refresh_ses_referral(ses, true);
|
|
}
|
|
|
|
/**
|
|
@@ -1271,34 +1280,20 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
|
|
*/
|
|
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
|
|
|
|
- return refresh_tcon(tcon, true);
|
|
+ force_refresh_ses_referral(tcon->ses);
|
|
+ return 0;
|
|
}
|
|
|
|
/* Refresh all DFS referrals related to DFS tcon */
|
|
void dfs_cache_refresh(struct work_struct *work)
|
|
{
|
|
- struct TCP_Server_Info *server;
|
|
- struct dfs_root_ses *rses;
|
|
struct cifs_tcon *tcon;
|
|
struct cifs_ses *ses;
|
|
|
|
tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
|
|
- ses = tcon->ses;
|
|
- server = ses->server;
|
|
|
|
- mutex_lock(&server->refpath_lock);
|
|
- if (server->leaf_fullpath)
|
|
- __refresh_tcon(server->leaf_fullpath + 1, ses, false);
|
|
- mutex_unlock(&server->refpath_lock);
|
|
-
|
|
- list_for_each_entry(rses, &tcon->dfs_ses_list, list) {
|
|
- ses = rses->ses;
|
|
- server = ses->server;
|
|
- mutex_lock(&server->refpath_lock);
|
|
- if (server->leaf_fullpath)
|
|
- __refresh_tcon(server->leaf_fullpath + 1, ses, false);
|
|
- mutex_unlock(&server->refpath_lock);
|
|
- }
|
|
+ for (ses = tcon->ses; ses; ses = ses->dfs_root_ses)
|
|
+ refresh_ses_referral(ses);
|
|
|
|
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
|
|
atomic_read(&dfs_cache_ttl) * HZ);
|
|
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
|
|
index ef573e3f8e52a..74627d647818a 100644
|
|
--- a/fs/smb/client/misc.c
|
|
+++ b/fs/smb/client/misc.c
|
|
@@ -140,9 +140,7 @@ tcon_info_alloc(bool dir_leases_enabled)
|
|
spin_lock_init(&ret_buf->stat_lock);
|
|
atomic_set(&ret_buf->num_local_opens, 0);
|
|
atomic_set(&ret_buf->num_remote_opens, 0);
|
|
-#ifdef CONFIG_CIFS_DFS_UPCALL
|
|
- INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
|
|
-#endif
|
|
+ ret_buf->stats_from_time = ktime_get_real_seconds();
|
|
|
|
return ret_buf;
|
|
}
|
|
@@ -158,9 +156,6 @@ tconInfoFree(struct cifs_tcon *tcon)
|
|
atomic_dec(&tconInfoAllocCount);
|
|
kfree(tcon->nativeFileSystem);
|
|
kfree_sensitive(tcon->password);
|
|
-#ifdef CONFIG_CIFS_DFS_UPCALL
|
|
- dfs_put_root_smb_sessions(&tcon->dfs_ses_list);
|
|
-#endif
|
|
kfree(tcon->origin_fullpath);
|
|
kfree(tcon);
|
|
}
|
|
diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
|
|
index 63f969a8e44d7..22c94dea52116 100644
|
|
--- a/fs/smb/common/smb2pdu.h
|
|
+++ b/fs/smb/common/smb2pdu.h
|
|
@@ -700,7 +700,7 @@ struct smb2_close_rsp {
|
|
__le16 StructureSize; /* 60 */
|
|
__le16 Flags;
|
|
__le32 Reserved;
|
|
- struct_group(network_open_info,
|
|
+ struct_group_attr(network_open_info, __packed,
|
|
__le64 CreationTime;
|
|
__le64 LastAccessTime;
|
|
__le64 LastWriteTime;
|
|
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
|
|
index 3079e607c5fe6..2bbc3c3316f0f 100644
|
|
--- a/fs/smb/server/server.c
|
|
+++ b/fs/smb/server/server.c
|
|
@@ -167,20 +167,17 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
|
|
int rc;
|
|
bool is_chained = false;
|
|
|
|
- if (conn->ops->allocate_rsp_buf(work))
|
|
- return;
|
|
-
|
|
if (conn->ops->is_transform_hdr &&
|
|
conn->ops->is_transform_hdr(work->request_buf)) {
|
|
rc = conn->ops->decrypt_req(work);
|
|
- if (rc < 0) {
|
|
- conn->ops->set_rsp_status(work, STATUS_DATA_ERROR);
|
|
- goto send;
|
|
- }
|
|
-
|
|
+ if (rc < 0)
|
|
+ return;
|
|
work->encrypted = true;
|
|
}
|
|
|
|
+ if (conn->ops->allocate_rsp_buf(work))
|
|
+ return;
|
|
+
|
|
rc = conn->ops->init_rsp_hdr(work);
|
|
if (rc) {
|
|
/* either uid or tid is not correct */
|
|
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
|
|
index 924f08326eef4..fb9eea631069e 100644
|
|
--- a/fs/smb/server/smb2pdu.c
|
|
+++ b/fs/smb/server/smb2pdu.c
|
|
@@ -535,6 +535,10 @@ int smb2_allocate_rsp_buf(struct ksmbd_work *work)
|
|
if (cmd == SMB2_QUERY_INFO_HE) {
|
|
struct smb2_query_info_req *req;
|
|
|
|
+ if (get_rfc1002_len(work->request_buf) <
|
|
+ offsetof(struct smb2_query_info_req, OutputBufferLength))
|
|
+ return -EINVAL;
|
|
+
|
|
req = smb2_get_msg(work->request_buf);
|
|
if ((req->InfoType == SMB2_O_INFO_FILE &&
|
|
(req->FileInfoClass == FILE_FULL_EA_INFORMATION ||
|
|
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
|
|
index a8936aba7710e..626406b0cf4ac 100644
|
|
--- a/fs/smb/server/vfs.c
|
|
+++ b/fs/smb/server/vfs.c
|
|
@@ -745,10 +745,15 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
|
|
goto out4;
|
|
}
|
|
|
|
+ /*
|
|
+ * explicitly handle file overwrite case, for compatibility with
|
|
+ * filesystems that may not support rename flags (e.g: fuse)
|
|
+ */
|
|
if ((flags & RENAME_NOREPLACE) && d_is_positive(new_dentry)) {
|
|
err = -EEXIST;
|
|
goto out4;
|
|
}
|
|
+ flags &= ~(RENAME_NOREPLACE);
|
|
|
|
if (old_child == trap) {
|
|
err = -EINVAL;
|
|
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c
|
|
index a12ac0356c69c..f21e73d107249 100644
|
|
--- a/fs/sysfs/file.c
|
|
+++ b/fs/sysfs/file.c
|
|
@@ -450,6 +450,8 @@ struct kernfs_node *sysfs_break_active_protection(struct kobject *kobj,
|
|
kn = kernfs_find_and_get(kobj->sd, attr->name);
|
|
if (kn)
|
|
kernfs_break_active_protection(kn);
|
|
+ else
|
|
+ kobject_put(kobj);
|
|
return kn;
|
|
}
|
|
EXPORT_SYMBOL_GPL(sysfs_break_active_protection);
|
|
diff --git a/include/asm-generic/barrier.h b/include/asm-generic/barrier.h
|
|
index 961f4d88f9ef7..1985c22d90ca4 100644
|
|
--- a/include/asm-generic/barrier.h
|
|
+++ b/include/asm-generic/barrier.h
|
|
@@ -296,5 +296,13 @@ do { \
|
|
#define io_stop_wc() do { } while (0)
|
|
#endif
|
|
|
|
+/*
|
|
+ * Architectures that guarantee an implicit smp_mb() in switch_mm()
|
|
+ * can override smp_mb__after_switch_mm.
|
|
+ */
|
|
+#ifndef smp_mb__after_switch_mm
|
|
+# define smp_mb__after_switch_mm() smp_mb()
|
|
+#endif
|
|
+
|
|
#endif /* !__ASSEMBLY__ */
|
|
#endif /* __ASM_GENERIC_BARRIER_H */
|
|
diff --git a/include/linux/bootconfig.h b/include/linux/bootconfig.h
|
|
index ca73940e26df8..4195444ec45d1 100644
|
|
--- a/include/linux/bootconfig.h
|
|
+++ b/include/linux/bootconfig.h
|
|
@@ -287,7 +287,12 @@ int __init xbc_init(const char *buf, size_t size, const char **emsg, int *epos);
|
|
int __init xbc_get_info(int *node_size, size_t *data_size);
|
|
|
|
/* XBC cleanup data structures */
|
|
-void __init xbc_exit(void);
|
|
+void __init _xbc_exit(bool early);
|
|
+
|
|
+static inline void xbc_exit(void)
|
|
+{
|
|
+ _xbc_exit(false);
|
|
+}
|
|
|
|
/* XBC embedded bootconfig data in kernel */
|
|
#ifdef CONFIG_BOOT_CONFIG_EMBED
|
|
diff --git a/include/linux/ceph/mdsmap.h b/include/linux/ceph/mdsmap.h
|
|
index fcc95bff72a57..1f2171dd01bfa 100644
|
|
--- a/include/linux/ceph/mdsmap.h
|
|
+++ b/include/linux/ceph/mdsmap.h
|
|
@@ -5,6 +5,8 @@
|
|
#include <linux/bug.h>
|
|
#include <linux/ceph/types.h>
|
|
|
|
+struct ceph_mds_client;
|
|
+
|
|
/*
|
|
* mds map - describe servers in the mds cluster.
|
|
*
|
|
@@ -69,7 +71,8 @@ static inline bool ceph_mdsmap_is_laggy(struct ceph_mdsmap *m, int w)
|
|
}
|
|
|
|
extern int ceph_mdsmap_get_random_mds(struct ceph_mdsmap *m);
|
|
-struct ceph_mdsmap *ceph_mdsmap_decode(void **p, void *end, bool msgr2);
|
|
+struct ceph_mdsmap *ceph_mdsmap_decode(struct ceph_mds_client *mdsc, void **p,
|
|
+ void *end, bool msgr2);
|
|
extern void ceph_mdsmap_destroy(struct ceph_mdsmap *m);
|
|
extern bool ceph_mdsmap_is_cluster_available(struct ceph_mdsmap *m);
|
|
|
|
diff --git a/include/linux/gpio/property.h b/include/linux/gpio/property.h
|
|
index 6c75c8bd44a0b..1a14e239221f7 100644
|
|
--- a/include/linux/gpio/property.h
|
|
+++ b/include/linux/gpio/property.h
|
|
@@ -2,7 +2,6 @@
|
|
#ifndef __LINUX_GPIO_PROPERTY_H
|
|
#define __LINUX_GPIO_PROPERTY_H
|
|
|
|
-#include <dt-bindings/gpio/gpio.h> /* for GPIO_* flags */
|
|
#include <linux/property.h>
|
|
|
|
#define PROPERTY_ENTRY_GPIO(_name_, _chip_node_, _idx_, _flags_) \
|
|
diff --git a/include/linux/pci.h b/include/linux/pci.h
|
|
index b548d5646a86d..ee89a69817aaf 100644
|
|
--- a/include/linux/pci.h
|
|
+++ b/include/linux/pci.h
|
|
@@ -1391,6 +1391,7 @@ int pci_load_and_free_saved_state(struct pci_dev *dev,
|
|
struct pci_saved_state **state);
|
|
int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state);
|
|
int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
|
|
+int pci_set_power_state_locked(struct pci_dev *dev, pci_power_t state);
|
|
pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);
|
|
bool pci_pme_capable(struct pci_dev *dev, pci_power_t state);
|
|
void pci_pme_active(struct pci_dev *dev, bool enable);
|
|
@@ -1594,6 +1595,8 @@ int pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max,
|
|
|
|
void pci_walk_bus(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
void *userdata);
|
|
+void pci_walk_bus_locked(struct pci_bus *top, int (*cb)(struct pci_dev *, void *),
|
|
+ void *userdata);
|
|
int pci_cfg_space_size(struct pci_dev *dev);
|
|
unsigned char pci_bus_max_busnr(struct pci_bus *bus);
|
|
void pci_setup_bridge(struct pci_bus *bus);
|
|
@@ -1990,6 +1993,8 @@ static inline int pci_save_state(struct pci_dev *dev) { return 0; }
|
|
static inline void pci_restore_state(struct pci_dev *dev) { }
|
|
static inline int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
|
|
{ return 0; }
|
|
+static inline int pci_set_power_state_locked(struct pci_dev *dev, pci_power_t state)
|
|
+{ return 0; }
|
|
static inline int pci_wake_from_d3(struct pci_dev *dev, bool enable)
|
|
{ return 0; }
|
|
static inline pci_power_t pci_choose_state(struct pci_dev *dev,
|
|
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
|
|
index fa99e68e5e776..134c686c8676c 100644
|
|
--- a/include/linux/shmem_fs.h
|
|
+++ b/include/linux/shmem_fs.h
|
|
@@ -110,8 +110,17 @@ extern struct page *shmem_read_mapping_page_gfp(struct address_space *mapping,
|
|
extern void shmem_truncate_range(struct inode *inode, loff_t start, loff_t end);
|
|
int shmem_unuse(unsigned int type);
|
|
|
|
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
|
extern bool shmem_is_huge(struct inode *inode, pgoff_t index, bool shmem_huge_force,
|
|
struct mm_struct *mm, unsigned long vm_flags);
|
|
+#else
|
|
+static __always_inline bool shmem_is_huge(struct inode *inode, pgoff_t index, bool shmem_huge_force,
|
|
+ struct mm_struct *mm, unsigned long vm_flags)
|
|
+{
|
|
+ return false;
|
|
+}
|
|
+#endif
|
|
+
|
|
#ifdef CONFIG_SHMEM
|
|
extern unsigned long shmem_swap_usage(struct vm_area_struct *vma);
|
|
#else
|
|
diff --git a/include/linux/swapops.h b/include/linux/swapops.h
|
|
index bff1e8d97de0e..925c84653af5e 100644
|
|
--- a/include/linux/swapops.h
|
|
+++ b/include/linux/swapops.h
|
|
@@ -390,6 +390,35 @@ static inline bool is_migration_entry_dirty(swp_entry_t entry)
|
|
}
|
|
#endif /* CONFIG_MIGRATION */
|
|
|
|
+#ifdef CONFIG_MEMORY_FAILURE
|
|
+
|
|
+/*
|
|
+ * Support for hardware poisoned pages
|
|
+ */
|
|
+static inline swp_entry_t make_hwpoison_entry(struct page *page)
|
|
+{
|
|
+ BUG_ON(!PageLocked(page));
|
|
+ return swp_entry(SWP_HWPOISON, page_to_pfn(page));
|
|
+}
|
|
+
|
|
+static inline int is_hwpoison_entry(swp_entry_t entry)
|
|
+{
|
|
+ return swp_type(entry) == SWP_HWPOISON;
|
|
+}
|
|
+
|
|
+#else
|
|
+
|
|
+static inline swp_entry_t make_hwpoison_entry(struct page *page)
|
|
+{
|
|
+ return swp_entry(0, 0);
|
|
+}
|
|
+
|
|
+static inline int is_hwpoison_entry(swp_entry_t swp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
typedef unsigned long pte_marker;
|
|
|
|
#define PTE_MARKER_UFFD_WP BIT(0)
|
|
@@ -470,8 +499,9 @@ static inline struct page *pfn_swap_entry_to_page(swp_entry_t entry)
|
|
|
|
/*
|
|
* A pfn swap entry is a special type of swap entry that always has a pfn stored
|
|
- * in the swap offset. They are used to represent unaddressable device memory
|
|
- * and to restrict access to a page undergoing migration.
|
|
+ * in the swap offset. They can either be used to represent unaddressable device
|
|
+ * memory, to restrict access to a page undergoing migration or to represent a
|
|
+ * pfn which has been hwpoisoned and unmapped.
|
|
*/
|
|
static inline bool is_pfn_swap_entry(swp_entry_t entry)
|
|
{
|
|
@@ -479,7 +509,7 @@ static inline bool is_pfn_swap_entry(swp_entry_t entry)
|
|
BUILD_BUG_ON(SWP_TYPE_SHIFT < SWP_PFN_BITS);
|
|
|
|
return is_migration_entry(entry) || is_device_private_entry(entry) ||
|
|
- is_device_exclusive_entry(entry);
|
|
+ is_device_exclusive_entry(entry) || is_hwpoison_entry(entry);
|
|
}
|
|
|
|
struct page_vma_mapped_walk;
|
|
@@ -548,35 +578,6 @@ static inline int is_pmd_migration_entry(pmd_t pmd)
|
|
}
|
|
#endif /* CONFIG_ARCH_ENABLE_THP_MIGRATION */
|
|
|
|
-#ifdef CONFIG_MEMORY_FAILURE
|
|
-
|
|
-/*
|
|
- * Support for hardware poisoned pages
|
|
- */
|
|
-static inline swp_entry_t make_hwpoison_entry(struct page *page)
|
|
-{
|
|
- BUG_ON(!PageLocked(page));
|
|
- return swp_entry(SWP_HWPOISON, page_to_pfn(page));
|
|
-}
|
|
-
|
|
-static inline int is_hwpoison_entry(swp_entry_t entry)
|
|
-{
|
|
- return swp_type(entry) == SWP_HWPOISON;
|
|
-}
|
|
-
|
|
-#else
|
|
-
|
|
-static inline swp_entry_t make_hwpoison_entry(struct page *page)
|
|
-{
|
|
- return swp_entry(0, 0);
|
|
-}
|
|
-
|
|
-static inline int is_hwpoison_entry(swp_entry_t swp)
|
|
-{
|
|
- return 0;
|
|
-}
|
|
-#endif
|
|
-
|
|
static inline int non_swap_entry(swp_entry_t entry)
|
|
{
|
|
return swp_type(entry) >= MAX_SWAPFILES;
|
|
diff --git a/include/linux/udp.h b/include/linux/udp.h
|
|
index 94e63b2695406..00790bb5cbde6 100644
|
|
--- a/include/linux/udp.h
|
|
+++ b/include/linux/udp.h
|
|
@@ -105,7 +105,7 @@ struct udp_sock {
|
|
#define udp_assign_bit(nr, sk, val) \
|
|
assign_bit(UDP_FLAGS_##nr, &udp_sk(sk)->udp_flags, val)
|
|
|
|
-#define UDP_MAX_SEGMENTS (1 << 6UL)
|
|
+#define UDP_MAX_SEGMENTS (1 << 7UL)
|
|
|
|
#define udp_sk(ptr) container_of_const(ptr, struct udp_sock, inet.sk)
|
|
|
|
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
|
|
index 61d4f0b793dcd..d0e19ac3ba6ce 100644
|
|
--- a/include/linux/usb/hcd.h
|
|
+++ b/include/linux/usb/hcd.h
|
|
@@ -372,8 +372,9 @@ struct hc_driver {
|
|
* or bandwidth constraints.
|
|
*/
|
|
void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
|
|
- /* Returns the hardware-chosen device address */
|
|
- int (*address_device)(struct usb_hcd *, struct usb_device *udev);
|
|
+ /* Set the hardware-chosen device address */
|
|
+ int (*address_device)(struct usb_hcd *, struct usb_device *udev,
|
|
+ unsigned int timeout_ms);
|
|
/* prepares the hardware to send commands to the device */
|
|
int (*enable_device)(struct usb_hcd *, struct usb_device *udev);
|
|
/* Notifies the HCD after a hub descriptor is fetched.
|
|
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
|
|
index eeb7c2157c72f..59409c1fc3dee 100644
|
|
--- a/include/linux/usb/quirks.h
|
|
+++ b/include/linux/usb/quirks.h
|
|
@@ -72,4 +72,7 @@
|
|
/* device has endpoints that should be ignored */
|
|
#define USB_QUIRK_ENDPOINT_IGNORE BIT(15)
|
|
|
|
+/* short SET_ADDRESS request timeout */
|
|
+#define USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT BIT(16)
|
|
+
|
|
#endif /* __LINUX_USB_QUIRKS_H */
|
|
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
|
|
index 4a767b3d20b9d..df7775afb92b9 100644
|
|
--- a/include/net/netfilter/nf_flow_table.h
|
|
+++ b/include/net/netfilter/nf_flow_table.h
|
|
@@ -335,7 +335,7 @@ int nf_flow_rule_route_ipv6(struct net *net, struct flow_offload *flow,
|
|
int nf_flow_table_offload_init(void);
|
|
void nf_flow_table_offload_exit(void);
|
|
|
|
-static inline __be16 nf_flow_pppoe_proto(const struct sk_buff *skb)
|
|
+static inline __be16 __nf_flow_pppoe_proto(const struct sk_buff *skb)
|
|
{
|
|
__be16 proto;
|
|
|
|
@@ -351,6 +351,16 @@ static inline __be16 nf_flow_pppoe_proto(const struct sk_buff *skb)
|
|
return 0;
|
|
}
|
|
|
|
+static inline bool nf_flow_pppoe_proto(struct sk_buff *skb, __be16 *inner_proto)
|
|
+{
|
|
+ if (!pskb_may_pull(skb, PPPOE_SES_HLEN))
|
|
+ return false;
|
|
+
|
|
+ *inner_proto = __nf_flow_pppoe_proto(skb);
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
#define NF_FLOW_TABLE_STAT_INC(net, count) __this_cpu_inc((net)->ft.stat->count)
|
|
#define NF_FLOW_TABLE_STAT_DEC(net, count) __this_cpu_dec((net)->ft.stat->count)
|
|
#define NF_FLOW_TABLE_STAT_INC_ATOMIC(net, count) \
|
|
diff --git a/include/trace/events/rpcgss.h b/include/trace/events/rpcgss.h
|
|
index ba2d96a1bc2f9..f50fcafc69de2 100644
|
|
--- a/include/trace/events/rpcgss.h
|
|
+++ b/include/trace/events/rpcgss.h
|
|
@@ -609,7 +609,7 @@ TRACE_EVENT(rpcgss_context,
|
|
__field(unsigned int, timeout)
|
|
__field(u32, window_size)
|
|
__field(int, len)
|
|
- __string(acceptor, data)
|
|
+ __string_len(acceptor, data, len)
|
|
),
|
|
|
|
TP_fast_assign(
|
|
@@ -618,7 +618,7 @@ TRACE_EVENT(rpcgss_context,
|
|
__entry->timeout = timeout;
|
|
__entry->window_size = window_size;
|
|
__entry->len = len;
|
|
- strncpy(__get_str(acceptor), data, len);
|
|
+ __assign_str(acceptor, data);
|
|
),
|
|
|
|
TP_printk("win_size=%u expiry=%lu now=%lu timeout=%u acceptor=%.*s",
|
|
diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h
|
|
index e5f558d964939..ade8dabf62108 100644
|
|
--- a/include/uapi/linux/pci_regs.h
|
|
+++ b/include/uapi/linux/pci_regs.h
|
|
@@ -1045,6 +1045,7 @@
|
|
#define PCI_EXP_DPC_STATUS_INTERRUPT 0x0008 /* Interrupt Status */
|
|
#define PCI_EXP_DPC_RP_BUSY 0x0010 /* Root Port Busy */
|
|
#define PCI_EXP_DPC_STATUS_TRIGGER_RSN_EXT 0x0060 /* Trig Reason Extension */
|
|
+#define PCI_EXP_DPC_RP_PIO_FEP 0x1f00 /* RP PIO First Err Ptr */
|
|
|
|
#define PCI_EXP_DPC_SOURCE_ID 0x0A /* DPC Source Identifier */
|
|
|
|
diff --git a/init/main.c b/init/main.c
|
|
index 9e6ab6d593bd8..b25c779e93ac4 100644
|
|
--- a/init/main.c
|
|
+++ b/init/main.c
|
|
@@ -630,6 +630,8 @@ static void __init setup_command_line(char *command_line)
|
|
if (!saved_command_line)
|
|
panic("%s: Failed to allocate %zu bytes\n", __func__, len + ilen);
|
|
|
|
+ len = xlen + strlen(command_line) + 1;
|
|
+
|
|
static_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
|
|
if (!static_command_line)
|
|
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
|
|
diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
|
|
index 62ff7cee5db5f..a5628d29b9b1f 100644
|
|
--- a/io_uring/io_uring.c
|
|
+++ b/io_uring/io_uring.c
|
|
@@ -2559,19 +2559,6 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,
|
|
if (__io_cqring_events_user(ctx) >= min_events)
|
|
return 0;
|
|
|
|
- if (sig) {
|
|
-#ifdef CONFIG_COMPAT
|
|
- if (in_compat_syscall())
|
|
- ret = set_compat_user_sigmask((const compat_sigset_t __user *)sig,
|
|
- sigsz);
|
|
- else
|
|
-#endif
|
|
- ret = set_user_sigmask(sig, sigsz);
|
|
-
|
|
- if (ret)
|
|
- return ret;
|
|
- }
|
|
-
|
|
init_waitqueue_func_entry(&iowq.wq, io_wake_function);
|
|
iowq.wq.private = current;
|
|
INIT_LIST_HEAD(&iowq.wq.entry);
|
|
@@ -2588,6 +2575,19 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,
|
|
iowq.timeout = ktime_add_ns(timespec64_to_ktime(ts), ktime_get_ns());
|
|
}
|
|
|
|
+ if (sig) {
|
|
+#ifdef CONFIG_COMPAT
|
|
+ if (in_compat_syscall())
|
|
+ ret = set_compat_user_sigmask((const compat_sigset_t __user *)sig,
|
|
+ sigsz);
|
|
+ else
|
|
+#endif
|
|
+ ret = set_user_sigmask(sig, sigsz);
|
|
+
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
trace_io_uring_cqring_wait(ctx, min_events);
|
|
do {
|
|
int nr_wait = (int) iowq.cq_tail - READ_ONCE(ctx->rings->cq.tail);
|
|
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
|
|
index 04846272409cc..35c38daa2d3e1 100644
|
|
--- a/kernel/sched/sched.h
|
|
+++ b/kernel/sched/sched.h
|
|
@@ -88,6 +88,8 @@
|
|
# include <asm/paravirt_api_clock.h>
|
|
#endif
|
|
|
|
+#include <asm/barrier.h>
|
|
+
|
|
#include "cpupri.h"
|
|
#include "cpudeadline.h"
|
|
|
|
@@ -3500,13 +3502,19 @@ static inline void switch_mm_cid(struct rq *rq,
|
|
* between rq->curr store and load of {prev,next}->mm->pcpu_cid[cpu].
|
|
* Provide it here.
|
|
*/
|
|
- if (!prev->mm) // from kernel
|
|
+ if (!prev->mm) { // from kernel
|
|
smp_mb();
|
|
- /*
|
|
- * user -> user transition guarantees a memory barrier through
|
|
- * switch_mm() when current->mm changes. If current->mm is
|
|
- * unchanged, no barrier is needed.
|
|
- */
|
|
+ } else { // from user
|
|
+ /*
|
|
+ * user->user transition relies on an implicit
|
|
+ * memory barrier in switch_mm() when
|
|
+ * current->mm changes. If the architecture
|
|
+ * switch_mm() does not have an implicit memory
|
|
+ * barrier, it is emitted here. If current->mm
|
|
+ * is unchanged, no barrier is needed.
|
|
+ */
|
|
+ smp_mb__after_switch_mm();
|
|
+ }
|
|
}
|
|
if (prev->mm_cid_active) {
|
|
mm_cid_snapshot_time(rq, prev->mm);
|
|
diff --git a/lib/bootconfig.c b/lib/bootconfig.c
|
|
index c59d26068a640..8841554432d5b 100644
|
|
--- a/lib/bootconfig.c
|
|
+++ b/lib/bootconfig.c
|
|
@@ -61,9 +61,12 @@ static inline void * __init xbc_alloc_mem(size_t size)
|
|
return memblock_alloc(size, SMP_CACHE_BYTES);
|
|
}
|
|
|
|
-static inline void __init xbc_free_mem(void *addr, size_t size)
|
|
+static inline void __init xbc_free_mem(void *addr, size_t size, bool early)
|
|
{
|
|
- memblock_free(addr, size);
|
|
+ if (early)
|
|
+ memblock_free(addr, size);
|
|
+ else if (addr)
|
|
+ memblock_free_late(__pa(addr), size);
|
|
}
|
|
|
|
#else /* !__KERNEL__ */
|
|
@@ -73,7 +76,7 @@ static inline void *xbc_alloc_mem(size_t size)
|
|
return malloc(size);
|
|
}
|
|
|
|
-static inline void xbc_free_mem(void *addr, size_t size)
|
|
+static inline void xbc_free_mem(void *addr, size_t size, bool early)
|
|
{
|
|
free(addr);
|
|
}
|
|
@@ -904,13 +907,13 @@ static int __init xbc_parse_tree(void)
|
|
* If you need to reuse xbc_init() with new boot config, you can
|
|
* use this.
|
|
*/
|
|
-void __init xbc_exit(void)
|
|
+void __init _xbc_exit(bool early)
|
|
{
|
|
- xbc_free_mem(xbc_data, xbc_data_size);
|
|
+ xbc_free_mem(xbc_data, xbc_data_size, early);
|
|
xbc_data = NULL;
|
|
xbc_data_size = 0;
|
|
xbc_node_num = 0;
|
|
- xbc_free_mem(xbc_nodes, sizeof(struct xbc_node) * XBC_NODE_MAX);
|
|
+ xbc_free_mem(xbc_nodes, sizeof(struct xbc_node) * XBC_NODE_MAX, early);
|
|
xbc_nodes = NULL;
|
|
brace_index = 0;
|
|
}
|
|
@@ -963,7 +966,7 @@ int __init xbc_init(const char *data, size_t size, const char **emsg, int *epos)
|
|
if (!xbc_nodes) {
|
|
if (emsg)
|
|
*emsg = "Failed to allocate bootconfig nodes";
|
|
- xbc_exit();
|
|
+ _xbc_exit(true);
|
|
return -ENOMEM;
|
|
}
|
|
memset(xbc_nodes, 0, sizeof(struct xbc_node) * XBC_NODE_MAX);
|
|
@@ -977,7 +980,7 @@ int __init xbc_init(const char *data, size_t size, const char **emsg, int *epos)
|
|
*epos = xbc_err_pos;
|
|
if (emsg)
|
|
*emsg = xbc_err_msg;
|
|
- xbc_exit();
|
|
+ _xbc_exit(true);
|
|
} else
|
|
ret = xbc_node_num;
|
|
|
|
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
|
|
index 5e6c4d367d33a..a17950160395d 100644
|
|
--- a/mm/hugetlb.c
|
|
+++ b/mm/hugetlb.c
|
|
@@ -6653,9 +6653,13 @@ long hugetlb_change_protection(struct vm_area_struct *vma,
|
|
if (!pte_same(pte, newpte))
|
|
set_huge_pte_at(mm, address, ptep, newpte, psize);
|
|
} else if (unlikely(is_pte_marker(pte))) {
|
|
- /* No other markers apply for now. */
|
|
- WARN_ON_ONCE(!pte_marker_uffd_wp(pte));
|
|
- if (uffd_wp_resolve)
|
|
+ /*
|
|
+ * Do nothing on a poison marker; page is
|
|
+ * corrupted, permissons do not apply. Here
|
|
+ * pte_marker_uffd_wp()==true implies !poison
|
|
+ * because they're mutual exclusive.
|
|
+ */
|
|
+ if (pte_marker_uffd_wp(pte) && uffd_wp_resolve)
|
|
/* Safe to modify directly (non-present->none). */
|
|
huge_pte_clear(mm, address, ptep, psize);
|
|
} else if (!huge_pte_none(pte)) {
|
|
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
|
|
index 455093f73a70c..5378edad9df8f 100644
|
|
--- a/mm/memory-failure.c
|
|
+++ b/mm/memory-failure.c
|
|
@@ -154,11 +154,23 @@ static int __page_handle_poison(struct page *page)
|
|
{
|
|
int ret;
|
|
|
|
- zone_pcp_disable(page_zone(page));
|
|
+ /*
|
|
+ * zone_pcp_disable() can't be used here. It will
|
|
+ * hold pcp_batch_high_lock and dissolve_free_huge_page() might hold
|
|
+ * cpu_hotplug_lock via static_key_slow_dec() when hugetlb vmemmap
|
|
+ * optimization is enabled. This will break current lock dependency
|
|
+ * chain and leads to deadlock.
|
|
+ * Disabling pcp before dissolving the page was a deterministic
|
|
+ * approach because we made sure that those pages cannot end up in any
|
|
+ * PCP list. Draining PCP lists expels those pages to the buddy system,
|
|
+ * but nothing guarantees that those pages do not get back to a PCP
|
|
+ * queue if we need to refill those.
|
|
+ */
|
|
ret = dissolve_free_huge_page(page);
|
|
- if (!ret)
|
|
+ if (!ret) {
|
|
+ drain_all_pages(page_zone(page));
|
|
ret = take_page_off_buddy(page);
|
|
- zone_pcp_enable(page_zone(page));
|
|
+ }
|
|
|
|
return ret;
|
|
}
|
|
diff --git a/mm/shmem.c b/mm/shmem.c
|
|
index 80c2666114b3e..f2023cb7f6f6e 100644
|
|
--- a/mm/shmem.c
|
|
+++ b/mm/shmem.c
|
|
@@ -742,12 +742,6 @@ static long shmem_unused_huge_count(struct super_block *sb,
|
|
|
|
#define shmem_huge SHMEM_HUGE_DENY
|
|
|
|
-bool shmem_is_huge(struct inode *inode, pgoff_t index, bool shmem_huge_force,
|
|
- struct mm_struct *mm, unsigned long vm_flags)
|
|
-{
|
|
- return false;
|
|
-}
|
|
-
|
|
static unsigned long shmem_unused_huge_shrink(struct shmem_sb_info *sbinfo,
|
|
struct shrink_control *sc, unsigned long nr_to_split)
|
|
{
|
|
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
|
|
index c729528b5e85f..e09000e38d071 100644
|
|
--- a/net/bridge/br_input.c
|
|
+++ b/net/bridge/br_input.c
|
|
@@ -30,7 +30,7 @@ br_netif_receive_skb(struct net *net, struct sock *sk, struct sk_buff *skb)
|
|
return netif_receive_skb(skb);
|
|
}
|
|
|
|
-static int br_pass_frame_up(struct sk_buff *skb)
|
|
+static int br_pass_frame_up(struct sk_buff *skb, bool promisc)
|
|
{
|
|
struct net_device *indev, *brdev = BR_INPUT_SKB_CB(skb)->brdev;
|
|
struct net_bridge *br = netdev_priv(brdev);
|
|
@@ -65,6 +65,8 @@ static int br_pass_frame_up(struct sk_buff *skb)
|
|
br_multicast_count(br, NULL, skb, br_multicast_igmp_type(skb),
|
|
BR_MCAST_DIR_TX);
|
|
|
|
+ BR_INPUT_SKB_CB(skb)->promisc = promisc;
|
|
+
|
|
return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN,
|
|
dev_net(indev), NULL, skb, indev, NULL,
|
|
br_netif_receive_skb);
|
|
@@ -82,6 +84,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
|
|
struct net_bridge_mcast *brmctx;
|
|
struct net_bridge_vlan *vlan;
|
|
struct net_bridge *br;
|
|
+ bool promisc;
|
|
u16 vid = 0;
|
|
u8 state;
|
|
|
|
@@ -137,7 +140,9 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
|
|
if (p->flags & BR_LEARNING)
|
|
br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, 0);
|
|
|
|
- local_rcv = !!(br->dev->flags & IFF_PROMISC);
|
|
+ promisc = !!(br->dev->flags & IFF_PROMISC);
|
|
+ local_rcv = promisc;
|
|
+
|
|
if (is_multicast_ether_addr(eth_hdr(skb)->h_dest)) {
|
|
/* by definition the broadcast is also a multicast address */
|
|
if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
|
|
@@ -200,7 +205,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
|
|
unsigned long now = jiffies;
|
|
|
|
if (test_bit(BR_FDB_LOCAL, &dst->flags))
|
|
- return br_pass_frame_up(skb);
|
|
+ return br_pass_frame_up(skb, false);
|
|
|
|
if (now != dst->used)
|
|
dst->used = now;
|
|
@@ -213,7 +218,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
|
|
}
|
|
|
|
if (local_rcv)
|
|
- return br_pass_frame_up(skb);
|
|
+ return br_pass_frame_up(skb, promisc);
|
|
|
|
out:
|
|
return 0;
|
|
@@ -386,6 +391,8 @@ static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
|
|
goto forward;
|
|
}
|
|
|
|
+ BR_INPUT_SKB_CB(skb)->promisc = false;
|
|
+
|
|
/* The else clause should be hit when nf_hook():
|
|
* - returns < 0 (drop/error)
|
|
* - returns = 0 (stolen/nf_queue)
|
|
diff --git a/net/bridge/br_netfilter_hooks.c b/net/bridge/br_netfilter_hooks.c
|
|
index 6ef67030b4db3..d848c84ed030d 100644
|
|
--- a/net/bridge/br_netfilter_hooks.c
|
|
+++ b/net/bridge/br_netfilter_hooks.c
|
|
@@ -600,11 +600,17 @@ static unsigned int br_nf_local_in(void *priv,
|
|
struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
+ bool promisc = BR_INPUT_SKB_CB(skb)->promisc;
|
|
struct nf_conntrack *nfct = skb_nfct(skb);
|
|
const struct nf_ct_hook *ct_hook;
|
|
struct nf_conn *ct;
|
|
int ret;
|
|
|
|
+ if (promisc) {
|
|
+ nf_reset_ct(skb);
|
|
+ return NF_ACCEPT;
|
|
+ }
|
|
+
|
|
if (!nfct || skb->pkt_type == PACKET_HOST)
|
|
return NF_ACCEPT;
|
|
|
|
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
|
|
index 82e63908dce8a..e4f1a08322da9 100644
|
|
--- a/net/bridge/br_private.h
|
|
+++ b/net/bridge/br_private.h
|
|
@@ -583,6 +583,7 @@ struct br_input_skb_cb {
|
|
#endif
|
|
u8 proxyarp_replied:1;
|
|
u8 src_port_isolated:1;
|
|
+ u8 promisc:1;
|
|
#ifdef CONFIG_BRIDGE_VLAN_FILTERING
|
|
u8 vlan_filtered:1;
|
|
#endif
|
|
diff --git a/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c
|
|
index d32fce70d797d..6ef04f9fe481b 100644
|
|
--- a/net/bridge/netfilter/nf_conntrack_bridge.c
|
|
+++ b/net/bridge/netfilter/nf_conntrack_bridge.c
|
|
@@ -294,18 +294,24 @@ static unsigned int nf_ct_bridge_pre(void *priv, struct sk_buff *skb,
|
|
static unsigned int nf_ct_bridge_in(void *priv, struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
- enum ip_conntrack_info ctinfo;
|
|
+ bool promisc = BR_INPUT_SKB_CB(skb)->promisc;
|
|
+ struct nf_conntrack *nfct = skb_nfct(skb);
|
|
struct nf_conn *ct;
|
|
|
|
- if (skb->pkt_type == PACKET_HOST)
|
|
+ if (promisc) {
|
|
+ nf_reset_ct(skb);
|
|
+ return NF_ACCEPT;
|
|
+ }
|
|
+
|
|
+ if (!nfct || skb->pkt_type == PACKET_HOST)
|
|
return NF_ACCEPT;
|
|
|
|
/* nf_conntrack_confirm() cannot handle concurrent clones,
|
|
* this happens for broad/multicast frames with e.g. macvlan on top
|
|
* of the bridge device.
|
|
*/
|
|
- ct = nf_ct_get(skb, &ctinfo);
|
|
- if (!ct || nf_ct_is_confirmed(ct) || nf_ct_is_template(ct))
|
|
+ ct = container_of(nfct, struct nf_conn, ct_general);
|
|
+ if (nf_ct_is_confirmed(ct) || nf_ct_is_template(ct))
|
|
return NF_ACCEPT;
|
|
|
|
/* let inet prerouting call conntrack again */
|
|
diff --git a/net/netfilter/nf_flow_table_inet.c b/net/netfilter/nf_flow_table_inet.c
|
|
index 9505f9d188ff2..6eef15648b7b0 100644
|
|
--- a/net/netfilter/nf_flow_table_inet.c
|
|
+++ b/net/netfilter/nf_flow_table_inet.c
|
|
@@ -21,7 +21,8 @@ nf_flow_offload_inet_hook(void *priv, struct sk_buff *skb,
|
|
proto = veth->h_vlan_encapsulated_proto;
|
|
break;
|
|
case htons(ETH_P_PPP_SES):
|
|
- proto = nf_flow_pppoe_proto(skb);
|
|
+ if (!nf_flow_pppoe_proto(skb, &proto))
|
|
+ return NF_ACCEPT;
|
|
break;
|
|
default:
|
|
proto = skb->protocol;
|
|
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
|
|
index e45fade764096..5383bed3d3e00 100644
|
|
--- a/net/netfilter/nf_flow_table_ip.c
|
|
+++ b/net/netfilter/nf_flow_table_ip.c
|
|
@@ -157,7 +157,7 @@ static void nf_flow_tuple_encap(struct sk_buff *skb,
|
|
tuple->encap[i].proto = skb->protocol;
|
|
break;
|
|
case htons(ETH_P_PPP_SES):
|
|
- phdr = (struct pppoe_hdr *)skb_mac_header(skb);
|
|
+ phdr = (struct pppoe_hdr *)skb_network_header(skb);
|
|
tuple->encap[i].id = ntohs(phdr->sid);
|
|
tuple->encap[i].proto = skb->protocol;
|
|
break;
|
|
@@ -273,10 +273,11 @@ static unsigned int nf_flow_xmit_xfrm(struct sk_buff *skb,
|
|
return NF_STOLEN;
|
|
}
|
|
|
|
-static bool nf_flow_skb_encap_protocol(const struct sk_buff *skb, __be16 proto,
|
|
+static bool nf_flow_skb_encap_protocol(struct sk_buff *skb, __be16 proto,
|
|
u32 *offset)
|
|
{
|
|
struct vlan_ethhdr *veth;
|
|
+ __be16 inner_proto;
|
|
|
|
switch (skb->protocol) {
|
|
case htons(ETH_P_8021Q):
|
|
@@ -287,7 +288,8 @@ static bool nf_flow_skb_encap_protocol(const struct sk_buff *skb, __be16 proto,
|
|
}
|
|
break;
|
|
case htons(ETH_P_PPP_SES):
|
|
- if (nf_flow_pppoe_proto(skb) == proto) {
|
|
+ if (nf_flow_pppoe_proto(skb, &inner_proto) &&
|
|
+ inner_proto == proto) {
|
|
*offset += PPPOE_SES_HLEN;
|
|
return true;
|
|
}
|
|
@@ -316,7 +318,7 @@ static void nf_flow_encap_pop(struct sk_buff *skb,
|
|
skb_reset_network_header(skb);
|
|
break;
|
|
case htons(ETH_P_PPP_SES):
|
|
- skb->protocol = nf_flow_pppoe_proto(skb);
|
|
+ skb->protocol = __nf_flow_pppoe_proto(skb);
|
|
skb_pull(skb, PPPOE_SES_HLEN);
|
|
skb_reset_network_header(skb);
|
|
break;
|
|
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
|
|
index 2a4649df8f086..387eee416b0bf 100644
|
|
--- a/net/netfilter/nf_tables_api.c
|
|
+++ b/net/netfilter/nf_tables_api.c
|
|
@@ -3047,7 +3047,7 @@ static const struct nft_expr_type *__nft_expr_type_get(u8 family,
|
|
{
|
|
const struct nft_expr_type *type, *candidate = NULL;
|
|
|
|
- list_for_each_entry(type, &nf_tables_expressions, list) {
|
|
+ list_for_each_entry_rcu(type, &nf_tables_expressions, list) {
|
|
if (!nla_strcmp(nla, type->name)) {
|
|
if (!type->family && !candidate)
|
|
candidate = type;
|
|
@@ -3079,9 +3079,13 @@ static const struct nft_expr_type *nft_expr_type_get(struct net *net,
|
|
if (nla == NULL)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
+ rcu_read_lock();
|
|
type = __nft_expr_type_get(family, nla);
|
|
- if (type != NULL && try_module_get(type->owner))
|
|
+ if (type != NULL && try_module_get(type->owner)) {
|
|
+ rcu_read_unlock();
|
|
return type;
|
|
+ }
|
|
+ rcu_read_unlock();
|
|
|
|
lockdep_nfnl_nft_mutex_not_held();
|
|
#ifdef CONFIG_MODULES
|
|
@@ -7464,7 +7468,7 @@ static const struct nft_object_type *__nft_obj_type_get(u32 objtype, u8 family)
|
|
{
|
|
const struct nft_object_type *type;
|
|
|
|
- list_for_each_entry(type, &nf_tables_objects, list) {
|
|
+ list_for_each_entry_rcu(type, &nf_tables_objects, list) {
|
|
if (type->family != NFPROTO_UNSPEC &&
|
|
type->family != family)
|
|
continue;
|
|
@@ -7480,9 +7484,13 @@ nft_obj_type_get(struct net *net, u32 objtype, u8 family)
|
|
{
|
|
const struct nft_object_type *type;
|
|
|
|
+ rcu_read_lock();
|
|
type = __nft_obj_type_get(objtype, family);
|
|
- if (type != NULL && try_module_get(type->owner))
|
|
+ if (type != NULL && try_module_get(type->owner)) {
|
|
+ rcu_read_unlock();
|
|
return type;
|
|
+ }
|
|
+ rcu_read_unlock();
|
|
|
|
lockdep_nfnl_nft_mutex_not_held();
|
|
#ifdef CONFIG_MODULES
|
|
diff --git a/net/netfilter/nft_set_pipapo.c b/net/netfilter/nft_set_pipapo.c
|
|
index a890aa0abad58..69b02a3f1ff05 100644
|
|
--- a/net/netfilter/nft_set_pipapo.c
|
|
+++ b/net/netfilter/nft_set_pipapo.c
|
|
@@ -1993,6 +1993,8 @@ static void nft_pipapo_remove(const struct net *net, const struct nft_set *set,
|
|
rules_fx = rules_f0;
|
|
|
|
nft_pipapo_for_each_field(f, i, m) {
|
|
+ bool last = i == m->field_count - 1;
|
|
+
|
|
if (!pipapo_match_field(f, start, rules_fx,
|
|
match_start, match_end))
|
|
break;
|
|
@@ -2005,16 +2007,18 @@ static void nft_pipapo_remove(const struct net *net, const struct nft_set *set,
|
|
|
|
match_start += NFT_PIPAPO_GROUPS_PADDED_SIZE(f);
|
|
match_end += NFT_PIPAPO_GROUPS_PADDED_SIZE(f);
|
|
- }
|
|
|
|
- if (i == m->field_count) {
|
|
- priv->dirty = true;
|
|
- pipapo_drop(m, rulemap);
|
|
- return;
|
|
+ if (last && f->mt[rulemap[i].to].e == e) {
|
|
+ priv->dirty = true;
|
|
+ pipapo_drop(m, rulemap);
|
|
+ return;
|
|
+ }
|
|
}
|
|
|
|
first_rule += rules_f0;
|
|
}
|
|
+
|
|
+ WARN_ON_ONCE(1); /* elem_priv not found */
|
|
}
|
|
|
|
/**
|
|
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
|
|
index 918724844231e..6eab35a5e2f3b 100644
|
|
--- a/net/unix/af_unix.c
|
|
+++ b/net/unix/af_unix.c
|
|
@@ -2587,7 +2587,9 @@ static struct sk_buff *manage_oob(struct sk_buff *skb, struct sock *sk,
|
|
WRITE_ONCE(u->oob_skb, NULL);
|
|
consume_skb(skb);
|
|
}
|
|
- } else if (!(flags & MSG_PEEK)) {
|
|
+ } else if (flags & MSG_PEEK) {
|
|
+ skb = NULL;
|
|
+ } else {
|
|
skb_unlink(skb, &sk->sk_receive_queue);
|
|
WRITE_ONCE(u->oob_skb, NULL);
|
|
if (!WARN_ON_ONCE(skb_unref(skb)))
|
|
@@ -2665,18 +2667,16 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
|
|
last = skb = skb_peek(&sk->sk_receive_queue);
|
|
last_len = last ? last->len : 0;
|
|
|
|
+again:
|
|
#if IS_ENABLED(CONFIG_AF_UNIX_OOB)
|
|
if (skb) {
|
|
skb = manage_oob(skb, sk, flags, copied);
|
|
- if (!skb) {
|
|
+ if (!skb && copied) {
|
|
unix_state_unlock(sk);
|
|
- if (copied)
|
|
- break;
|
|
- goto redo;
|
|
+ break;
|
|
}
|
|
}
|
|
#endif
|
|
-again:
|
|
if (skb == NULL) {
|
|
if (copied >= target)
|
|
goto unlock;
|
|
diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c
|
|
index b141024830ecc..ee6ac649df836 100644
|
|
--- a/sound/core/seq/seq_ump_convert.c
|
|
+++ b/sound/core/seq/seq_ump_convert.c
|
|
@@ -428,7 +428,7 @@ static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest,
|
|
midi1->note.group = midi2->note.group;
|
|
midi1->note.status = midi2->note.status;
|
|
midi1->note.channel = midi2->note.channel;
|
|
- switch (midi2->note.status << 4) {
|
|
+ switch (midi2->note.status) {
|
|
case UMP_MSG_STATUS_NOTE_ON:
|
|
case UMP_MSG_STATUS_NOTE_OFF:
|
|
midi1->note.note = midi2->note.note;
|
|
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
|
|
index 6841de9d423e3..057c207237cc2 100644
|
|
--- a/sound/pci/hda/patch_realtek.c
|
|
+++ b/sound/pci/hda/patch_realtek.c
|
|
@@ -10098,6 +10098,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
|
SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC),
|
|
SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC),
|
|
SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK),
|
|
+ SND_PCI_QUIRK(0x152d, 0x1262, "Huawei NBLB-WAX9N", ALC2XX_FIXUP_HEADSET_MIC),
|
|
SND_PCI_QUIRK(0x1558, 0x0353, "Clevo V35[05]SN[CDE]Q", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
|
|
SND_PCI_QUIRK(0x1558, 0x1323, "Clevo N130ZU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
|
|
SND_PCI_QUIRK(0x1558, 0x1325, "Clevo N15[01][CW]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE),
|
|
@@ -10203,6 +10204,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
|
SND_PCI_QUIRK(0x17aa, 0x222e, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
|
|
SND_PCI_QUIRK(0x17aa, 0x2231, "Thinkpad T560", ALC292_FIXUP_TPT460),
|
|
SND_PCI_QUIRK(0x17aa, 0x2233, "Thinkpad", ALC292_FIXUP_TPT460),
|
|
+ SND_PCI_QUIRK(0x17aa, 0x2234, "Thinkpad ICE-1", ALC287_FIXUP_TAS2781_I2C),
|
|
SND_PCI_QUIRK(0x17aa, 0x2245, "Thinkpad T470", ALC298_FIXUP_TPT470_DOCK),
|
|
SND_PCI_QUIRK(0x17aa, 0x2246, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
|
|
SND_PCI_QUIRK(0x17aa, 0x2247, "Thinkpad", ALC298_FIXUP_TPT470_DOCK),
|
|
@@ -10315,6 +10317,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
|
|
SND_PCI_QUIRK(0x1d05, 0x115c, "TongFang GMxTGxx", ALC269_FIXUP_NO_SHUTUP),
|
|
SND_PCI_QUIRK(0x1d05, 0x121b, "TongFang GMxAGxx", ALC269_FIXUP_NO_SHUTUP),
|
|
SND_PCI_QUIRK(0x1d05, 0x1387, "TongFang GMxIXxx", ALC2XX_FIXUP_HEADSET_MIC),
|
|
+ SND_PCI_QUIRK(0x1d17, 0x3288, "Haier Boyue G42", ALC269VC_FIXUP_ACER_VCOPPERBOX_PINS),
|
|
SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC),
|
|
SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE),
|
|
SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC),
|
|
diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c
|
|
index 6188308223588..a3dec624132d4 100644
|
|
--- a/sound/pci/hda/tas2781_hda_i2c.c
|
|
+++ b/sound/pci/hda/tas2781_hda_i2c.c
|
|
@@ -420,10 +420,10 @@ static const struct snd_kcontrol_new tas2781_dsp_conf_ctrl = {
|
|
static void tas2781_apply_calib(struct tasdevice_priv *tas_priv)
|
|
{
|
|
static const unsigned char page_array[CALIB_MAX] = {
|
|
- 0x17, 0x18, 0x18, 0x0d, 0x18
|
|
+ 0x17, 0x18, 0x18, 0x13, 0x18,
|
|
};
|
|
static const unsigned char rgno_array[CALIB_MAX] = {
|
|
- 0x74, 0x0c, 0x14, 0x3c, 0x7c
|
|
+ 0x74, 0x0c, 0x14, 0x70, 0x7c,
|
|
};
|
|
unsigned char *data;
|
|
int i, j, rc;
|
|
diff --git a/sound/soc/ti/omap3pandora.c b/sound/soc/ti/omap3pandora.c
|
|
index a287e9747c2a1..fa92ed97dfe3b 100644
|
|
--- a/sound/soc/ti/omap3pandora.c
|
|
+++ b/sound/soc/ti/omap3pandora.c
|
|
@@ -7,7 +7,7 @@
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/platform_device.h>
|
|
-#include <linux/gpio.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/module.h>
|
|
@@ -21,12 +21,11 @@
|
|
|
|
#include "omap-mcbsp.h"
|
|
|
|
-#define OMAP3_PANDORA_DAC_POWER_GPIO 118
|
|
-#define OMAP3_PANDORA_AMP_POWER_GPIO 14
|
|
-
|
|
#define PREFIX "ASoC omap3pandora: "
|
|
|
|
static struct regulator *omap3pandora_dac_reg;
|
|
+static struct gpio_desc *dac_power_gpio;
|
|
+static struct gpio_desc *amp_power_gpio;
|
|
|
|
static int omap3pandora_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
@@ -78,9 +77,9 @@ static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w,
|
|
return ret;
|
|
}
|
|
mdelay(1);
|
|
- gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
|
|
+ gpiod_set_value(dac_power_gpio, 1);
|
|
} else {
|
|
- gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
|
|
+ gpiod_set_value(dac_power_gpio, 0);
|
|
mdelay(1);
|
|
regulator_disable(omap3pandora_dac_reg);
|
|
}
|
|
@@ -92,9 +91,9 @@ static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *k, int event)
|
|
{
|
|
if (SND_SOC_DAPM_EVENT_ON(event))
|
|
- gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
|
|
+ gpiod_set_value(amp_power_gpio, 1);
|
|
else
|
|
- gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
|
|
+ gpiod_set_value(amp_power_gpio, 0);
|
|
|
|
return 0;
|
|
}
|
|
@@ -229,35 +228,10 @@ static int __init omap3pandora_soc_init(void)
|
|
|
|
pr_info("OMAP3 Pandora SoC init\n");
|
|
|
|
- ret = gpio_request(OMAP3_PANDORA_DAC_POWER_GPIO, "dac_power");
|
|
- if (ret) {
|
|
- pr_err(PREFIX "Failed to get DAC power GPIO\n");
|
|
- return ret;
|
|
- }
|
|
-
|
|
- ret = gpio_direction_output(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
|
|
- if (ret) {
|
|
- pr_err(PREFIX "Failed to set DAC power GPIO direction\n");
|
|
- goto fail0;
|
|
- }
|
|
-
|
|
- ret = gpio_request(OMAP3_PANDORA_AMP_POWER_GPIO, "amp_power");
|
|
- if (ret) {
|
|
- pr_err(PREFIX "Failed to get amp power GPIO\n");
|
|
- goto fail0;
|
|
- }
|
|
-
|
|
- ret = gpio_direction_output(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
|
|
- if (ret) {
|
|
- pr_err(PREFIX "Failed to set amp power GPIO direction\n");
|
|
- goto fail1;
|
|
- }
|
|
-
|
|
omap3pandora_snd_device = platform_device_alloc("soc-audio", -1);
|
|
if (omap3pandora_snd_device == NULL) {
|
|
pr_err(PREFIX "Platform device allocation failed\n");
|
|
- ret = -ENOMEM;
|
|
- goto fail1;
|
|
+ return -ENOMEM;
|
|
}
|
|
|
|
platform_set_drvdata(omap3pandora_snd_device, &snd_soc_card_omap3pandora);
|
|
@@ -268,6 +242,20 @@ static int __init omap3pandora_soc_init(void)
|
|
goto fail2;
|
|
}
|
|
|
|
+ dac_power_gpio = devm_gpiod_get(&omap3pandora_snd_device->dev,
|
|
+ "dac", GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(dac_power_gpio)) {
|
|
+ ret = PTR_ERR(dac_power_gpio);
|
|
+ goto fail3;
|
|
+ }
|
|
+
|
|
+ amp_power_gpio = devm_gpiod_get(&omap3pandora_snd_device->dev,
|
|
+ "amp", GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(amp_power_gpio)) {
|
|
+ ret = PTR_ERR(amp_power_gpio);
|
|
+ goto fail3;
|
|
+ }
|
|
+
|
|
omap3pandora_dac_reg = regulator_get(&omap3pandora_snd_device->dev, "vcc");
|
|
if (IS_ERR(omap3pandora_dac_reg)) {
|
|
pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n",
|
|
@@ -283,10 +271,7 @@ static int __init omap3pandora_soc_init(void)
|
|
platform_device_del(omap3pandora_snd_device);
|
|
fail2:
|
|
platform_device_put(omap3pandora_snd_device);
|
|
-fail1:
|
|
- gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
|
|
-fail0:
|
|
- gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
|
|
+
|
|
return ret;
|
|
}
|
|
module_init(omap3pandora_soc_init);
|
|
@@ -295,8 +280,6 @@ static void __exit omap3pandora_soc_exit(void)
|
|
{
|
|
regulator_put(omap3pandora_dac_reg);
|
|
platform_device_unregister(omap3pandora_snd_device);
|
|
- gpio_free(OMAP3_PANDORA_AMP_POWER_GPIO);
|
|
- gpio_free(OMAP3_PANDORA_DAC_POWER_GPIO);
|
|
}
|
|
module_exit(omap3pandora_soc_exit);
|
|
|
|
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
|
|
index db5ff76d0e61f..8c657c2753c84 100644
|
|
--- a/sound/usb/Makefile
|
|
+++ b/sound/usb/Makefile
|
|
@@ -12,7 +12,7 @@ snd-usb-audio-objs := card.o \
|
|
mixer.o \
|
|
mixer_quirks.o \
|
|
mixer_scarlett.o \
|
|
- mixer_scarlett_gen2.o \
|
|
+ mixer_scarlett2.o \
|
|
mixer_us16x08.o \
|
|
mixer_s1810c.o \
|
|
pcm.o \
|
|
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
|
|
index 1f32e3ae3aa31..c8d48566e1759 100644
|
|
--- a/sound/usb/mixer_quirks.c
|
|
+++ b/sound/usb/mixer_quirks.c
|
|
@@ -33,7 +33,7 @@
|
|
#include "mixer.h"
|
|
#include "mixer_quirks.h"
|
|
#include "mixer_scarlett.h"
|
|
-#include "mixer_scarlett_gen2.h"
|
|
+#include "mixer_scarlett2.h"
|
|
#include "mixer_us16x08.h"
|
|
#include "mixer_s1810c.h"
|
|
#include "helper.h"
|
|
@@ -3447,8 +3447,13 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
|
|
case USB_ID(0x1235, 0x8213): /* Focusrite Scarlett 8i6 3rd Gen */
|
|
case USB_ID(0x1235, 0x8214): /* Focusrite Scarlett 18i8 3rd Gen */
|
|
case USB_ID(0x1235, 0x8215): /* Focusrite Scarlett 18i20 3rd Gen */
|
|
+ case USB_ID(0x1235, 0x8206): /* Focusrite Clarett 2Pre USB */
|
|
+ case USB_ID(0x1235, 0x8207): /* Focusrite Clarett 4Pre USB */
|
|
+ case USB_ID(0x1235, 0x8208): /* Focusrite Clarett 8Pre USB */
|
|
+ case USB_ID(0x1235, 0x820a): /* Focusrite Clarett+ 2Pre */
|
|
+ case USB_ID(0x1235, 0x820b): /* Focusrite Clarett+ 4Pre */
|
|
case USB_ID(0x1235, 0x820c): /* Focusrite Clarett+ 8Pre */
|
|
- err = snd_scarlett_gen2_init(mixer);
|
|
+ err = snd_scarlett2_init(mixer);
|
|
break;
|
|
|
|
case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */
|
|
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
|
|
new file mode 100644
|
|
index 0000000000000..90480b9b9b089
|
|
--- /dev/null
|
|
+++ b/sound/usb/mixer_scarlett2.c
|
|
@@ -0,0 +1,4391 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Focusrite Scarlett 2 Protocol Driver for ALSA
|
|
+ * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+
|
|
+ * series products)
|
|
+ *
|
|
+ * Supported models:
|
|
+ * - 6i6/18i8/18i20 Gen 2
|
|
+ * - Solo/2i2/4i4/8i6/18i8/18i20 Gen 3
|
|
+ * - Clarett 2Pre/4Pre/8Pre USB
|
|
+ * - Clarett+ 2Pre/4Pre/8Pre
|
|
+ *
|
|
+ * Copyright (c) 2018-2023 by Geoffrey D. Bennett <g at b4.vu>
|
|
+ * Copyright (c) 2020-2021 by Vladimir Sadovnikov <sadko4u@gmail.com>
|
|
+ * Copyright (c) 2022 by Christian Colglazier <christian@cacolglazier.com>
|
|
+ *
|
|
+ * Based on the Scarlett (Gen 1) Driver for ALSA:
|
|
+ *
|
|
+ * Copyright (c) 2013 by Tobias Hoffmann
|
|
+ * Copyright (c) 2013 by Robin Gareus <robin at gareus.org>
|
|
+ * Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de>
|
|
+ * Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com>
|
|
+ *
|
|
+ * Many codes borrowed from audio.c by
|
|
+ * Alan Cox (alan at lxorguk.ukuu.org.uk)
|
|
+ * Thomas Sailer (sailer at ife.ee.ethz.ch)
|
|
+ *
|
|
+ * Code cleanup:
|
|
+ * David Henningsson <david.henningsson at canonical.com>
|
|
+ */
|
|
+
|
|
+/* The protocol was reverse engineered by looking at the communication
|
|
+ * between Focusrite Control 2.3.4 and the Focusrite(R) Scarlett 18i20
|
|
+ * (firmware 1083) using usbmon in July-August 2018.
|
|
+ *
|
|
+ * Scarlett 18i8 support added in April 2019.
|
|
+ *
|
|
+ * Scarlett 6i6 support added in June 2019 (thanks to Martin Wittmann
|
|
+ * for providing usbmon output and testing).
|
|
+ *
|
|
+ * Scarlett 4i4/8i6 Gen 3 support added in May 2020 (thanks to Laurent
|
|
+ * Debricon for donating a 4i4 and to Fredrik Unger for providing 8i6
|
|
+ * usbmon output and testing).
|
|
+ *
|
|
+ * Scarlett 18i8/18i20 Gen 3 support added in June 2020 (thanks to
|
|
+ * Darren Jaeckel, Alex Sedlack, and Clovis Lunel for providing usbmon
|
|
+ * output, protocol traces and testing).
|
|
+ *
|
|
+ * Support for loading mixer volume and mux configuration from the
|
|
+ * interface during driver initialisation added in May 2021 (thanks to
|
|
+ * Vladimir Sadovnikov for figuring out how).
|
|
+ *
|
|
+ * Support for Solo/2i2 Gen 3 added in May 2021 (thanks to Alexander
|
|
+ * Vorona for 2i2 protocol traces).
|
|
+ *
|
|
+ * Support for phantom power, direct monitoring, speaker switching,
|
|
+ * and talkback added in May-June 2021.
|
|
+ *
|
|
+ * Support for Clarett+ 8Pre added in Aug 2022 by Christian
|
|
+ * Colglazier.
|
|
+ *
|
|
+ * Support for Clarett 8Pre USB added in Sep 2023 (thanks to Philippe
|
|
+ * Perrot for confirmation).
|
|
+ *
|
|
+ * Support for Clarett+ 4Pre and 2Pre added in Sep 2023 (thanks to
|
|
+ * Gregory Rozzo for donating a 4Pre, and David Sherwood and Patrice
|
|
+ * Peterson for usbmon output).
|
|
+ *
|
|
+ * Support for Clarett 2Pre and 4Pre USB added in Oct 2023.
|
|
+ *
|
|
+ * This ALSA mixer gives access to (model-dependent):
|
|
+ * - input, output, mixer-matrix muxes
|
|
+ * - mixer-matrix gain stages
|
|
+ * - gain/volume/mute controls
|
|
+ * - level meters
|
|
+ * - line/inst level, pad, and air controls
|
|
+ * - phantom power, direct monitor, speaker switching, and talkback
|
|
+ * controls
|
|
+ * - disable/enable MSD mode
|
|
+ * - disable/enable standalone mode
|
|
+ *
|
|
+ * <ditaa>
|
|
+ * /--------------\ 18chn 20chn /--------------\
|
|
+ * | Hardware in +--+------\ /-------------+--+ ALSA PCM out |
|
|
+ * \--------------/ | | | | \--------------/
|
|
+ * | | | /-----\ |
|
|
+ * | | | | | |
|
|
+ * | v v v | |
|
|
+ * | +---------------+ | |
|
|
+ * | \ Matrix Mux / | |
|
|
+ * | +-----+-----+ | |
|
|
+ * | | | |
|
|
+ * | |18chn | |
|
|
+ * | | | |
|
|
+ * | | 10chn| |
|
|
+ * | v | |
|
|
+ * | +------------+ | |
|
|
+ * | | Mixer | | |
|
|
+ * | | Matrix | | |
|
|
+ * | | | | |
|
|
+ * | | 18x10 Gain | | |
|
|
+ * | | stages | | |
|
|
+ * | +-----+------+ | |
|
|
+ * | | | |
|
|
+ * |18chn |10chn | |20chn
|
|
+ * | | | |
|
|
+ * | +----------/ |
|
|
+ * | | |
|
|
+ * v v v
|
|
+ * ===========================
|
|
+ * +---------------+ +--—------------+
|
|
+ * \ Output Mux / \ Capture Mux /
|
|
+ * +---+---+---+ +-----+-----+
|
|
+ * | | |
|
|
+ * 10chn| | |18chn
|
|
+ * | | |
|
|
+ * /--------------\ | | | /--------------\
|
|
+ * | S/PDIF, ADAT |<--/ |10chn \-->| ALSA PCM in |
|
|
+ * | Hardware out | | \--------------/
|
|
+ * \--------------/ |
|
|
+ * v
|
|
+ * +-------------+ Software gain per channel.
|
|
+ * | Master Gain |<-- 18i20 only: Switch per channel
|
|
+ * +------+------+ to select HW or SW gain control.
|
|
+ * |
|
|
+ * |10chn
|
|
+ * /--------------\ |
|
|
+ * | Analogue |<------/
|
|
+ * | Hardware out |
|
|
+ * \--------------/
|
|
+ * </ditaa>
|
|
+ *
|
|
+ * Gen 3 devices have a Mass Storage Device (MSD) mode where a small
|
|
+ * disk with registration and driver download information is presented
|
|
+ * to the host. To access the full functionality of the device without
|
|
+ * proprietary software, MSD mode can be disabled by:
|
|
+ * - holding down the 48V button for five seconds while powering on
|
|
+ * the device, or
|
|
+ * - using this driver and alsamixer to change the "MSD Mode" setting
|
|
+ * to Off and power-cycling the device
|
|
+ */
|
|
+
|
|
+#include <linux/slab.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/moduleparam.h>
|
|
+
|
|
+#include <sound/control.h>
|
|
+#include <sound/tlv.h>
|
|
+
|
|
+#include "usbaudio.h"
|
|
+#include "mixer.h"
|
|
+#include "helper.h"
|
|
+
|
|
+#include "mixer_scarlett2.h"
|
|
+
|
|
+/* device_setup value to allow turning MSD mode back on */
|
|
+#define SCARLETT2_MSD_ENABLE 0x02
|
|
+
|
|
+/* device_setup value to disable this mixer driver */
|
|
+#define SCARLETT2_DISABLE 0x04
|
|
+
|
|
+/* some gui mixers can't handle negative ctl values */
|
|
+#define SCARLETT2_VOLUME_BIAS 127
|
|
+
|
|
+/* mixer range from -80dB to +6dB in 0.5dB steps */
|
|
+#define SCARLETT2_MIXER_MIN_DB -80
|
|
+#define SCARLETT2_MIXER_BIAS (-SCARLETT2_MIXER_MIN_DB * 2)
|
|
+#define SCARLETT2_MIXER_MAX_DB 6
|
|
+#define SCARLETT2_MIXER_MAX_VALUE \
|
|
+ ((SCARLETT2_MIXER_MAX_DB - SCARLETT2_MIXER_MIN_DB) * 2)
|
|
+#define SCARLETT2_MIXER_VALUE_COUNT (SCARLETT2_MIXER_MAX_VALUE + 1)
|
|
+
|
|
+/* map from (dB + 80) * 2 to mixer value
|
|
+ * for dB in 0 .. 172: int(8192 * pow(10, ((dB - 160) / 2 / 20)))
|
|
+ */
|
|
+static const u16 scarlett2_mixer_values[SCARLETT2_MIXER_VALUE_COUNT] = {
|
|
+ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
|
+ 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8,
|
|
+ 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
|
|
+ 23, 24, 25, 27, 29, 30, 32, 34, 36, 38, 41, 43, 46, 48, 51,
|
|
+ 54, 57, 61, 65, 68, 73, 77, 81, 86, 91, 97, 103, 109, 115,
|
|
+ 122, 129, 137, 145, 154, 163, 173, 183, 194, 205, 217, 230,
|
|
+ 244, 259, 274, 290, 307, 326, 345, 365, 387, 410, 434, 460,
|
|
+ 487, 516, 547, 579, 614, 650, 689, 730, 773, 819, 867, 919,
|
|
+ 973, 1031, 1092, 1157, 1225, 1298, 1375, 1456, 1543, 1634,
|
|
+ 1731, 1833, 1942, 2057, 2179, 2308, 2445, 2590, 2744, 2906,
|
|
+ 3078, 3261, 3454, 3659, 3876, 4105, 4349, 4606, 4879, 5168,
|
|
+ 5475, 5799, 6143, 6507, 6892, 7301, 7733, 8192, 8677, 9191,
|
|
+ 9736, 10313, 10924, 11571, 12257, 12983, 13752, 14567, 15430,
|
|
+ 16345
|
|
+};
|
|
+
|
|
+/* Maximum number of analogue outputs */
|
|
+#define SCARLETT2_ANALOGUE_MAX 10
|
|
+
|
|
+/* Maximum number of level and pad switches */
|
|
+#define SCARLETT2_LEVEL_SWITCH_MAX 2
|
|
+#define SCARLETT2_PAD_SWITCH_MAX 8
|
|
+#define SCARLETT2_AIR_SWITCH_MAX 8
|
|
+#define SCARLETT2_PHANTOM_SWITCH_MAX 2
|
|
+
|
|
+/* Maximum number of inputs to the mixer */
|
|
+#define SCARLETT2_INPUT_MIX_MAX 25
|
|
+
|
|
+/* Maximum number of outputs from the mixer */
|
|
+#define SCARLETT2_OUTPUT_MIX_MAX 12
|
|
+
|
|
+/* Maximum size of the data in the USB mux assignment message:
|
|
+ * 20 inputs, 20 outputs, 25 matrix inputs, 12 spare
|
|
+ */
|
|
+#define SCARLETT2_MUX_MAX 77
|
|
+
|
|
+/* Maximum number of meters (sum of output port counts) */
|
|
+#define SCARLETT2_MAX_METERS 65
|
|
+
|
|
+/* There are three different sets of configuration parameters across
|
|
+ * the devices
|
|
+ */
|
|
+enum {
|
|
+ SCARLETT2_CONFIG_SET_NO_MIXER = 0,
|
|
+ SCARLETT2_CONFIG_SET_GEN_2 = 1,
|
|
+ SCARLETT2_CONFIG_SET_GEN_3 = 2,
|
|
+ SCARLETT2_CONFIG_SET_CLARETT = 3,
|
|
+ SCARLETT2_CONFIG_SET_COUNT = 4
|
|
+};
|
|
+
|
|
+/* Hardware port types:
|
|
+ * - None (no input to mux)
|
|
+ * - Analogue I/O
|
|
+ * - S/PDIF I/O
|
|
+ * - ADAT I/O
|
|
+ * - Mixer I/O
|
|
+ * - PCM I/O
|
|
+ */
|
|
+enum {
|
|
+ SCARLETT2_PORT_TYPE_NONE = 0,
|
|
+ SCARLETT2_PORT_TYPE_ANALOGUE = 1,
|
|
+ SCARLETT2_PORT_TYPE_SPDIF = 2,
|
|
+ SCARLETT2_PORT_TYPE_ADAT = 3,
|
|
+ SCARLETT2_PORT_TYPE_MIX = 4,
|
|
+ SCARLETT2_PORT_TYPE_PCM = 5,
|
|
+ SCARLETT2_PORT_TYPE_COUNT = 6,
|
|
+};
|
|
+
|
|
+/* I/O count of each port type kept in struct scarlett2_ports */
|
|
+enum {
|
|
+ SCARLETT2_PORT_IN = 0,
|
|
+ SCARLETT2_PORT_OUT = 1,
|
|
+ SCARLETT2_PORT_DIRNS = 2,
|
|
+};
|
|
+
|
|
+/* Dim/Mute buttons on the 18i20 */
|
|
+enum {
|
|
+ SCARLETT2_BUTTON_MUTE = 0,
|
|
+ SCARLETT2_BUTTON_DIM = 1,
|
|
+ SCARLETT2_DIM_MUTE_COUNT = 2,
|
|
+};
|
|
+
|
|
+static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = {
|
|
+ "Mute Playback Switch", "Dim Playback Switch"
|
|
+};
|
|
+
|
|
+/* Description of each hardware port type:
|
|
+ * - id: hardware ID of this port type
|
|
+ * - src_descr: printf format string for mux input selections
|
|
+ * - src_num_offset: added to channel number for the fprintf
|
|
+ * - dst_descr: printf format string for mixer controls
|
|
+ */
|
|
+struct scarlett2_port {
|
|
+ u16 id;
|
|
+ const char * const src_descr;
|
|
+ int src_num_offset;
|
|
+ const char * const dst_descr;
|
|
+};
|
|
+
|
|
+static const struct scarlett2_port scarlett2_ports[SCARLETT2_PORT_TYPE_COUNT] = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = {
|
|
+ .id = 0x000,
|
|
+ .src_descr = "Off"
|
|
+ },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = {
|
|
+ .id = 0x080,
|
|
+ .src_descr = "Analogue %d",
|
|
+ .src_num_offset = 1,
|
|
+ .dst_descr = "Analogue Output %02d Playback"
|
|
+ },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = {
|
|
+ .id = 0x180,
|
|
+ .src_descr = "S/PDIF %d",
|
|
+ .src_num_offset = 1,
|
|
+ .dst_descr = "S/PDIF Output %d Playback"
|
|
+ },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = {
|
|
+ .id = 0x200,
|
|
+ .src_descr = "ADAT %d",
|
|
+ .src_num_offset = 1,
|
|
+ .dst_descr = "ADAT Output %d Playback"
|
|
+ },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = {
|
|
+ .id = 0x300,
|
|
+ .src_descr = "Mix %c",
|
|
+ .src_num_offset = 'A',
|
|
+ .dst_descr = "Mixer Input %02d Capture"
|
|
+ },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = {
|
|
+ .id = 0x600,
|
|
+ .src_descr = "PCM %d",
|
|
+ .src_num_offset = 1,
|
|
+ .dst_descr = "PCM %02d Capture"
|
|
+ },
|
|
+};
|
|
+
|
|
+/* Number of mux tables: one for each band of sample rates
|
|
+ * (44.1/48kHz, 88.2/96kHz, and 176.4/176kHz)
|
|
+ */
|
|
+#define SCARLETT2_MUX_TABLES 3
|
|
+
|
|
+/* Maximum number of entries in a mux table */
|
|
+#define SCARLETT2_MAX_MUX_ENTRIES 10
|
|
+
|
|
+/* One entry within mux_assignment defines the port type and range of
|
|
+ * ports to add to the set_mux message. The end of the list is marked
|
|
+ * with count == 0.
|
|
+ */
|
|
+struct scarlett2_mux_entry {
|
|
+ u8 port_type;
|
|
+ u8 start;
|
|
+ u8 count;
|
|
+};
|
|
+
|
|
+struct scarlett2_device_info {
|
|
+ /* Gen 3 devices have an internal MSD mode switch that needs
|
|
+ * to be disabled in order to access the full functionality of
|
|
+ * the device.
|
|
+ */
|
|
+ u8 has_msd_mode;
|
|
+
|
|
+ /* which set of configuration parameters the device uses */
|
|
+ u8 config_set;
|
|
+
|
|
+ /* line out hw volume is sw controlled */
|
|
+ u8 line_out_hw_vol;
|
|
+
|
|
+ /* support for main/alt speaker switching */
|
|
+ u8 has_speaker_switching;
|
|
+
|
|
+ /* support for talkback microphone */
|
|
+ u8 has_talkback;
|
|
+
|
|
+ /* the number of analogue inputs with a software switchable
|
|
+ * level control that can be set to line or instrument
|
|
+ */
|
|
+ u8 level_input_count;
|
|
+
|
|
+ /* the first input with a level control (0-based) */
|
|
+ u8 level_input_first;
|
|
+
|
|
+ /* the number of analogue inputs with a software switchable
|
|
+ * 10dB pad control
|
|
+ */
|
|
+ u8 pad_input_count;
|
|
+
|
|
+ /* the number of analogue inputs with a software switchable
|
|
+ * "air" control
|
|
+ */
|
|
+ u8 air_input_count;
|
|
+
|
|
+ /* the number of phantom (48V) software switchable controls */
|
|
+ u8 phantom_count;
|
|
+
|
|
+ /* the number of inputs each phantom switch controls */
|
|
+ u8 inputs_per_phantom;
|
|
+
|
|
+ /* the number of direct monitor options
|
|
+ * (0 = none, 1 = mono only, 2 = mono/stereo)
|
|
+ */
|
|
+ u8 direct_monitor;
|
|
+
|
|
+ /* remap analogue outputs; 18i8 Gen 3 has "line 3/4" connected
|
|
+ * internally to the analogue 7/8 outputs
|
|
+ */
|
|
+ u8 line_out_remap_enable;
|
|
+ u8 line_out_remap[SCARLETT2_ANALOGUE_MAX];
|
|
+
|
|
+ /* additional description for the line out volume controls */
|
|
+ const char * const line_out_descrs[SCARLETT2_ANALOGUE_MAX];
|
|
+
|
|
+ /* number of sources/destinations of each port type */
|
|
+ const int port_count[SCARLETT2_PORT_TYPE_COUNT][SCARLETT2_PORT_DIRNS];
|
|
+
|
|
+ /* layout/order of the entries in the set_mux message */
|
|
+ struct scarlett2_mux_entry mux_assignment[SCARLETT2_MUX_TABLES]
|
|
+ [SCARLETT2_MAX_MUX_ENTRIES];
|
|
+};
|
|
+
|
|
+struct scarlett2_data {
|
|
+ struct usb_mixer_interface *mixer;
|
|
+ struct mutex usb_mutex; /* prevent sending concurrent USB requests */
|
|
+ struct mutex data_mutex; /* lock access to this data */
|
|
+ struct delayed_work work;
|
|
+ const struct scarlett2_device_info *info;
|
|
+ const char *series_name;
|
|
+ __u8 bInterfaceNumber;
|
|
+ __u8 bEndpointAddress;
|
|
+ __u16 wMaxPacketSize;
|
|
+ __u8 bInterval;
|
|
+ int num_mux_srcs;
|
|
+ int num_mux_dsts;
|
|
+ u16 scarlett2_seq;
|
|
+ u8 sync_updated;
|
|
+ u8 vol_updated;
|
|
+ u8 input_other_updated;
|
|
+ u8 monitor_other_updated;
|
|
+ u8 mux_updated;
|
|
+ u8 speaker_switching_switched;
|
|
+ u8 sync;
|
|
+ u8 master_vol;
|
|
+ u8 vol[SCARLETT2_ANALOGUE_MAX];
|
|
+ u8 vol_sw_hw_switch[SCARLETT2_ANALOGUE_MAX];
|
|
+ u8 mute_switch[SCARLETT2_ANALOGUE_MAX];
|
|
+ u8 level_switch[SCARLETT2_LEVEL_SWITCH_MAX];
|
|
+ u8 pad_switch[SCARLETT2_PAD_SWITCH_MAX];
|
|
+ u8 dim_mute[SCARLETT2_DIM_MUTE_COUNT];
|
|
+ u8 air_switch[SCARLETT2_AIR_SWITCH_MAX];
|
|
+ u8 phantom_switch[SCARLETT2_PHANTOM_SWITCH_MAX];
|
|
+ u8 phantom_persistence;
|
|
+ u8 direct_monitor_switch;
|
|
+ u8 speaker_switching_switch;
|
|
+ u8 talkback_switch;
|
|
+ u8 talkback_map[SCARLETT2_OUTPUT_MIX_MAX];
|
|
+ u8 msd_switch;
|
|
+ u8 standalone_switch;
|
|
+ struct snd_kcontrol *sync_ctl;
|
|
+ struct snd_kcontrol *master_vol_ctl;
|
|
+ struct snd_kcontrol *vol_ctls[SCARLETT2_ANALOGUE_MAX];
|
|
+ struct snd_kcontrol *sw_hw_ctls[SCARLETT2_ANALOGUE_MAX];
|
|
+ struct snd_kcontrol *mute_ctls[SCARLETT2_ANALOGUE_MAX];
|
|
+ struct snd_kcontrol *dim_mute_ctls[SCARLETT2_DIM_MUTE_COUNT];
|
|
+ struct snd_kcontrol *level_ctls[SCARLETT2_LEVEL_SWITCH_MAX];
|
|
+ struct snd_kcontrol *pad_ctls[SCARLETT2_PAD_SWITCH_MAX];
|
|
+ struct snd_kcontrol *air_ctls[SCARLETT2_AIR_SWITCH_MAX];
|
|
+ struct snd_kcontrol *phantom_ctls[SCARLETT2_PHANTOM_SWITCH_MAX];
|
|
+ struct snd_kcontrol *mux_ctls[SCARLETT2_MUX_MAX];
|
|
+ struct snd_kcontrol *direct_monitor_ctl;
|
|
+ struct snd_kcontrol *speaker_switching_ctl;
|
|
+ struct snd_kcontrol *talkback_ctl;
|
|
+ u8 mux[SCARLETT2_MUX_MAX];
|
|
+ u8 mix[SCARLETT2_INPUT_MIX_MAX * SCARLETT2_OUTPUT_MIX_MAX];
|
|
+};
|
|
+
|
|
+/*** Model-specific data ***/
|
|
+
|
|
+static const struct scarlett2_device_info s6i6_gen2_info = {
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_2,
|
|
+ .level_input_count = 2,
|
|
+ .pad_input_count = 2,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 6, 6 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s18i8_gen2_info = {
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_2,
|
|
+ .level_input_count = 2,
|
|
+ .pad_input_count = 4,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 6 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 8, 18 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 4 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s18i20_gen2_info = {
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_2,
|
|
+ .line_out_hw_vol = 1,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 6 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info solo_gen3_info = {
|
|
+ .has_msd_mode = 1,
|
|
+ .config_set = SCARLETT2_CONFIG_SET_NO_MIXER,
|
|
+ .level_input_count = 1,
|
|
+ .level_input_first = 1,
|
|
+ .air_input_count = 1,
|
|
+ .phantom_count = 1,
|
|
+ .inputs_per_phantom = 1,
|
|
+ .direct_monitor = 1,
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s2i2_gen3_info = {
|
|
+ .has_msd_mode = 1,
|
|
+ .config_set = SCARLETT2_CONFIG_SET_NO_MIXER,
|
|
+ .level_input_count = 2,
|
|
+ .air_input_count = 2,
|
|
+ .phantom_count = 1,
|
|
+ .inputs_per_phantom = 2,
|
|
+ .direct_monitor = 2,
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s4i4_gen3_info = {
|
|
+ .has_msd_mode = 1,
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
+ .level_input_count = 2,
|
|
+ .pad_input_count = 2,
|
|
+ .air_input_count = 2,
|
|
+ .phantom_count = 1,
|
|
+ .inputs_per_phantom = 2,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ "Headphones L",
|
|
+ "Headphones R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 6, 8 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 4, 6 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s8i6_gen3_info = {
|
|
+ .has_msd_mode = 1,
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
+ .level_input_count = 2,
|
|
+ .pad_input_count = 2,
|
|
+ .air_input_count = 2,
|
|
+ .phantom_count = 1,
|
|
+ .inputs_per_phantom = 2,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 6, 4 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 8, 8 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 6, 10 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s18i8_gen3_info = {
|
|
+ .has_msd_mode = 1,
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
+ .line_out_hw_vol = 1,
|
|
+ .has_speaker_switching = 1,
|
|
+ .level_input_count = 2,
|
|
+ .pad_input_count = 4,
|
|
+ .air_input_count = 4,
|
|
+ .phantom_count = 2,
|
|
+ .inputs_per_phantom = 2,
|
|
+
|
|
+ .line_out_remap_enable = 1,
|
|
+ .line_out_remap = { 0, 1, 6, 7, 2, 3, 4, 5 },
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ "Alt Monitor L",
|
|
+ "Alt Monitor R",
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 8 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 20 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 8, 20 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 12, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 10, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 12, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 10, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info s18i20_gen3_info = {
|
|
+ .has_msd_mode = 1,
|
|
+ .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
+ .line_out_hw_vol = 1,
|
|
+ .has_speaker_switching = 1,
|
|
+ .has_talkback = 1,
|
|
+ .level_input_count = 2,
|
|
+ .pad_input_count = 8,
|
|
+ .air_input_count = 8,
|
|
+ .phantom_count = 2,
|
|
+ .inputs_per_phantom = 4,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor 1 L",
|
|
+ "Monitor 1 R",
|
|
+ "Monitor 2 L",
|
|
+ "Monitor 2 R",
|
|
+ NULL,
|
|
+ NULL,
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 9, 10 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 12, 25 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 20, 20 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 10, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 25 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 12 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 10, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 25 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 24 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info clarett_2pre_info = {
|
|
+ .config_set = SCARLETT2_CONFIG_SET_CLARETT,
|
|
+ .line_out_hw_vol = 1,
|
|
+ .level_input_count = 2,
|
|
+ .air_input_count = 2,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ "Headphones L",
|
|
+ "Headphones R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 4 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 4, 12 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 12 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 26 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info clarett_4pre_info = {
|
|
+ .config_set = SCARLETT2_CONFIG_SET_CLARETT,
|
|
+ .line_out_hw_vol = 1,
|
|
+ .level_input_count = 2,
|
|
+ .air_input_count = 4,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 6 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 8, 18 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 12 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 24 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_info clarett_8pre_info = {
|
|
+ .config_set = SCARLETT2_CONFIG_SET_CLARETT,
|
|
+ .line_out_hw_vol = 1,
|
|
+ .level_input_count = 2,
|
|
+ .air_input_count = 8,
|
|
+
|
|
+ .line_out_descrs = {
|
|
+ "Monitor L",
|
|
+ "Monitor R",
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ NULL,
|
|
+ "Headphones 1 L",
|
|
+ "Headphones 1 R",
|
|
+ "Headphones 2 L",
|
|
+ "Headphones 2 R",
|
|
+ },
|
|
+
|
|
+ .port_count = {
|
|
+ [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
+ [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 },
|
|
+ [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
+ [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
|
|
+ [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
+ [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 },
|
|
+ },
|
|
+
|
|
+ .mux_assignment = { {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_ADAT, 0, 4 },
|
|
+ { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
+ { 0, 0, 0 },
|
|
+ }, {
|
|
+ { SCARLETT2_PORT_TYPE_PCM, 0, 12 },
|
|
+ { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
+ { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
+ { SCARLETT2_PORT_TYPE_NONE, 0, 22 },
|
|
+ { 0, 0, 0 },
|
|
+ } },
|
|
+};
|
|
+
|
|
+struct scarlett2_device_entry {
|
|
+ const u32 usb_id; /* USB device identifier */
|
|
+ const struct scarlett2_device_info *info;
|
|
+ const char *series_name;
|
|
+};
|
|
+
|
|
+static const struct scarlett2_device_entry scarlett2_devices[] = {
|
|
+ /* Supported Gen 2 devices */
|
|
+ { USB_ID(0x1235, 0x8203), &s6i6_gen2_info, "Scarlett Gen 2" },
|
|
+ { USB_ID(0x1235, 0x8204), &s18i8_gen2_info, "Scarlett Gen 2" },
|
|
+ { USB_ID(0x1235, 0x8201), &s18i20_gen2_info, "Scarlett Gen 2" },
|
|
+
|
|
+ /* Supported Gen 3 devices */
|
|
+ { USB_ID(0x1235, 0x8211), &solo_gen3_info, "Scarlett Gen 3" },
|
|
+ { USB_ID(0x1235, 0x8210), &s2i2_gen3_info, "Scarlett Gen 3" },
|
|
+ { USB_ID(0x1235, 0x8212), &s4i4_gen3_info, "Scarlett Gen 3" },
|
|
+ { USB_ID(0x1235, 0x8213), &s8i6_gen3_info, "Scarlett Gen 3" },
|
|
+ { USB_ID(0x1235, 0x8214), &s18i8_gen3_info, "Scarlett Gen 3" },
|
|
+ { USB_ID(0x1235, 0x8215), &s18i20_gen3_info, "Scarlett Gen 3" },
|
|
+
|
|
+ /* Supported Clarett USB/Clarett+ devices */
|
|
+ { USB_ID(0x1235, 0x8206), &clarett_2pre_info, "Clarett USB" },
|
|
+ { USB_ID(0x1235, 0x8207), &clarett_4pre_info, "Clarett USB" },
|
|
+ { USB_ID(0x1235, 0x8208), &clarett_8pre_info, "Clarett USB" },
|
|
+ { USB_ID(0x1235, 0x820a), &clarett_2pre_info, "Clarett+" },
|
|
+ { USB_ID(0x1235, 0x820b), &clarett_4pre_info, "Clarett+" },
|
|
+ { USB_ID(0x1235, 0x820c), &clarett_8pre_info, "Clarett+" },
|
|
+
|
|
+ /* End of list */
|
|
+ { 0, NULL },
|
|
+};
|
|
+
|
|
+/* get the starting port index number for a given port type/direction */
|
|
+static int scarlett2_get_port_start_num(
|
|
+ const int port_count[][SCARLETT2_PORT_DIRNS],
|
|
+ int direction, int port_type)
|
|
+{
|
|
+ int i, num = 0;
|
|
+
|
|
+ for (i = 0; i < port_type; i++)
|
|
+ num += port_count[i][direction];
|
|
+
|
|
+ return num;
|
|
+}
|
|
+
|
|
+/*** USB Interactions ***/
|
|
+
|
|
+/* Notifications from the interface */
|
|
+#define SCARLETT2_USB_NOTIFY_SYNC 0x00000008
|
|
+#define SCARLETT2_USB_NOTIFY_DIM_MUTE 0x00200000
|
|
+#define SCARLETT2_USB_NOTIFY_MONITOR 0x00400000
|
|
+#define SCARLETT2_USB_NOTIFY_INPUT_OTHER 0x00800000
|
|
+#define SCARLETT2_USB_NOTIFY_MONITOR_OTHER 0x01000000
|
|
+
|
|
+/* Commands for sending/receiving requests/responses */
|
|
+#define SCARLETT2_USB_CMD_INIT 0
|
|
+#define SCARLETT2_USB_CMD_REQ 2
|
|
+#define SCARLETT2_USB_CMD_RESP 3
|
|
+
|
|
+#define SCARLETT2_USB_INIT_1 0x00000000
|
|
+#define SCARLETT2_USB_INIT_2 0x00000002
|
|
+#define SCARLETT2_USB_GET_METER 0x00001001
|
|
+#define SCARLETT2_USB_GET_MIX 0x00002001
|
|
+#define SCARLETT2_USB_SET_MIX 0x00002002
|
|
+#define SCARLETT2_USB_GET_MUX 0x00003001
|
|
+#define SCARLETT2_USB_SET_MUX 0x00003002
|
|
+#define SCARLETT2_USB_GET_SYNC 0x00006004
|
|
+#define SCARLETT2_USB_GET_DATA 0x00800000
|
|
+#define SCARLETT2_USB_SET_DATA 0x00800001
|
|
+#define SCARLETT2_USB_DATA_CMD 0x00800002
|
|
+
|
|
+#define SCARLETT2_USB_CONFIG_SAVE 6
|
|
+
|
|
+#define SCARLETT2_USB_VOLUME_STATUS_OFFSET 0x31
|
|
+#define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1
|
|
+
|
|
+/* volume status is read together (matches scarlett2_config_items[1]) */
|
|
+struct scarlett2_usb_volume_status {
|
|
+ /* dim/mute buttons */
|
|
+ u8 dim_mute[SCARLETT2_DIM_MUTE_COUNT];
|
|
+
|
|
+ u8 pad1;
|
|
+
|
|
+ /* software volume setting */
|
|
+ s16 sw_vol[SCARLETT2_ANALOGUE_MAX];
|
|
+
|
|
+ /* actual volume of output inc. dim (-18dB) */
|
|
+ s16 hw_vol[SCARLETT2_ANALOGUE_MAX];
|
|
+
|
|
+ /* internal mute buttons */
|
|
+ u8 mute_switch[SCARLETT2_ANALOGUE_MAX];
|
|
+
|
|
+ /* sw (0) or hw (1) controlled */
|
|
+ u8 sw_hw_switch[SCARLETT2_ANALOGUE_MAX];
|
|
+
|
|
+ u8 pad3[6];
|
|
+
|
|
+ /* front panel volume knob */
|
|
+ s16 master_vol;
|
|
+} __packed;
|
|
+
|
|
+/* Configuration parameters that can be read and written */
|
|
+enum {
|
|
+ SCARLETT2_CONFIG_DIM_MUTE = 0,
|
|
+ SCARLETT2_CONFIG_LINE_OUT_VOLUME = 1,
|
|
+ SCARLETT2_CONFIG_MUTE_SWITCH = 2,
|
|
+ SCARLETT2_CONFIG_SW_HW_SWITCH = 3,
|
|
+ SCARLETT2_CONFIG_LEVEL_SWITCH = 4,
|
|
+ SCARLETT2_CONFIG_PAD_SWITCH = 5,
|
|
+ SCARLETT2_CONFIG_MSD_SWITCH = 6,
|
|
+ SCARLETT2_CONFIG_AIR_SWITCH = 7,
|
|
+ SCARLETT2_CONFIG_STANDALONE_SWITCH = 8,
|
|
+ SCARLETT2_CONFIG_PHANTOM_SWITCH = 9,
|
|
+ SCARLETT2_CONFIG_PHANTOM_PERSISTENCE = 10,
|
|
+ SCARLETT2_CONFIG_DIRECT_MONITOR = 11,
|
|
+ SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH = 12,
|
|
+ SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE = 13,
|
|
+ SCARLETT2_CONFIG_TALKBACK_MAP = 14,
|
|
+ SCARLETT2_CONFIG_COUNT = 15
|
|
+};
|
|
+
|
|
+/* Location, size, and activation command number for the configuration
|
|
+ * parameters. Size is in bits and may be 1, 8, or 16.
|
|
+ */
|
|
+struct scarlett2_config {
|
|
+ u8 offset;
|
|
+ u8 size;
|
|
+ u8 activate;
|
|
+};
|
|
+
|
|
+static const struct scarlett2_config
|
|
+ scarlett2_config_items[SCARLETT2_CONFIG_SET_COUNT]
|
|
+ [SCARLETT2_CONFIG_COUNT] =
|
|
+
|
|
+/* Devices without a mixer (Gen 3 Solo and 2i2) */
|
|
+{ {
|
|
+ [SCARLETT2_CONFIG_MSD_SWITCH] = {
|
|
+ .offset = 0x04, .size = 8, .activate = 6 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = {
|
|
+ .offset = 0x05, .size = 8, .activate = 6 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_PHANTOM_SWITCH] = {
|
|
+ .offset = 0x06, .size = 8, .activate = 3 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_DIRECT_MONITOR] = {
|
|
+ .offset = 0x07, .size = 8, .activate = 4 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
+ .offset = 0x08, .size = 1, .activate = 7 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_AIR_SWITCH] = {
|
|
+ .offset = 0x09, .size = 1, .activate = 8 },
|
|
+
|
|
+/* Gen 2 devices: 6i6, 18i8, 18i20 */
|
|
+}, {
|
|
+ [SCARLETT2_CONFIG_DIM_MUTE] = {
|
|
+ .offset = 0x31, .size = 8, .activate = 2 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = {
|
|
+ .offset = 0x34, .size = 16, .activate = 1 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_MUTE_SWITCH] = {
|
|
+ .offset = 0x5c, .size = 8, .activate = 1 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_SW_HW_SWITCH] = {
|
|
+ .offset = 0x66, .size = 8, .activate = 3 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
+ .offset = 0x7c, .size = 8, .activate = 7 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_PAD_SWITCH] = {
|
|
+ .offset = 0x84, .size = 8, .activate = 8 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_STANDALONE_SWITCH] = {
|
|
+ .offset = 0x8d, .size = 8, .activate = 6 },
|
|
+
|
|
+/* Gen 3 devices: 4i4, 8i6, 18i8, 18i20 */
|
|
+}, {
|
|
+ [SCARLETT2_CONFIG_DIM_MUTE] = {
|
|
+ .offset = 0x31, .size = 8, .activate = 2 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = {
|
|
+ .offset = 0x34, .size = 16, .activate = 1 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_MUTE_SWITCH] = {
|
|
+ .offset = 0x5c, .size = 8, .activate = 1 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_SW_HW_SWITCH] = {
|
|
+ .offset = 0x66, .size = 8, .activate = 3 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
+ .offset = 0x7c, .size = 8, .activate = 7 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_PAD_SWITCH] = {
|
|
+ .offset = 0x84, .size = 8, .activate = 8 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_AIR_SWITCH] = {
|
|
+ .offset = 0x8c, .size = 8, .activate = 8 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_STANDALONE_SWITCH] = {
|
|
+ .offset = 0x95, .size = 8, .activate = 6 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_PHANTOM_SWITCH] = {
|
|
+ .offset = 0x9c, .size = 1, .activate = 8 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_MSD_SWITCH] = {
|
|
+ .offset = 0x9d, .size = 8, .activate = 6 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = {
|
|
+ .offset = 0x9e, .size = 8, .activate = 6 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH] = {
|
|
+ .offset = 0x9f, .size = 1, .activate = 10 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE] = {
|
|
+ .offset = 0xa0, .size = 1, .activate = 10 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_TALKBACK_MAP] = {
|
|
+ .offset = 0xb0, .size = 16, .activate = 10 },
|
|
+
|
|
+/* Clarett USB and Clarett+ devices: 2Pre, 4Pre, 8Pre */
|
|
+}, {
|
|
+ [SCARLETT2_CONFIG_DIM_MUTE] = {
|
|
+ .offset = 0x31, .size = 8, .activate = 2 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = {
|
|
+ .offset = 0x34, .size = 16, .activate = 1 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_MUTE_SWITCH] = {
|
|
+ .offset = 0x5c, .size = 8, .activate = 1 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_SW_HW_SWITCH] = {
|
|
+ .offset = 0x66, .size = 8, .activate = 3 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
+ .offset = 0x7c, .size = 8, .activate = 7 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_AIR_SWITCH] = {
|
|
+ .offset = 0x95, .size = 8, .activate = 8 },
|
|
+
|
|
+ [SCARLETT2_CONFIG_STANDALONE_SWITCH] = {
|
|
+ .offset = 0x8d, .size = 8, .activate = 6 },
|
|
+} };
|
|
+
|
|
+/* proprietary request/response format */
|
|
+struct scarlett2_usb_packet {
|
|
+ __le32 cmd;
|
|
+ __le16 size;
|
|
+ __le16 seq;
|
|
+ __le32 error;
|
|
+ __le32 pad;
|
|
+ u8 data[];
|
|
+};
|
|
+
|
|
+static void scarlett2_fill_request_header(struct scarlett2_data *private,
|
|
+ struct scarlett2_usb_packet *req,
|
|
+ u32 cmd, u16 req_size)
|
|
+{
|
|
+ /* sequence must go up by 1 for each request */
|
|
+ u16 seq = private->scarlett2_seq++;
|
|
+
|
|
+ req->cmd = cpu_to_le32(cmd);
|
|
+ req->size = cpu_to_le16(req_size);
|
|
+ req->seq = cpu_to_le16(seq);
|
|
+ req->error = 0;
|
|
+ req->pad = 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_usb_tx(struct usb_device *dev, int interface,
|
|
+ void *buf, u16 size)
|
|
+{
|
|
+ return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
|
|
+ SCARLETT2_USB_CMD_REQ,
|
|
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
|
|
+ 0, interface, buf, size);
|
|
+}
|
|
+
|
|
+static int scarlett2_usb_rx(struct usb_device *dev, int interface,
|
|
+ u32 usb_req, void *buf, u16 size)
|
|
+{
|
|
+ return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
|
|
+ usb_req,
|
|
+ USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
|
|
+ 0, interface, buf, size);
|
|
+}
|
|
+
|
|
+/* Send a proprietary format request to the Scarlett interface */
|
|
+static int scarlett2_usb(
|
|
+ struct usb_mixer_interface *mixer, u32 cmd,
|
|
+ void *req_data, u16 req_size, void *resp_data, u16 resp_size)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ struct usb_device *dev = mixer->chip->dev;
|
|
+ struct scarlett2_usb_packet *req, *resp = NULL;
|
|
+ size_t req_buf_size = struct_size(req, data, req_size);
|
|
+ size_t resp_buf_size = struct_size(resp, data, resp_size);
|
|
+ int err;
|
|
+
|
|
+ req = kmalloc(req_buf_size, GFP_KERNEL);
|
|
+ if (!req) {
|
|
+ err = -ENOMEM;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ resp = kmalloc(resp_buf_size, GFP_KERNEL);
|
|
+ if (!resp) {
|
|
+ err = -ENOMEM;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&private->usb_mutex);
|
|
+
|
|
+ /* build request message and send it */
|
|
+
|
|
+ scarlett2_fill_request_header(private, req, cmd, req_size);
|
|
+
|
|
+ if (req_size)
|
|
+ memcpy(req->data, req_data, req_size);
|
|
+
|
|
+ err = scarlett2_usb_tx(dev, private->bInterfaceNumber,
|
|
+ req, req_buf_size);
|
|
+
|
|
+ if (err != req_buf_size) {
|
|
+ usb_audio_err(
|
|
+ mixer->chip,
|
|
+ "%s USB request result cmd %x was %d\n",
|
|
+ private->series_name, cmd, err);
|
|
+ err = -EINVAL;
|
|
+ goto unlock;
|
|
+ }
|
|
+
|
|
+ /* send a second message to get the response */
|
|
+
|
|
+ err = scarlett2_usb_rx(dev, private->bInterfaceNumber,
|
|
+ SCARLETT2_USB_CMD_RESP,
|
|
+ resp, resp_buf_size);
|
|
+
|
|
+ /* validate the response */
|
|
+
|
|
+ if (err != resp_buf_size) {
|
|
+ usb_audio_err(
|
|
+ mixer->chip,
|
|
+ "%s USB response result cmd %x was %d expected %zu\n",
|
|
+ private->series_name, cmd, err, resp_buf_size);
|
|
+ err = -EINVAL;
|
|
+ goto unlock;
|
|
+ }
|
|
+
|
|
+ /* cmd/seq/size should match except when initialising
|
|
+ * seq sent = 1, response = 0
|
|
+ */
|
|
+ if (resp->cmd != req->cmd ||
|
|
+ (resp->seq != req->seq &&
|
|
+ (le16_to_cpu(req->seq) != 1 || resp->seq != 0)) ||
|
|
+ resp_size != le16_to_cpu(resp->size) ||
|
|
+ resp->error ||
|
|
+ resp->pad) {
|
|
+ usb_audio_err(
|
|
+ mixer->chip,
|
|
+ "%s USB invalid response; "
|
|
+ "cmd tx/rx %d/%d seq %d/%d size %d/%d "
|
|
+ "error %d pad %d\n",
|
|
+ private->series_name,
|
|
+ le32_to_cpu(req->cmd), le32_to_cpu(resp->cmd),
|
|
+ le16_to_cpu(req->seq), le16_to_cpu(resp->seq),
|
|
+ resp_size, le16_to_cpu(resp->size),
|
|
+ le32_to_cpu(resp->error),
|
|
+ le32_to_cpu(resp->pad));
|
|
+ err = -EINVAL;
|
|
+ goto unlock;
|
|
+ }
|
|
+
|
|
+ if (resp_data && resp_size > 0)
|
|
+ memcpy(resp_data, resp->data, resp_size);
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->usb_mutex);
|
|
+error:
|
|
+ kfree(req);
|
|
+ kfree(resp);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* Send a USB message to get data; result placed in *buf */
|
|
+static int scarlett2_usb_get(
|
|
+ struct usb_mixer_interface *mixer,
|
|
+ int offset, void *buf, int size)
|
|
+{
|
|
+ struct {
|
|
+ __le32 offset;
|
|
+ __le32 size;
|
|
+ } __packed req;
|
|
+
|
|
+ req.offset = cpu_to_le32(offset);
|
|
+ req.size = cpu_to_le32(size);
|
|
+ return scarlett2_usb(mixer, SCARLETT2_USB_GET_DATA,
|
|
+ &req, sizeof(req), buf, size);
|
|
+}
|
|
+
|
|
+/* Send a USB message to get configuration parameters; result placed in *buf */
|
|
+static int scarlett2_usb_get_config(
|
|
+ struct usb_mixer_interface *mixer,
|
|
+ int config_item_num, int count, void *buf)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const struct scarlett2_config *config_item =
|
|
+ &scarlett2_config_items[info->config_set][config_item_num];
|
|
+ int size, err, i;
|
|
+ u8 *buf_8;
|
|
+ u8 value;
|
|
+
|
|
+ /* For byte-sized parameters, retrieve directly into buf */
|
|
+ if (config_item->size >= 8) {
|
|
+ size = config_item->size / 8 * count;
|
|
+ err = scarlett2_usb_get(mixer, config_item->offset, buf, size);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ if (size == 2) {
|
|
+ u16 *buf_16 = buf;
|
|
+
|
|
+ for (i = 0; i < count; i++, buf_16++)
|
|
+ *buf_16 = le16_to_cpu(*(__le16 *)buf_16);
|
|
+ }
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ /* For bit-sized parameters, retrieve into value */
|
|
+ err = scarlett2_usb_get(mixer, config_item->offset, &value, 1);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* then unpack from value into buf[] */
|
|
+ buf_8 = buf;
|
|
+ for (i = 0; i < 8 && i < count; i++, value >>= 1)
|
|
+ *buf_8++ = value & 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */
|
|
+static void scarlett2_config_save(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE);
|
|
+
|
|
+ int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD,
|
|
+ &req, sizeof(u32),
|
|
+ NULL, 0);
|
|
+ if (err < 0)
|
|
+ usb_audio_err(mixer->chip, "config save failed: %d\n", err);
|
|
+}
|
|
+
|
|
+/* Delayed work to save config */
|
|
+static void scarlett2_config_save_work(struct work_struct *work)
|
|
+{
|
|
+ struct scarlett2_data *private =
|
|
+ container_of(work, struct scarlett2_data, work.work);
|
|
+
|
|
+ scarlett2_config_save(private->mixer);
|
|
+}
|
|
+
|
|
+/* Send a USB message to set a SCARLETT2_CONFIG_* parameter */
|
|
+static int scarlett2_usb_set_config(
|
|
+ struct usb_mixer_interface *mixer,
|
|
+ int config_item_num, int index, int value)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const struct scarlett2_config *config_item =
|
|
+ &scarlett2_config_items[info->config_set][config_item_num];
|
|
+ struct {
|
|
+ __le32 offset;
|
|
+ __le32 bytes;
|
|
+ __le32 value;
|
|
+ } __packed req;
|
|
+ __le32 req2;
|
|
+ int offset, size;
|
|
+ int err;
|
|
+
|
|
+ /* Cancel any pending NVRAM save */
|
|
+ cancel_delayed_work_sync(&private->work);
|
|
+
|
|
+ /* Convert config_item->size in bits to size in bytes and
|
|
+ * calculate offset
|
|
+ */
|
|
+ if (config_item->size >= 8) {
|
|
+ size = config_item->size / 8;
|
|
+ offset = config_item->offset + index * size;
|
|
+
|
|
+ /* If updating a bit, retrieve the old value, set/clear the
|
|
+ * bit as needed, and update value
|
|
+ */
|
|
+ } else {
|
|
+ u8 tmp;
|
|
+
|
|
+ size = 1;
|
|
+ offset = config_item->offset;
|
|
+
|
|
+ err = scarlett2_usb_get(mixer, offset, &tmp, 1);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ if (value)
|
|
+ tmp |= (1 << index);
|
|
+ else
|
|
+ tmp &= ~(1 << index);
|
|
+
|
|
+ value = tmp;
|
|
+ }
|
|
+
|
|
+ /* Send the configuration parameter data */
|
|
+ req.offset = cpu_to_le32(offset);
|
|
+ req.bytes = cpu_to_le32(size);
|
|
+ req.value = cpu_to_le32(value);
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_SET_DATA,
|
|
+ &req, sizeof(u32) * 2 + size,
|
|
+ NULL, 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Activate the change */
|
|
+ req2 = cpu_to_le32(config_item->activate);
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD,
|
|
+ &req2, sizeof(req2), NULL, 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Schedule the change to be written to NVRAM */
|
|
+ if (config_item->activate != SCARLETT2_USB_CONFIG_SAVE)
|
|
+ schedule_delayed_work(&private->work, msecs_to_jiffies(2000));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Send a USB message to get sync status; result placed in *sync */
|
|
+static int scarlett2_usb_get_sync_status(
|
|
+ struct usb_mixer_interface *mixer,
|
|
+ u8 *sync)
|
|
+{
|
|
+ __le32 data;
|
|
+ int err;
|
|
+
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_GET_SYNC,
|
|
+ NULL, 0, &data, sizeof(data));
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ *sync = !!data;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Send a USB message to get volume status; result placed in *buf */
|
|
+static int scarlett2_usb_get_volume_status(
|
|
+ struct usb_mixer_interface *mixer,
|
|
+ struct scarlett2_usb_volume_status *buf)
|
|
+{
|
|
+ return scarlett2_usb_get(mixer, SCARLETT2_USB_VOLUME_STATUS_OFFSET,
|
|
+ buf, sizeof(*buf));
|
|
+}
|
|
+
|
|
+/* Send a USB message to get the volumes for all inputs of one mix
|
|
+ * and put the values into private->mix[]
|
|
+ */
|
|
+static int scarlett2_usb_get_mix(struct usb_mixer_interface *mixer,
|
|
+ int mix_num)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ int num_mixer_in =
|
|
+ info->port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
+ int err, i, j, k;
|
|
+
|
|
+ struct {
|
|
+ __le16 mix_num;
|
|
+ __le16 count;
|
|
+ } __packed req;
|
|
+
|
|
+ __le16 data[SCARLETT2_INPUT_MIX_MAX];
|
|
+
|
|
+ req.mix_num = cpu_to_le16(mix_num);
|
|
+ req.count = cpu_to_le16(num_mixer_in);
|
|
+
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_GET_MIX,
|
|
+ &req, sizeof(req),
|
|
+ data, num_mixer_in * sizeof(u16));
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++) {
|
|
+ u16 mixer_value = le16_to_cpu(data[i]);
|
|
+
|
|
+ for (k = 0; k < SCARLETT2_MIXER_VALUE_COUNT; k++)
|
|
+ if (scarlett2_mixer_values[k] >= mixer_value)
|
|
+ break;
|
|
+ if (k == SCARLETT2_MIXER_VALUE_COUNT)
|
|
+ k = SCARLETT2_MIXER_MAX_VALUE;
|
|
+ private->mix[j] = k;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Send a USB message to set the volumes for all inputs of one mix
|
|
+ * (values obtained from private->mix[])
|
|
+ */
|
|
+static int scarlett2_usb_set_mix(struct usb_mixer_interface *mixer,
|
|
+ int mix_num)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ struct {
|
|
+ __le16 mix_num;
|
|
+ __le16 data[SCARLETT2_INPUT_MIX_MAX];
|
|
+ } __packed req;
|
|
+
|
|
+ int i, j;
|
|
+ int num_mixer_in =
|
|
+ info->port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
+
|
|
+ req.mix_num = cpu_to_le16(mix_num);
|
|
+
|
|
+ for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++)
|
|
+ req.data[i] = cpu_to_le16(
|
|
+ scarlett2_mixer_values[private->mix[j]]
|
|
+ );
|
|
+
|
|
+ return scarlett2_usb(mixer, SCARLETT2_USB_SET_MIX,
|
|
+ &req, (num_mixer_in + 1) * sizeof(u16),
|
|
+ NULL, 0);
|
|
+}
|
|
+
|
|
+/* Convert a port number index (per info->port_count) to a hardware ID */
|
|
+static u32 scarlett2_mux_src_num_to_id(
|
|
+ const int port_count[][SCARLETT2_PORT_DIRNS], int num)
|
|
+{
|
|
+ int port_type;
|
|
+
|
|
+ for (port_type = 0;
|
|
+ port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
+ port_type++) {
|
|
+ if (num < port_count[port_type][SCARLETT2_PORT_IN])
|
|
+ return scarlett2_ports[port_type].id | num;
|
|
+ num -= port_count[port_type][SCARLETT2_PORT_IN];
|
|
+ }
|
|
+
|
|
+ /* Oops */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Convert a hardware ID to a port number index */
|
|
+static u32 scarlett2_mux_id_to_num(
|
|
+ const int port_count[][SCARLETT2_PORT_DIRNS], int direction, u32 id)
|
|
+{
|
|
+ int port_type;
|
|
+ int port_num = 0;
|
|
+
|
|
+ for (port_type = 0;
|
|
+ port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
+ port_type++) {
|
|
+ int base = scarlett2_ports[port_type].id;
|
|
+ int count = port_count[port_type][direction];
|
|
+
|
|
+ if (id >= base && id < base + count)
|
|
+ return port_num + id - base;
|
|
+ port_num += count;
|
|
+ }
|
|
+
|
|
+ /* Oops */
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* Convert one mux entry from the interface and load into private->mux[] */
|
|
+static void scarlett2_usb_populate_mux(struct scarlett2_data *private,
|
|
+ u32 mux_entry)
|
|
+{
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+
|
|
+ int dst_idx, src_idx;
|
|
+
|
|
+ dst_idx = scarlett2_mux_id_to_num(port_count, SCARLETT2_PORT_OUT,
|
|
+ mux_entry & 0xFFF);
|
|
+ if (dst_idx < 0)
|
|
+ return;
|
|
+
|
|
+ if (dst_idx >= private->num_mux_dsts) {
|
|
+ usb_audio_err(private->mixer->chip,
|
|
+ "BUG: scarlett2_mux_id_to_num(%06x, OUT): %d >= %d",
|
|
+ mux_entry, dst_idx, private->num_mux_dsts);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ src_idx = scarlett2_mux_id_to_num(port_count, SCARLETT2_PORT_IN,
|
|
+ mux_entry >> 12);
|
|
+ if (src_idx < 0)
|
|
+ return;
|
|
+
|
|
+ if (src_idx >= private->num_mux_srcs) {
|
|
+ usb_audio_err(private->mixer->chip,
|
|
+ "BUG: scarlett2_mux_id_to_num(%06x, IN): %d >= %d",
|
|
+ mux_entry, src_idx, private->num_mux_srcs);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ private->mux[dst_idx] = src_idx;
|
|
+}
|
|
+
|
|
+/* Send USB message to get mux inputs and then populate private->mux[] */
|
|
+static int scarlett2_usb_get_mux(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int count = private->num_mux_dsts;
|
|
+ int err, i;
|
|
+
|
|
+ struct {
|
|
+ __le16 num;
|
|
+ __le16 count;
|
|
+ } __packed req;
|
|
+
|
|
+ __le32 data[SCARLETT2_MUX_MAX];
|
|
+
|
|
+ private->mux_updated = 0;
|
|
+
|
|
+ req.num = 0;
|
|
+ req.count = cpu_to_le16(count);
|
|
+
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_GET_MUX,
|
|
+ &req, sizeof(req),
|
|
+ data, count * sizeof(u32));
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ for (i = 0; i < count; i++)
|
|
+ scarlett2_usb_populate_mux(private, le32_to_cpu(data[i]));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Send USB messages to set mux inputs */
|
|
+static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int table;
|
|
+
|
|
+ struct {
|
|
+ __le16 pad;
|
|
+ __le16 num;
|
|
+ __le32 data[SCARLETT2_MUX_MAX];
|
|
+ } __packed req;
|
|
+
|
|
+ req.pad = 0;
|
|
+
|
|
+ /* set mux settings for each rate */
|
|
+ for (table = 0; table < SCARLETT2_MUX_TABLES; table++) {
|
|
+ const struct scarlett2_mux_entry *entry;
|
|
+
|
|
+ /* i counts over the output array */
|
|
+ int i = 0, err;
|
|
+
|
|
+ req.num = cpu_to_le16(table);
|
|
+
|
|
+ /* loop through each entry */
|
|
+ for (entry = info->mux_assignment[table];
|
|
+ entry->count;
|
|
+ entry++) {
|
|
+ int j;
|
|
+ int port_type = entry->port_type;
|
|
+ int port_idx = entry->start;
|
|
+ int mux_idx = scarlett2_get_port_start_num(port_count,
|
|
+ SCARLETT2_PORT_OUT, port_type) + port_idx;
|
|
+ int dst_id = scarlett2_ports[port_type].id + port_idx;
|
|
+
|
|
+ /* Empty slots */
|
|
+ if (!dst_id) {
|
|
+ for (j = 0; j < entry->count; j++)
|
|
+ req.data[i++] = 0;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* Non-empty mux slots use the lower 12 bits
|
|
+ * for the destination and next 12 bits for
|
|
+ * the source
|
|
+ */
|
|
+ for (j = 0; j < entry->count; j++) {
|
|
+ int src_id = scarlett2_mux_src_num_to_id(
|
|
+ port_count, private->mux[mux_idx++]);
|
|
+ req.data[i++] = cpu_to_le32(dst_id |
|
|
+ src_id << 12);
|
|
+ dst_id++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_SET_MUX,
|
|
+ &req, (i + 1) * sizeof(u32),
|
|
+ NULL, 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Send USB message to get meter levels */
|
|
+static int scarlett2_usb_get_meter_levels(struct usb_mixer_interface *mixer,
|
|
+ u16 num_meters, u16 *levels)
|
|
+{
|
|
+ struct {
|
|
+ __le16 pad;
|
|
+ __le16 num_meters;
|
|
+ __le32 magic;
|
|
+ } __packed req;
|
|
+ u32 resp[SCARLETT2_MAX_METERS];
|
|
+ int i, err;
|
|
+
|
|
+ req.pad = 0;
|
|
+ req.num_meters = cpu_to_le16(num_meters);
|
|
+ req.magic = cpu_to_le32(SCARLETT2_USB_METER_LEVELS_GET_MAGIC);
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_GET_METER,
|
|
+ &req, sizeof(req), resp, num_meters * sizeof(u32));
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* copy, convert to u16 */
|
|
+ for (i = 0; i < num_meters; i++)
|
|
+ levels[i] = resp[i];
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Control Functions ***/
|
|
+
|
|
+/* helper function to create a new control */
|
|
+static int scarlett2_add_new_ctl(struct usb_mixer_interface *mixer,
|
|
+ const struct snd_kcontrol_new *ncontrol,
|
|
+ int index, int channels, const char *name,
|
|
+ struct snd_kcontrol **kctl_return)
|
|
+{
|
|
+ struct snd_kcontrol *kctl;
|
|
+ struct usb_mixer_elem_info *elem;
|
|
+ int err;
|
|
+
|
|
+ elem = kzalloc(sizeof(*elem), GFP_KERNEL);
|
|
+ if (!elem)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code
|
|
+ * ignores them for resume and other operations.
|
|
+ * Also, the head.id field is set to 0, as we don't use this field.
|
|
+ */
|
|
+ elem->head.mixer = mixer;
|
|
+ elem->control = index;
|
|
+ elem->head.id = 0;
|
|
+ elem->channels = channels;
|
|
+ elem->val_type = USB_MIXER_BESPOKEN;
|
|
+
|
|
+ kctl = snd_ctl_new1(ncontrol, elem);
|
|
+ if (!kctl) {
|
|
+ kfree(elem);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ kctl->private_free = snd_usb_mixer_elem_free;
|
|
+
|
|
+ strscpy(kctl->id.name, name, sizeof(kctl->id.name));
|
|
+
|
|
+ err = snd_usb_mixer_add_control(&elem->head, kctl);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ if (kctl_return)
|
|
+ *kctl_return = kctl;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Sync Control ***/
|
|
+
|
|
+/* Update sync control after receiving notification that the status
|
|
+ * has changed
|
|
+ */
|
|
+static int scarlett2_update_sync(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ private->sync_updated = 0;
|
|
+ return scarlett2_usb_get_sync_status(mixer, &private->sync);
|
|
+}
|
|
+
|
|
+static int scarlett2_sync_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ static const char *texts[2] = {
|
|
+ "Unlocked", "Locked"
|
|
+ };
|
|
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
|
+}
|
|
+
|
|
+static int scarlett2_sync_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->sync_updated) {
|
|
+ err = scarlett2_update_sync(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.enumerated.item[0] = private->sync;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_sync_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
+ .name = "",
|
|
+ .info = scarlett2_sync_ctl_info,
|
|
+ .get = scarlett2_sync_ctl_get
|
|
+};
|
|
+
|
|
+static int scarlett2_add_sync_ctl(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ /* devices without a mixer also don't support reporting sync status */
|
|
+ if (private->info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
+ return 0;
|
|
+
|
|
+ return scarlett2_add_new_ctl(mixer, &scarlett2_sync_ctl,
|
|
+ 0, 1, "Sync Status", &private->sync_ctl);
|
|
+}
|
|
+
|
|
+/*** Analogue Line Out Volume Controls ***/
|
|
+
|
|
+/* Update hardware volume controls after receiving notification that
|
|
+ * they have changed
|
|
+ */
|
|
+static int scarlett2_update_volumes(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ struct scarlett2_usb_volume_status volume_status;
|
|
+ int num_line_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+ int err, i;
|
|
+ int mute;
|
|
+
|
|
+ private->vol_updated = 0;
|
|
+
|
|
+ err = scarlett2_usb_get_volume_status(mixer, &volume_status);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ private->master_vol = clamp(
|
|
+ volume_status.master_vol + SCARLETT2_VOLUME_BIAS,
|
|
+ 0, SCARLETT2_VOLUME_BIAS);
|
|
+
|
|
+ if (info->line_out_hw_vol)
|
|
+ for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++)
|
|
+ private->dim_mute[i] = !!volume_status.dim_mute[i];
|
|
+
|
|
+ mute = private->dim_mute[SCARLETT2_BUTTON_MUTE];
|
|
+
|
|
+ for (i = 0; i < num_line_out; i++)
|
|
+ if (private->vol_sw_hw_switch[i]) {
|
|
+ private->vol[i] = private->master_vol;
|
|
+ private->mute_switch[i] = mute;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_volume_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
+ uinfo->count = elem->channels;
|
|
+ uinfo->value.integer.min = 0;
|
|
+ uinfo->value.integer.max = SCARLETT2_VOLUME_BIAS;
|
|
+ uinfo->value.integer.step = 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->vol_updated) {
|
|
+ err = scarlett2_update_volumes(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] = private->master_vol;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int line_out_remap(struct scarlett2_data *private, int index)
|
|
+{
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int line_out_count =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+
|
|
+ if (!info->line_out_remap_enable)
|
|
+ return index;
|
|
+
|
|
+ if (index >= line_out_count)
|
|
+ return index;
|
|
+
|
|
+ return info->line_out_remap[index];
|
|
+}
|
|
+
|
|
+static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->vol_updated) {
|
|
+ err = scarlett2_update_volumes(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] = private->vol[index];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->vol[index];
|
|
+ val = ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->vol[index] = val;
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME,
|
|
+ index, val - SCARLETT2_VOLUME_BIAS);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const DECLARE_TLV_DB_MINMAX(
|
|
+ db_scale_scarlett2_gain, -SCARLETT2_VOLUME_BIAS * 100, 0
|
|
+);
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_master_volume_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
|
+ .name = "",
|
|
+ .info = scarlett2_volume_ctl_info,
|
|
+ .get = scarlett2_master_volume_ctl_get,
|
|
+ .private_value = 0, /* max value */
|
|
+ .tlv = { .p = db_scale_scarlett2_gain }
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_line_out_volume_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
|
+ .name = "",
|
|
+ .info = scarlett2_volume_ctl_info,
|
|
+ .get = scarlett2_volume_ctl_get,
|
|
+ .put = scarlett2_volume_ctl_put,
|
|
+ .private_value = 0, /* max value */
|
|
+ .tlv = { .p = db_scale_scarlett2_gain }
|
|
+};
|
|
+
|
|
+/*** Mute Switch Controls ***/
|
|
+
|
|
+static int scarlett2_mute_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->vol_updated) {
|
|
+ err = scarlett2_update_volumes(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] = private->mute_switch[index];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_mute_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->mute_switch[index];
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->mute_switch[index] = val;
|
|
+
|
|
+ /* Send mute change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MUTE_SWITCH,
|
|
+ index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_mute_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_mute_ctl_get,
|
|
+ .put = scarlett2_mute_ctl_put,
|
|
+};
|
|
+
|
|
+/*** HW/SW Volume Switch Controls ***/
|
|
+
|
|
+static void scarlett2_sw_hw_ctl_ro(struct scarlett2_data *private, int index)
|
|
+{
|
|
+ private->sw_hw_ctls[index]->vd[0].access &=
|
|
+ ~SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
+}
|
|
+
|
|
+static void scarlett2_sw_hw_ctl_rw(struct scarlett2_data *private, int index)
|
|
+{
|
|
+ private->sw_hw_ctls[index]->vd[0].access |=
|
|
+ SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
+}
|
|
+
|
|
+static int scarlett2_sw_hw_enum_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ static const char *const values[2] = {
|
|
+ "SW", "HW"
|
|
+ };
|
|
+
|
|
+ return snd_ctl_enum_info(uinfo, 1, 2, values);
|
|
+}
|
|
+
|
|
+static int scarlett2_sw_hw_enum_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+
|
|
+ ucontrol->value.enumerated.item[0] = private->vol_sw_hw_switch[index];
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void scarlett2_vol_ctl_set_writable(struct usb_mixer_interface *mixer,
|
|
+ int index, int value)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+
|
|
+ /* Set/Clear write bits */
|
|
+ if (value) {
|
|
+ private->vol_ctls[index]->vd[0].access |=
|
|
+ SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
+ private->mute_ctls[index]->vd[0].access |=
|
|
+ SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
+ } else {
|
|
+ private->vol_ctls[index]->vd[0].access &=
|
|
+ ~SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
+ private->mute_ctls[index]->vd[0].access &=
|
|
+ ~SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
+ }
|
|
+
|
|
+ /* Notify of write bit and possible value change */
|
|
+ snd_ctl_notify(card,
|
|
+ SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
|
|
+ &private->vol_ctls[index]->id);
|
|
+ snd_ctl_notify(card,
|
|
+ SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
|
|
+ &private->mute_ctls[index]->id);
|
|
+}
|
|
+
|
|
+static int scarlett2_sw_hw_change(struct usb_mixer_interface *mixer,
|
|
+ int ctl_index, int val)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, ctl_index);
|
|
+ int err;
|
|
+
|
|
+ private->vol_sw_hw_switch[index] = val;
|
|
+
|
|
+ /* Change access mode to RO (hardware controlled volume)
|
|
+ * or RW (software controlled volume)
|
|
+ */
|
|
+ scarlett2_vol_ctl_set_writable(mixer, ctl_index, !val);
|
|
+
|
|
+ /* Reset volume/mute to master volume/mute */
|
|
+ private->vol[index] = private->master_vol;
|
|
+ private->mute_switch[index] = private->dim_mute[SCARLETT2_BUTTON_MUTE];
|
|
+
|
|
+ /* Set SW volume to current HW volume */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME,
|
|
+ index, private->master_vol - SCARLETT2_VOLUME_BIAS);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Set SW mute to current HW mute */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_MUTE_SWITCH,
|
|
+ index, private->dim_mute[SCARLETT2_BUTTON_MUTE]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Send SW/HW switch change to the device */
|
|
+ return scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SW_HW_SWITCH,
|
|
+ index, val);
|
|
+}
|
|
+
|
|
+static int scarlett2_sw_hw_enum_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int ctl_index = elem->control;
|
|
+ int index = line_out_remap(private, ctl_index);
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->vol_sw_hw_switch[index];
|
|
+ val = !!ucontrol->value.enumerated.item[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ err = scarlett2_sw_hw_change(mixer, ctl_index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_sw_hw_enum_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = scarlett2_sw_hw_enum_ctl_info,
|
|
+ .get = scarlett2_sw_hw_enum_ctl_get,
|
|
+ .put = scarlett2_sw_hw_enum_ctl_put,
|
|
+};
|
|
+
|
|
+/*** Line Level/Instrument Level Switch Controls ***/
|
|
+
|
|
+static int scarlett2_update_input_other(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ private->input_other_updated = 0;
|
|
+
|
|
+ if (info->level_input_count) {
|
|
+ int err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_LEVEL_SWITCH,
|
|
+ info->level_input_count + info->level_input_first,
|
|
+ private->level_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ if (info->pad_input_count) {
|
|
+ int err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_PAD_SWITCH,
|
|
+ info->pad_input_count, private->pad_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ if (info->air_input_count) {
|
|
+ int err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_AIR_SWITCH,
|
|
+ info->air_input_count, private->air_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ if (info->phantom_count) {
|
|
+ int err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_PHANTOM_SWITCH,
|
|
+ info->phantom_count, private->phantom_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE,
|
|
+ 1, &private->phantom_persistence);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_level_enum_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ static const char *const values[2] = {
|
|
+ "Line", "Inst"
|
|
+ };
|
|
+
|
|
+ return snd_ctl_enum_info(uinfo, 1, 2, values);
|
|
+}
|
|
+
|
|
+static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ int index = elem->control + info->level_input_first;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->input_other_updated) {
|
|
+ err = scarlett2_update_input_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.enumerated.item[0] = private->level_switch[index];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ int index = elem->control + info->level_input_first;
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->level_switch[index];
|
|
+ val = !!ucontrol->value.enumerated.item[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->level_switch[index] = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LEVEL_SWITCH,
|
|
+ index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_level_enum_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = scarlett2_level_enum_ctl_info,
|
|
+ .get = scarlett2_level_enum_ctl_get,
|
|
+ .put = scarlett2_level_enum_ctl_put,
|
|
+};
|
|
+
|
|
+/*** Pad Switch Controls ***/
|
|
+
|
|
+static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->input_other_updated) {
|
|
+ err = scarlett2_update_input_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] =
|
|
+ private->pad_switch[elem->control];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->pad_switch[index];
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->pad_switch[index] = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PAD_SWITCH,
|
|
+ index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_pad_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_pad_ctl_get,
|
|
+ .put = scarlett2_pad_ctl_put,
|
|
+};
|
|
+
|
|
+/*** Air Switch Controls ***/
|
|
+
|
|
+static int scarlett2_air_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->input_other_updated) {
|
|
+ err = scarlett2_update_input_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] = private->air_switch[elem->control];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_air_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->air_switch[index];
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->air_switch[index] = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_AIR_SWITCH,
|
|
+ index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_air_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_air_ctl_get,
|
|
+ .put = scarlett2_air_ctl_put,
|
|
+};
|
|
+
|
|
+/*** Phantom Switch Controls ***/
|
|
+
|
|
+static int scarlett2_phantom_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->input_other_updated) {
|
|
+ err = scarlett2_update_input_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] =
|
|
+ private->phantom_switch[elem->control];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_phantom_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->phantom_switch[index];
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->phantom_switch[index] = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PHANTOM_SWITCH,
|
|
+ index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_phantom_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_phantom_ctl_get,
|
|
+ .put = scarlett2_phantom_ctl_put,
|
|
+};
|
|
+
|
|
+/*** Phantom Persistence Control ***/
|
|
+
|
|
+static int scarlett2_phantom_persistence_ctl_get(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+
|
|
+ ucontrol->value.integer.value[0] = private->phantom_persistence;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_phantom_persistence_ctl_put(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->phantom_persistence;
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->phantom_persistence = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE, index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_phantom_persistence_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_phantom_persistence_ctl_get,
|
|
+ .put = scarlett2_phantom_persistence_ctl_put,
|
|
+};
|
|
+
|
|
+/*** Direct Monitor Control ***/
|
|
+
|
|
+static int scarlett2_update_monitor_other(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ int err;
|
|
+
|
|
+ /* monitor_other_enable[0] enables speaker switching
|
|
+ * monitor_other_enable[1] enables talkback
|
|
+ */
|
|
+ u8 monitor_other_enable[2];
|
|
+
|
|
+ /* monitor_other_switch[0] activates the alternate speakers
|
|
+ * monitor_other_switch[1] activates talkback
|
|
+ */
|
|
+ u8 monitor_other_switch[2];
|
|
+
|
|
+ private->monitor_other_updated = 0;
|
|
+
|
|
+ if (info->direct_monitor)
|
|
+ return scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_DIRECT_MONITOR,
|
|
+ 1, &private->direct_monitor_switch);
|
|
+
|
|
+ /* if it doesn't do speaker switching then it also doesn't do
|
|
+ * talkback
|
|
+ */
|
|
+ if (!info->has_speaker_switching)
|
|
+ return 0;
|
|
+
|
|
+ err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
|
|
+ 2, monitor_other_enable);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
|
|
+ 2, monitor_other_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ if (!monitor_other_enable[0])
|
|
+ private->speaker_switching_switch = 0;
|
|
+ else
|
|
+ private->speaker_switching_switch = monitor_other_switch[0] + 1;
|
|
+
|
|
+ if (info->has_talkback) {
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] =
|
|
+ info->port_count;
|
|
+ int num_mixes =
|
|
+ port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
+ u16 bitmap;
|
|
+ int i;
|
|
+
|
|
+ if (!monitor_other_enable[1])
|
|
+ private->talkback_switch = 0;
|
|
+ else
|
|
+ private->talkback_switch = monitor_other_switch[1] + 1;
|
|
+
|
|
+ err = scarlett2_usb_get_config(mixer,
|
|
+ SCARLETT2_CONFIG_TALKBACK_MAP,
|
|
+ 1, &bitmap);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ for (i = 0; i < num_mixes; i++, bitmap >>= 1)
|
|
+ private->talkback_map[i] = bitmap & 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_direct_monitor_ctl_get(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->monitor_other_updated) {
|
|
+ err = scarlett2_update_monitor_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.enumerated.item[0] = private->direct_monitor_switch;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_direct_monitor_ctl_put(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->direct_monitor_switch;
|
|
+ val = min(ucontrol->value.enumerated.item[0], 2U);
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->direct_monitor_switch = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_DIRECT_MONITOR, index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_direct_monitor_stereo_enum_ctl_info(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ static const char *const values[3] = {
|
|
+ "Off", "Mono", "Stereo"
|
|
+ };
|
|
+
|
|
+ return snd_ctl_enum_info(uinfo, 1, 3, values);
|
|
+}
|
|
+
|
|
+/* Direct Monitor for Solo is mono-only and only needs a boolean control
|
|
+ * Direct Monitor for 2i2 is selectable between Off/Mono/Stereo
|
|
+ */
|
|
+static const struct snd_kcontrol_new scarlett2_direct_monitor_ctl[2] = {
|
|
+ {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_direct_monitor_ctl_get,
|
|
+ .put = scarlett2_direct_monitor_ctl_put,
|
|
+ },
|
|
+ {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = scarlett2_direct_monitor_stereo_enum_ctl_info,
|
|
+ .get = scarlett2_direct_monitor_ctl_get,
|
|
+ .put = scarlett2_direct_monitor_ctl_put,
|
|
+ }
|
|
+};
|
|
+
|
|
+static int scarlett2_add_direct_monitor_ctl(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const char *s;
|
|
+
|
|
+ if (!info->direct_monitor)
|
|
+ return 0;
|
|
+
|
|
+ s = info->direct_monitor == 1
|
|
+ ? "Direct Monitor Playback Switch"
|
|
+ : "Direct Monitor Playback Enum";
|
|
+
|
|
+ return scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_direct_monitor_ctl[info->direct_monitor - 1],
|
|
+ 0, 1, s, &private->direct_monitor_ctl);
|
|
+}
|
|
+
|
|
+/*** Speaker Switching Control ***/
|
|
+
|
|
+static int scarlett2_speaker_switch_enum_ctl_info(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ static const char *const values[3] = {
|
|
+ "Off", "Main", "Alt"
|
|
+ };
|
|
+
|
|
+ return snd_ctl_enum_info(uinfo, 1, 3, values);
|
|
+}
|
|
+
|
|
+static int scarlett2_speaker_switch_enum_ctl_get(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->monitor_other_updated) {
|
|
+ err = scarlett2_update_monitor_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.enumerated.item[0] = private->speaker_switching_switch;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+/* when speaker switching gets enabled, switch the main/alt speakers
|
|
+ * to HW volume and disable those controls
|
|
+ */
|
|
+static int scarlett2_speaker_switch_enable(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int i, err;
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ int index = line_out_remap(private, i);
|
|
+
|
|
+ /* switch the main/alt speakers to HW volume */
|
|
+ if (!private->vol_sw_hw_switch[index]) {
|
|
+ err = scarlett2_sw_hw_change(private->mixer, i, 1);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* disable the line out SW/HW switch */
|
|
+ scarlett2_sw_hw_ctl_ro(private, i);
|
|
+ snd_ctl_notify(card,
|
|
+ SNDRV_CTL_EVENT_MASK_VALUE |
|
|
+ SNDRV_CTL_EVENT_MASK_INFO,
|
|
+ &private->sw_hw_ctls[i]->id);
|
|
+ }
|
|
+
|
|
+ /* when the next monitor-other notify comes in, update the mux
|
|
+ * configuration
|
|
+ */
|
|
+ private->speaker_switching_switched = 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* when speaker switching gets disabled, reenable the hw/sw controls
|
|
+ * and invalidate the routing
|
|
+ */
|
|
+static void scarlett2_speaker_switch_disable(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int i;
|
|
+
|
|
+ /* enable the line out SW/HW switch */
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ scarlett2_sw_hw_ctl_rw(private, i);
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO,
|
|
+ &private->sw_hw_ctls[i]->id);
|
|
+ }
|
|
+
|
|
+ /* when the next monitor-other notify comes in, update the mux
|
|
+ * configuration
|
|
+ */
|
|
+ private->speaker_switching_switched = 1;
|
|
+}
|
|
+
|
|
+static int scarlett2_speaker_switch_enum_ctl_put(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->speaker_switching_switch;
|
|
+ val = min(ucontrol->value.enumerated.item[0], 2U);
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->speaker_switching_switch = val;
|
|
+
|
|
+ /* enable/disable speaker switching */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
|
|
+ 0, !!val);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+
|
|
+ /* if speaker switching is enabled, select main or alt */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
|
|
+ 0, val == 2);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+
|
|
+ /* update controls if speaker switching gets enabled or disabled */
|
|
+ if (!oval && val)
|
|
+ err = scarlett2_speaker_switch_enable(mixer);
|
|
+ else if (oval && !val)
|
|
+ scarlett2_speaker_switch_disable(mixer);
|
|
+
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_speaker_switch_enum_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = scarlett2_speaker_switch_enum_ctl_info,
|
|
+ .get = scarlett2_speaker_switch_enum_ctl_get,
|
|
+ .put = scarlett2_speaker_switch_enum_ctl_put,
|
|
+};
|
|
+
|
|
+static int scarlett2_add_speaker_switch_ctl(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ if (!info->has_speaker_switching)
|
|
+ return 0;
|
|
+
|
|
+ return scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_speaker_switch_enum_ctl,
|
|
+ 0, 1, "Speaker Switching Playback Enum",
|
|
+ &private->speaker_switching_ctl);
|
|
+}
|
|
+
|
|
+/*** Talkback and Talkback Map Controls ***/
|
|
+
|
|
+static int scarlett2_talkback_enum_ctl_info(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ static const char *const values[3] = {
|
|
+ "Disabled", "Off", "On"
|
|
+ };
|
|
+
|
|
+ return snd_ctl_enum_info(uinfo, 1, 3, values);
|
|
+}
|
|
+
|
|
+static int scarlett2_talkback_enum_ctl_get(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->monitor_other_updated) {
|
|
+ err = scarlett2_update_monitor_other(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.enumerated.item[0] = private->talkback_switch;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_talkback_enum_ctl_put(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->talkback_switch;
|
|
+ val = min(ucontrol->value.enumerated.item[0], 2U);
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->talkback_switch = val;
|
|
+
|
|
+ /* enable/disable talkback */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
|
|
+ 1, !!val);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+
|
|
+ /* if talkback is enabled, select main or alt */
|
|
+ err = scarlett2_usb_set_config(
|
|
+ mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
|
|
+ 1, val == 2);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_talkback_enum_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = scarlett2_talkback_enum_ctl_info,
|
|
+ .get = scarlett2_talkback_enum_ctl_get,
|
|
+ .put = scarlett2_talkback_enum_ctl_put,
|
|
+};
|
|
+
|
|
+static int scarlett2_talkback_map_ctl_get(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = elem->control;
|
|
+
|
|
+ ucontrol->value.integer.value[0] = private->talkback_map[index];
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_talkback_map_ctl_put(
|
|
+ struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] =
|
|
+ private->info->port_count;
|
|
+ int num_mixes = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0, i;
|
|
+ u16 bitmap = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->talkback_map[index];
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->talkback_map[index] = val;
|
|
+
|
|
+ for (i = 0; i < num_mixes; i++)
|
|
+ bitmap |= private->talkback_map[i] << i;
|
|
+
|
|
+ /* Send updated bitmap to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_TALKBACK_MAP,
|
|
+ 0, bitmap);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_talkback_map_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_talkback_map_ctl_get,
|
|
+ .put = scarlett2_talkback_map_ctl_put,
|
|
+};
|
|
+
|
|
+static int scarlett2_add_talkback_ctls(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int num_mixes = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
+ int err, i;
|
|
+ char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
+
|
|
+ if (!info->has_talkback)
|
|
+ return 0;
|
|
+
|
|
+ err = scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_talkback_enum_ctl,
|
|
+ 0, 1, "Talkback Playback Enum",
|
|
+ &private->talkback_ctl);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ for (i = 0; i < num_mixes; i++) {
|
|
+ snprintf(s, sizeof(s),
|
|
+ "Talkback Mix %c Playback Switch", i + 'A');
|
|
+ err = scarlett2_add_new_ctl(mixer, &scarlett2_talkback_map_ctl,
|
|
+ i, 1, s, NULL);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Dim/Mute Controls ***/
|
|
+
|
|
+static int scarlett2_dim_mute_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->vol_updated) {
|
|
+ err = scarlett2_update_volumes(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] = private->dim_mute[elem->control];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_dim_mute_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int num_line_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+
|
|
+ int index = elem->control;
|
|
+ int oval, val, err = 0, i;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->dim_mute[index];
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->dim_mute[index] = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_DIM_MUTE,
|
|
+ index, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+ if (index == SCARLETT2_BUTTON_MUTE)
|
|
+ for (i = 0; i < num_line_out; i++) {
|
|
+ int line_index = line_out_remap(private, i);
|
|
+
|
|
+ if (private->vol_sw_hw_switch[line_index]) {
|
|
+ private->mute_switch[line_index] = val;
|
|
+ snd_ctl_notify(mixer->chip->card,
|
|
+ SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->mute_ctls[i]->id);
|
|
+ }
|
|
+ }
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_dim_mute_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_dim_mute_ctl_get,
|
|
+ .put = scarlett2_dim_mute_ctl_put
|
|
+};
|
|
+
|
|
+/*** Create the analogue output controls ***/
|
|
+
|
|
+static int scarlett2_add_line_out_ctls(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int num_line_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+ int err, i;
|
|
+ char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
+
|
|
+ /* Add R/O HW volume control */
|
|
+ if (info->line_out_hw_vol) {
|
|
+ snprintf(s, sizeof(s), "Master HW Playback Volume");
|
|
+ err = scarlett2_add_new_ctl(mixer,
|
|
+ &scarlett2_master_volume_ctl,
|
|
+ 0, 1, s, &private->master_vol_ctl);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Add volume controls */
|
|
+ for (i = 0; i < num_line_out; i++) {
|
|
+ int index = line_out_remap(private, i);
|
|
+
|
|
+ /* Fader */
|
|
+ if (info->line_out_descrs[i])
|
|
+ snprintf(s, sizeof(s),
|
|
+ "Line %02d (%s) Playback Volume",
|
|
+ i + 1, info->line_out_descrs[i]);
|
|
+ else
|
|
+ snprintf(s, sizeof(s),
|
|
+ "Line %02d Playback Volume",
|
|
+ i + 1);
|
|
+ err = scarlett2_add_new_ctl(mixer,
|
|
+ &scarlett2_line_out_volume_ctl,
|
|
+ i, 1, s, &private->vol_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Mute Switch */
|
|
+ snprintf(s, sizeof(s),
|
|
+ "Line %02d Mute Playback Switch",
|
|
+ i + 1);
|
|
+ err = scarlett2_add_new_ctl(mixer,
|
|
+ &scarlett2_mute_ctl,
|
|
+ i, 1, s,
|
|
+ &private->mute_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Make the fader and mute controls read-only if the
|
|
+ * SW/HW switch is set to HW
|
|
+ */
|
|
+ if (private->vol_sw_hw_switch[index])
|
|
+ scarlett2_vol_ctl_set_writable(mixer, i, 0);
|
|
+
|
|
+ /* SW/HW Switch */
|
|
+ if (info->line_out_hw_vol) {
|
|
+ snprintf(s, sizeof(s),
|
|
+ "Line Out %02d Volume Control Playback Enum",
|
|
+ i + 1);
|
|
+ err = scarlett2_add_new_ctl(mixer,
|
|
+ &scarlett2_sw_hw_enum_ctl,
|
|
+ i, 1, s,
|
|
+ &private->sw_hw_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Make the switch read-only if the line is
|
|
+ * involved in speaker switching
|
|
+ */
|
|
+ if (private->speaker_switching_switch && i < 4)
|
|
+ scarlett2_sw_hw_ctl_ro(private, i);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Add dim/mute controls */
|
|
+ if (info->line_out_hw_vol)
|
|
+ for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++) {
|
|
+ err = scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_dim_mute_ctl,
|
|
+ i, 1, scarlett2_dim_mute_names[i],
|
|
+ &private->dim_mute_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Create the analogue input controls ***/
|
|
+
|
|
+static int scarlett2_add_line_in_ctls(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ int err, i;
|
|
+ char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
+ const char *fmt = "Line In %d %s Capture %s";
|
|
+ const char *fmt2 = "Line In %d-%d %s Capture %s";
|
|
+
|
|
+ /* Add input level (line/inst) controls */
|
|
+ for (i = 0; i < info->level_input_count; i++) {
|
|
+ snprintf(s, sizeof(s), fmt, i + 1 + info->level_input_first,
|
|
+ "Level", "Enum");
|
|
+ err = scarlett2_add_new_ctl(mixer, &scarlett2_level_enum_ctl,
|
|
+ i, 1, s, &private->level_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Add input pad controls */
|
|
+ for (i = 0; i < info->pad_input_count; i++) {
|
|
+ snprintf(s, sizeof(s), fmt, i + 1, "Pad", "Switch");
|
|
+ err = scarlett2_add_new_ctl(mixer, &scarlett2_pad_ctl,
|
|
+ i, 1, s, &private->pad_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Add input air controls */
|
|
+ for (i = 0; i < info->air_input_count; i++) {
|
|
+ snprintf(s, sizeof(s), fmt, i + 1, "Air", "Switch");
|
|
+ err = scarlett2_add_new_ctl(mixer, &scarlett2_air_ctl,
|
|
+ i, 1, s, &private->air_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ /* Add input phantom controls */
|
|
+ if (info->inputs_per_phantom == 1) {
|
|
+ for (i = 0; i < info->phantom_count; i++) {
|
|
+ scnprintf(s, sizeof(s), fmt, i + 1,
|
|
+ "Phantom Power", "Switch");
|
|
+ err = scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_phantom_ctl,
|
|
+ i, 1, s, &private->phantom_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+ } else if (info->inputs_per_phantom > 1) {
|
|
+ for (i = 0; i < info->phantom_count; i++) {
|
|
+ int from = i * info->inputs_per_phantom + 1;
|
|
+ int to = (i + 1) * info->inputs_per_phantom;
|
|
+
|
|
+ scnprintf(s, sizeof(s), fmt2, from, to,
|
|
+ "Phantom Power", "Switch");
|
|
+ err = scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_phantom_ctl,
|
|
+ i, 1, s, &private->phantom_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+ }
|
|
+ if (info->phantom_count) {
|
|
+ err = scarlett2_add_new_ctl(
|
|
+ mixer, &scarlett2_phantom_persistence_ctl, 0, 1,
|
|
+ "Phantom Power Persistence Capture Switch", NULL);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Mixer Volume Controls ***/
|
|
+
|
|
+static int scarlett2_mixer_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
+ uinfo->count = elem->channels;
|
|
+ uinfo->value.integer.min = 0;
|
|
+ uinfo->value.integer.max = SCARLETT2_MIXER_MAX_VALUE;
|
|
+ uinfo->value.integer.step = 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_mixer_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+
|
|
+ ucontrol->value.integer.value[0] = private->mix[elem->control];
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int oval, val, num_mixer_in, mix_num, err = 0;
|
|
+ int index = elem->control;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->mix[index];
|
|
+ val = clamp(ucontrol->value.integer.value[0],
|
|
+ 0L, (long)SCARLETT2_MIXER_MAX_VALUE);
|
|
+ num_mixer_in = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
+ mix_num = index / num_mixer_in;
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->mix[index] = val;
|
|
+ err = scarlett2_usb_set_mix(mixer, mix_num);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const DECLARE_TLV_DB_MINMAX(
|
|
+ db_scale_scarlett2_mixer,
|
|
+ SCARLETT2_MIXER_MIN_DB * 100,
|
|
+ SCARLETT2_MIXER_MAX_DB * 100
|
|
+);
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_mixer_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
|
+ .name = "",
|
|
+ .info = scarlett2_mixer_ctl_info,
|
|
+ .get = scarlett2_mixer_ctl_get,
|
|
+ .put = scarlett2_mixer_ctl_put,
|
|
+ .private_value = SCARLETT2_MIXER_MAX_DB, /* max value */
|
|
+ .tlv = { .p = db_scale_scarlett2_mixer }
|
|
+};
|
|
+
|
|
+static int scarlett2_add_mixer_ctls(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int err, i, j;
|
|
+ int index;
|
|
+ char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
+
|
|
+ int num_inputs =
|
|
+ port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
+ int num_outputs =
|
|
+ port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
+
|
|
+ for (i = 0, index = 0; i < num_outputs; i++)
|
|
+ for (j = 0; j < num_inputs; j++, index++) {
|
|
+ snprintf(s, sizeof(s),
|
|
+ "Mix %c Input %02d Playback Volume",
|
|
+ 'A' + i, j + 1);
|
|
+ err = scarlett2_add_new_ctl(mixer, &scarlett2_mixer_ctl,
|
|
+ index, 1, s, NULL);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Mux Source Selection Controls ***/
|
|
+
|
|
+static int scarlett2_mux_src_enum_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ unsigned int item = uinfo->value.enumerated.item;
|
|
+ int items = private->num_mux_srcs;
|
|
+ int port_type;
|
|
+
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
+ uinfo->count = elem->channels;
|
|
+ uinfo->value.enumerated.items = items;
|
|
+
|
|
+ if (item >= items)
|
|
+ item = uinfo->value.enumerated.item = items - 1;
|
|
+
|
|
+ for (port_type = 0;
|
|
+ port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
+ port_type++) {
|
|
+ if (item < port_count[port_type][SCARLETT2_PORT_IN]) {
|
|
+ const struct scarlett2_port *port =
|
|
+ &scarlett2_ports[port_type];
|
|
+
|
|
+ sprintf(uinfo->value.enumerated.name,
|
|
+ port->src_descr, item + port->src_num_offset);
|
|
+ return 0;
|
|
+ }
|
|
+ item -= port_count[port_type][SCARLETT2_PORT_IN];
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+ int err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ if (private->mux_updated) {
|
|
+ err = scarlett2_usb_get_mux(mixer);
|
|
+ if (err < 0)
|
|
+ goto unlock;
|
|
+ }
|
|
+ ucontrol->value.enumerated.item[0] = private->mux[index];
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ int index = line_out_remap(private, elem->control);
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->mux[index];
|
|
+ val = min(ucontrol->value.enumerated.item[0],
|
|
+ private->num_mux_srcs - 1U);
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->mux[index] = val;
|
|
+ err = scarlett2_usb_set_mux(mixer);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_mux_src_enum_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = scarlett2_mux_src_enum_ctl_info,
|
|
+ .get = scarlett2_mux_src_enum_ctl_get,
|
|
+ .put = scarlett2_mux_src_enum_ctl_put,
|
|
+};
|
|
+
|
|
+static int scarlett2_add_mux_enums(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int port_type, channel, i;
|
|
+
|
|
+ for (i = 0, port_type = 0;
|
|
+ port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
+ port_type++) {
|
|
+ for (channel = 0;
|
|
+ channel < port_count[port_type][SCARLETT2_PORT_OUT];
|
|
+ channel++, i++) {
|
|
+ int err;
|
|
+ char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
+ const char *const descr =
|
|
+ scarlett2_ports[port_type].dst_descr;
|
|
+
|
|
+ snprintf(s, sizeof(s) - 5, descr, channel + 1);
|
|
+ strcat(s, " Enum");
|
|
+
|
|
+ err = scarlett2_add_new_ctl(mixer,
|
|
+ &scarlett2_mux_src_enum_ctl,
|
|
+ i, 1, s,
|
|
+ &private->mux_ctls[i]);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*** Meter Controls ***/
|
|
+
|
|
+static int scarlett2_meter_ctl_info(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
+ uinfo->count = elem->channels;
|
|
+ uinfo->value.integer.min = 0;
|
|
+ uinfo->value.integer.max = 4095;
|
|
+ uinfo->value.integer.step = 1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ u16 meter_levels[SCARLETT2_MAX_METERS];
|
|
+ int i, err;
|
|
+
|
|
+ err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels,
|
|
+ meter_levels);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ for (i = 0; i < elem->channels; i++)
|
|
+ ucontrol->value.integer.value[i] = meter_levels[i];
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_meter_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
|
+ .name = "",
|
|
+ .info = scarlett2_meter_ctl_info,
|
|
+ .get = scarlett2_meter_ctl_get
|
|
+};
|
|
+
|
|
+static int scarlett2_add_meter_ctl(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ /* devices without a mixer also don't support reporting levels */
|
|
+ if (private->info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
+ return 0;
|
|
+
|
|
+ return scarlett2_add_new_ctl(mixer, &scarlett2_meter_ctl,
|
|
+ 0, private->num_mux_dsts,
|
|
+ "Level Meter", NULL);
|
|
+}
|
|
+
|
|
+/*** MSD Controls ***/
|
|
+
|
|
+static int scarlett2_msd_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+
|
|
+ ucontrol->value.integer.value[0] = private->msd_switch;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_msd_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->msd_switch;
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->msd_switch = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MSD_SWITCH,
|
|
+ 0, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_msd_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_msd_ctl_get,
|
|
+ .put = scarlett2_msd_ctl_put,
|
|
+};
|
|
+
|
|
+static int scarlett2_add_msd_ctl(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ if (!info->has_msd_mode)
|
|
+ return 0;
|
|
+
|
|
+ /* If MSD mode is off, hide the switch by default */
|
|
+ if (!private->msd_switch && !(mixer->chip->setup & SCARLETT2_MSD_ENABLE))
|
|
+ return 0;
|
|
+
|
|
+ /* Add MSD control */
|
|
+ return scarlett2_add_new_ctl(mixer, &scarlett2_msd_ctl,
|
|
+ 0, 1, "MSD Mode Switch", NULL);
|
|
+}
|
|
+
|
|
+/*** Standalone Control ***/
|
|
+
|
|
+static int scarlett2_standalone_ctl_get(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
+
|
|
+ ucontrol->value.integer.value[0] = private->standalone_switch;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int scarlett2_standalone_ctl_put(struct snd_kcontrol *kctl,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
+ struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ int oval, val, err = 0;
|
|
+
|
|
+ mutex_lock(&private->data_mutex);
|
|
+
|
|
+ oval = private->standalone_switch;
|
|
+ val = !!ucontrol->value.integer.value[0];
|
|
+
|
|
+ if (oval == val)
|
|
+ goto unlock;
|
|
+
|
|
+ private->standalone_switch = val;
|
|
+
|
|
+ /* Send switch change to the device */
|
|
+ err = scarlett2_usb_set_config(mixer,
|
|
+ SCARLETT2_CONFIG_STANDALONE_SWITCH,
|
|
+ 0, val);
|
|
+ if (err == 0)
|
|
+ err = 1;
|
|
+
|
|
+unlock:
|
|
+ mutex_unlock(&private->data_mutex);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new scarlett2_standalone_ctl = {
|
|
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
+ .name = "",
|
|
+ .info = snd_ctl_boolean_mono_info,
|
|
+ .get = scarlett2_standalone_ctl_get,
|
|
+ .put = scarlett2_standalone_ctl_put,
|
|
+};
|
|
+
|
|
+static int scarlett2_add_standalone_ctl(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ if (private->info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
+ return 0;
|
|
+
|
|
+ /* Add standalone control */
|
|
+ return scarlett2_add_new_ctl(mixer, &scarlett2_standalone_ctl,
|
|
+ 0, 1, "Standalone Switch", NULL);
|
|
+}
|
|
+
|
|
+/*** Cleanup/Suspend Callbacks ***/
|
|
+
|
|
+static void scarlett2_private_free(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ cancel_delayed_work_sync(&private->work);
|
|
+ kfree(private);
|
|
+ mixer->private_data = NULL;
|
|
+}
|
|
+
|
|
+static void scarlett2_private_suspend(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ if (cancel_delayed_work_sync(&private->work))
|
|
+ scarlett2_config_save(private->mixer);
|
|
+}
|
|
+
|
|
+/*** Initialisation ***/
|
|
+
|
|
+static void scarlett2_count_mux_io(struct scarlett2_data *private)
|
|
+{
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int port_type, srcs = 0, dsts = 0;
|
|
+
|
|
+ for (port_type = 0;
|
|
+ port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
+ port_type++) {
|
|
+ srcs += port_count[port_type][SCARLETT2_PORT_IN];
|
|
+ dsts += port_count[port_type][SCARLETT2_PORT_OUT];
|
|
+ }
|
|
+
|
|
+ private->num_mux_srcs = srcs;
|
|
+ private->num_mux_dsts = dsts;
|
|
+}
|
|
+
|
|
+/* Look through the interface descriptors for the Focusrite Control
|
|
+ * interface (bInterfaceClass = 255 Vendor Specific Class) and set
|
|
+ * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval
|
|
+ * in private
|
|
+ */
|
|
+static int scarlett2_find_fc_interface(struct usb_device *dev,
|
|
+ struct scarlett2_data *private)
|
|
+{
|
|
+ struct usb_host_config *config = dev->actconfig;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < config->desc.bNumInterfaces; i++) {
|
|
+ struct usb_interface *intf = config->interface[i];
|
|
+ struct usb_interface_descriptor *desc =
|
|
+ &intf->altsetting[0].desc;
|
|
+ struct usb_endpoint_descriptor *epd;
|
|
+
|
|
+ if (desc->bInterfaceClass != 255)
|
|
+ continue;
|
|
+
|
|
+ epd = get_endpoint(intf->altsetting, 0);
|
|
+ private->bInterfaceNumber = desc->bInterfaceNumber;
|
|
+ private->bEndpointAddress = epd->bEndpointAddress &
|
|
+ USB_ENDPOINT_NUMBER_MASK;
|
|
+ private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize);
|
|
+ private->bInterval = epd->bInterval;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+/* Initialise private data */
|
|
+static int scarlett2_init_private(struct usb_mixer_interface *mixer,
|
|
+ const struct scarlett2_device_entry *entry)
|
|
+{
|
|
+ struct scarlett2_data *private =
|
|
+ kzalloc(sizeof(struct scarlett2_data), GFP_KERNEL);
|
|
+
|
|
+ if (!private)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ mutex_init(&private->usb_mutex);
|
|
+ mutex_init(&private->data_mutex);
|
|
+ INIT_DELAYED_WORK(&private->work, scarlett2_config_save_work);
|
|
+
|
|
+ mixer->private_data = private;
|
|
+ mixer->private_free = scarlett2_private_free;
|
|
+ mixer->private_suspend = scarlett2_private_suspend;
|
|
+
|
|
+ private->info = entry->info;
|
|
+ private->series_name = entry->series_name;
|
|
+ scarlett2_count_mux_io(private);
|
|
+ private->scarlett2_seq = 0;
|
|
+ private->mixer = mixer;
|
|
+
|
|
+ return scarlett2_find_fc_interface(mixer->chip->dev, private);
|
|
+}
|
|
+
|
|
+/* Cargo cult proprietary initialisation sequence */
|
|
+static int scarlett2_usb_init(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct usb_device *dev = mixer->chip->dev;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ u8 buf[24];
|
|
+ int err;
|
|
+
|
|
+ if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0)))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* step 0 */
|
|
+ err = scarlett2_usb_rx(dev, private->bInterfaceNumber,
|
|
+ SCARLETT2_USB_CMD_INIT, buf, sizeof(buf));
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* step 1 */
|
|
+ private->scarlett2_seq = 1;
|
|
+ err = scarlett2_usb(mixer, SCARLETT2_USB_INIT_1, NULL, 0, NULL, 0);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* step 2 */
|
|
+ private->scarlett2_seq = 1;
|
|
+ return scarlett2_usb(mixer, SCARLETT2_USB_INIT_2, NULL, 0, NULL, 84);
|
|
+}
|
|
+
|
|
+/* Read configuration from the interface on start */
|
|
+static int scarlett2_read_configs(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int num_line_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+ int num_mixer_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
+ struct scarlett2_usb_volume_status volume_status;
|
|
+ int err, i;
|
|
+
|
|
+ if (info->has_msd_mode) {
|
|
+ err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_MSD_SWITCH,
|
|
+ 1, &private->msd_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* no other controls are created if MSD mode is on */
|
|
+ if (private->msd_switch)
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ err = scarlett2_update_input_other(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = scarlett2_update_monitor_other(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* the rest of the configuration is for devices with a mixer */
|
|
+ if (info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
+ return 0;
|
|
+
|
|
+ err = scarlett2_usb_get_config(
|
|
+ mixer, SCARLETT2_CONFIG_STANDALONE_SWITCH,
|
|
+ 1, &private->standalone_switch);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = scarlett2_update_sync(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ err = scarlett2_usb_get_volume_status(mixer, &volume_status);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ if (info->line_out_hw_vol)
|
|
+ for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++)
|
|
+ private->dim_mute[i] = !!volume_status.dim_mute[i];
|
|
+
|
|
+ private->master_vol = clamp(
|
|
+ volume_status.master_vol + SCARLETT2_VOLUME_BIAS,
|
|
+ 0, SCARLETT2_VOLUME_BIAS);
|
|
+
|
|
+ for (i = 0; i < num_line_out; i++) {
|
|
+ int volume, mute;
|
|
+
|
|
+ private->vol_sw_hw_switch[i] =
|
|
+ info->line_out_hw_vol
|
|
+ && volume_status.sw_hw_switch[i];
|
|
+
|
|
+ volume = private->vol_sw_hw_switch[i]
|
|
+ ? volume_status.master_vol
|
|
+ : volume_status.sw_vol[i];
|
|
+ volume = clamp(volume + SCARLETT2_VOLUME_BIAS,
|
|
+ 0, SCARLETT2_VOLUME_BIAS);
|
|
+ private->vol[i] = volume;
|
|
+
|
|
+ mute = private->vol_sw_hw_switch[i]
|
|
+ ? private->dim_mute[SCARLETT2_BUTTON_MUTE]
|
|
+ : volume_status.mute_switch[i];
|
|
+ private->mute_switch[i] = mute;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < num_mixer_out; i++) {
|
|
+ err = scarlett2_usb_get_mix(mixer, i);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ return scarlett2_usb_get_mux(mixer);
|
|
+}
|
|
+
|
|
+/* Notify on sync change */
|
|
+static void scarlett2_notify_sync(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+
|
|
+ private->sync_updated = 1;
|
|
+
|
|
+ snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->sync_ctl->id);
|
|
+}
|
|
+
|
|
+/* Notify on monitor change */
|
|
+static void scarlett2_notify_monitor(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int num_line_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+ int i;
|
|
+
|
|
+ /* if line_out_hw_vol is 0, there are no controls to update */
|
|
+ if (!info->line_out_hw_vol)
|
|
+ return;
|
|
+
|
|
+ private->vol_updated = 1;
|
|
+
|
|
+ snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->master_vol_ctl->id);
|
|
+
|
|
+ for (i = 0; i < num_line_out; i++)
|
|
+ if (private->vol_sw_hw_switch[line_out_remap(private, i)])
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->vol_ctls[i]->id);
|
|
+}
|
|
+
|
|
+/* Notify on dim/mute change */
|
|
+static void scarlett2_notify_dim_mute(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
+ int num_line_out =
|
|
+ port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
+ int i;
|
|
+
|
|
+ private->vol_updated = 1;
|
|
+
|
|
+ if (!info->line_out_hw_vol)
|
|
+ return;
|
|
+
|
|
+ for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->dim_mute_ctls[i]->id);
|
|
+
|
|
+ for (i = 0; i < num_line_out; i++)
|
|
+ if (private->vol_sw_hw_switch[line_out_remap(private, i)])
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->mute_ctls[i]->id);
|
|
+}
|
|
+
|
|
+/* Notify on "input other" change (level/pad/air) */
|
|
+static void scarlett2_notify_input_other(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+ int i;
|
|
+
|
|
+ private->input_other_updated = 1;
|
|
+
|
|
+ for (i = 0; i < info->level_input_count; i++)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->level_ctls[i]->id);
|
|
+ for (i = 0; i < info->pad_input_count; i++)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->pad_ctls[i]->id);
|
|
+ for (i = 0; i < info->air_input_count; i++)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->air_ctls[i]->id);
|
|
+ for (i = 0; i < info->phantom_count; i++)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->phantom_ctls[i]->id);
|
|
+}
|
|
+
|
|
+/* Notify on "monitor other" change (direct monitor, speaker
|
|
+ * switching, talkback)
|
|
+ */
|
|
+static void scarlett2_notify_monitor_other(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_card *card = mixer->chip->card;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ const struct scarlett2_device_info *info = private->info;
|
|
+
|
|
+ private->monitor_other_updated = 1;
|
|
+
|
|
+ if (info->direct_monitor) {
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->direct_monitor_ctl->id);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (info->has_speaker_switching)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->speaker_switching_ctl->id);
|
|
+
|
|
+ if (info->has_talkback)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->talkback_ctl->id);
|
|
+
|
|
+ /* if speaker switching was recently enabled or disabled,
|
|
+ * invalidate the dim/mute and mux enum controls
|
|
+ */
|
|
+ if (private->speaker_switching_switched) {
|
|
+ int i;
|
|
+
|
|
+ scarlett2_notify_dim_mute(mixer);
|
|
+
|
|
+ private->speaker_switching_switched = 0;
|
|
+ private->mux_updated = 1;
|
|
+
|
|
+ for (i = 0; i < private->num_mux_dsts; i++)
|
|
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
+ &private->mux_ctls[i]->id);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Interrupt callback */
|
|
+static void scarlett2_notify(struct urb *urb)
|
|
+{
|
|
+ struct usb_mixer_interface *mixer = urb->context;
|
|
+ int len = urb->actual_length;
|
|
+ int ustatus = urb->status;
|
|
+ u32 data;
|
|
+
|
|
+ if (ustatus != 0 || len != 8)
|
|
+ goto requeue;
|
|
+
|
|
+ data = le32_to_cpu(*(__le32 *)urb->transfer_buffer);
|
|
+ if (data & SCARLETT2_USB_NOTIFY_SYNC)
|
|
+ scarlett2_notify_sync(mixer);
|
|
+ if (data & SCARLETT2_USB_NOTIFY_MONITOR)
|
|
+ scarlett2_notify_monitor(mixer);
|
|
+ if (data & SCARLETT2_USB_NOTIFY_DIM_MUTE)
|
|
+ scarlett2_notify_dim_mute(mixer);
|
|
+ if (data & SCARLETT2_USB_NOTIFY_INPUT_OTHER)
|
|
+ scarlett2_notify_input_other(mixer);
|
|
+ if (data & SCARLETT2_USB_NOTIFY_MONITOR_OTHER)
|
|
+ scarlett2_notify_monitor_other(mixer);
|
|
+
|
|
+requeue:
|
|
+ if (ustatus != -ENOENT &&
|
|
+ ustatus != -ECONNRESET &&
|
|
+ ustatus != -ESHUTDOWN) {
|
|
+ urb->dev = mixer->chip->dev;
|
|
+ usb_submit_urb(urb, GFP_ATOMIC);
|
|
+ }
|
|
+}
|
|
+
|
|
+static int scarlett2_init_notify(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct usb_device *dev = mixer->chip->dev;
|
|
+ struct scarlett2_data *private = mixer->private_data;
|
|
+ unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress);
|
|
+ void *transfer_buffer;
|
|
+
|
|
+ if (mixer->urb) {
|
|
+ usb_audio_err(mixer->chip,
|
|
+ "%s: mixer urb already in use!\n", __func__);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (usb_pipe_type_check(dev, pipe))
|
|
+ return -EINVAL;
|
|
+
|
|
+ mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!mixer->urb)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL);
|
|
+ if (!transfer_buffer)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ usb_fill_int_urb(mixer->urb, dev, pipe,
|
|
+ transfer_buffer, private->wMaxPacketSize,
|
|
+ scarlett2_notify, mixer, private->bInterval);
|
|
+
|
|
+ return usb_submit_urb(mixer->urb, GFP_KERNEL);
|
|
+}
|
|
+
|
|
+static const struct scarlett2_device_entry *get_scarlett2_device_entry(
|
|
+ struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ const struct scarlett2_device_entry *entry = scarlett2_devices;
|
|
+
|
|
+ /* Find entry in scarlett2_devices */
|
|
+ while (entry->usb_id && entry->usb_id != mixer->chip->usb_id)
|
|
+ entry++;
|
|
+ if (!entry->usb_id)
|
|
+ return NULL;
|
|
+
|
|
+ return entry;
|
|
+}
|
|
+
|
|
+static int snd_scarlett2_controls_create(
|
|
+ struct usb_mixer_interface *mixer,
|
|
+ const struct scarlett2_device_entry *entry)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ /* Initialise private data */
|
|
+ err = scarlett2_init_private(mixer, entry);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Send proprietary USB initialisation sequence */
|
|
+ err = scarlett2_usb_init(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Read volume levels and controls from the interface */
|
|
+ err = scarlett2_read_configs(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the MSD control */
|
|
+ err = scarlett2_add_msd_ctl(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* If MSD mode is enabled, don't create any other controls */
|
|
+ if (((struct scarlett2_data *)mixer->private_data)->msd_switch)
|
|
+ return 0;
|
|
+
|
|
+ /* Create the analogue output controls */
|
|
+ err = scarlett2_add_line_out_ctls(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the analogue input controls */
|
|
+ err = scarlett2_add_line_in_ctls(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the input, output, and mixer mux input selections */
|
|
+ err = scarlett2_add_mux_enums(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the matrix mixer controls */
|
|
+ err = scarlett2_add_mixer_ctls(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the level meter controls */
|
|
+ err = scarlett2_add_meter_ctl(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the sync control */
|
|
+ err = scarlett2_add_sync_ctl(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the direct monitor control */
|
|
+ err = scarlett2_add_direct_monitor_ctl(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the speaker switching control */
|
|
+ err = scarlett2_add_speaker_switch_ctl(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the talkback controls */
|
|
+ err = scarlett2_add_talkback_ctls(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Create the standalone control */
|
|
+ err = scarlett2_add_standalone_ctl(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ /* Set up the interrupt polling */
|
|
+ err = scarlett2_init_notify(mixer);
|
|
+ if (err < 0)
|
|
+ return err;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int snd_scarlett2_init(struct usb_mixer_interface *mixer)
|
|
+{
|
|
+ struct snd_usb_audio *chip = mixer->chip;
|
|
+ const struct scarlett2_device_entry *entry;
|
|
+ int err;
|
|
+
|
|
+ /* only use UAC_VERSION_2 */
|
|
+ if (!mixer->protocol)
|
|
+ return 0;
|
|
+
|
|
+ /* find entry in scarlett2_devices */
|
|
+ entry = get_scarlett2_device_entry(mixer);
|
|
+ if (!entry) {
|
|
+ usb_audio_err(mixer->chip,
|
|
+ "%s: missing device entry for %04x:%04x\n",
|
|
+ __func__,
|
|
+ USB_ID_VENDOR(chip->usb_id),
|
|
+ USB_ID_PRODUCT(chip->usb_id));
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (chip->setup & SCARLETT2_DISABLE) {
|
|
+ usb_audio_info(chip,
|
|
+ "Focusrite %s Mixer Driver disabled "
|
|
+ "by modprobe options (snd_usb_audio "
|
|
+ "vid=0x%04x pid=0x%04x device_setup=%d)\n",
|
|
+ entry->series_name,
|
|
+ USB_ID_VENDOR(chip->usb_id),
|
|
+ USB_ID_PRODUCT(chip->usb_id),
|
|
+ SCARLETT2_DISABLE);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ usb_audio_info(chip,
|
|
+ "Focusrite %s Mixer Driver enabled (pid=0x%04x); "
|
|
+ "report any issues to g@b4.vu",
|
|
+ entry->series_name,
|
|
+ USB_ID_PRODUCT(chip->usb_id));
|
|
+
|
|
+ err = snd_scarlett2_controls_create(mixer, entry);
|
|
+ if (err < 0)
|
|
+ usb_audio_err(mixer->chip,
|
|
+ "Error initialising %s Mixer Driver: %d",
|
|
+ entry->series_name,
|
|
+ err);
|
|
+
|
|
+ return err;
|
|
+}
|
|
diff --git a/sound/usb/mixer_scarlett2.h b/sound/usb/mixer_scarlett2.h
|
|
new file mode 100644
|
|
index 0000000000000..d209362cf41a6
|
|
--- /dev/null
|
|
+++ b/sound/usb/mixer_scarlett2.h
|
|
@@ -0,0 +1,7 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0 */
|
|
+#ifndef __USB_MIXER_SCARLETT2_H
|
|
+#define __USB_MIXER_SCARLETT2_H
|
|
+
|
|
+int snd_scarlett2_init(struct usb_mixer_interface *mixer);
|
|
+
|
|
+#endif /* __USB_MIXER_SCARLETT2_H */
|
|
diff --git a/sound/usb/mixer_scarlett_gen2.c b/sound/usb/mixer_scarlett_gen2.c
|
|
deleted file mode 100644
|
|
index c04cff7225411..0000000000000
|
|
--- a/sound/usb/mixer_scarlett_gen2.c
|
|
+++ /dev/null
|
|
@@ -1,4274 +0,0 @@
|
|
-// SPDX-License-Identifier: GPL-2.0
|
|
-/*
|
|
- * Focusrite Scarlett Gen 2/3 and Clarett+ Driver for ALSA
|
|
- *
|
|
- * Supported models:
|
|
- * - 6i6/18i8/18i20 Gen 2
|
|
- * - Solo/2i2/4i4/8i6/18i8/18i20 Gen 3
|
|
- * - Clarett+ 8Pre
|
|
- *
|
|
- * Copyright (c) 2018-2022 by Geoffrey D. Bennett <g at b4.vu>
|
|
- * Copyright (c) 2020-2021 by Vladimir Sadovnikov <sadko4u@gmail.com>
|
|
- * Copyright (c) 2022 by Christian Colglazier <christian@cacolglazier.com>
|
|
- *
|
|
- * Based on the Scarlett (Gen 1) Driver for ALSA:
|
|
- *
|
|
- * Copyright (c) 2013 by Tobias Hoffmann
|
|
- * Copyright (c) 2013 by Robin Gareus <robin at gareus.org>
|
|
- * Copyright (c) 2002 by Takashi Iwai <tiwai at suse.de>
|
|
- * Copyright (c) 2014 by Chris J Arges <chris.j.arges at canonical.com>
|
|
- *
|
|
- * Many codes borrowed from audio.c by
|
|
- * Alan Cox (alan at lxorguk.ukuu.org.uk)
|
|
- * Thomas Sailer (sailer at ife.ee.ethz.ch)
|
|
- *
|
|
- * Code cleanup:
|
|
- * David Henningsson <david.henningsson at canonical.com>
|
|
- */
|
|
-
|
|
-/* The protocol was reverse engineered by looking at the communication
|
|
- * between Focusrite Control 2.3.4 and the Focusrite(R) Scarlett 18i20
|
|
- * (firmware 1083) using usbmon in July-August 2018.
|
|
- *
|
|
- * Scarlett 18i8 support added in April 2019.
|
|
- *
|
|
- * Scarlett 6i6 support added in June 2019 (thanks to Martin Wittmann
|
|
- * for providing usbmon output and testing).
|
|
- *
|
|
- * Scarlett 4i4/8i6 Gen 3 support added in May 2020 (thanks to Laurent
|
|
- * Debricon for donating a 4i4 and to Fredrik Unger for providing 8i6
|
|
- * usbmon output and testing).
|
|
- *
|
|
- * Scarlett 18i8/18i20 Gen 3 support added in June 2020 (thanks to
|
|
- * Darren Jaeckel, Alex Sedlack, and Clovis Lunel for providing usbmon
|
|
- * output, protocol traces and testing).
|
|
- *
|
|
- * Support for loading mixer volume and mux configuration from the
|
|
- * interface during driver initialisation added in May 2021 (thanks to
|
|
- * Vladimir Sadovnikov for figuring out how).
|
|
- *
|
|
- * Support for Solo/2i2 Gen 3 added in May 2021 (thanks to Alexander
|
|
- * Vorona for 2i2 protocol traces).
|
|
- *
|
|
- * Support for phantom power, direct monitoring, speaker switching,
|
|
- * and talkback added in May-June 2021.
|
|
- *
|
|
- * Support for Clarett+ 8Pre added in Aug 2022 by Christian
|
|
- * Colglazier.
|
|
- *
|
|
- * This ALSA mixer gives access to (model-dependent):
|
|
- * - input, output, mixer-matrix muxes
|
|
- * - mixer-matrix gain stages
|
|
- * - gain/volume/mute controls
|
|
- * - level meters
|
|
- * - line/inst level, pad, and air controls
|
|
- * - phantom power, direct monitor, speaker switching, and talkback
|
|
- * controls
|
|
- * - disable/enable MSD mode
|
|
- * - disable/enable standalone mode
|
|
- *
|
|
- * <ditaa>
|
|
- * /--------------\ 18chn 20chn /--------------\
|
|
- * | Hardware in +--+------\ /-------------+--+ ALSA PCM out |
|
|
- * \--------------/ | | | | \--------------/
|
|
- * | | | /-----\ |
|
|
- * | | | | | |
|
|
- * | v v v | |
|
|
- * | +---------------+ | |
|
|
- * | \ Matrix Mux / | |
|
|
- * | +-----+-----+ | |
|
|
- * | | | |
|
|
- * | |18chn | |
|
|
- * | | | |
|
|
- * | | 10chn| |
|
|
- * | v | |
|
|
- * | +------------+ | |
|
|
- * | | Mixer | | |
|
|
- * | | Matrix | | |
|
|
- * | | | | |
|
|
- * | | 18x10 Gain | | |
|
|
- * | | stages | | |
|
|
- * | +-----+------+ | |
|
|
- * | | | |
|
|
- * |18chn |10chn | |20chn
|
|
- * | | | |
|
|
- * | +----------/ |
|
|
- * | | |
|
|
- * v v v
|
|
- * ===========================
|
|
- * +---------------+ +--—------------+
|
|
- * \ Output Mux / \ Capture Mux /
|
|
- * +---+---+---+ +-----+-----+
|
|
- * | | |
|
|
- * 10chn| | |18chn
|
|
- * | | |
|
|
- * /--------------\ | | | /--------------\
|
|
- * | S/PDIF, ADAT |<--/ |10chn \-->| ALSA PCM in |
|
|
- * | Hardware out | | \--------------/
|
|
- * \--------------/ |
|
|
- * v
|
|
- * +-------------+ Software gain per channel.
|
|
- * | Master Gain |<-- 18i20 only: Switch per channel
|
|
- * +------+------+ to select HW or SW gain control.
|
|
- * |
|
|
- * |10chn
|
|
- * /--------------\ |
|
|
- * | Analogue |<------/
|
|
- * | Hardware out |
|
|
- * \--------------/
|
|
- * </ditaa>
|
|
- *
|
|
- * Gen 3 devices have a Mass Storage Device (MSD) mode where a small
|
|
- * disk with registration and driver download information is presented
|
|
- * to the host. To access the full functionality of the device without
|
|
- * proprietary software, MSD mode can be disabled by:
|
|
- * - holding down the 48V button for five seconds while powering on
|
|
- * the device, or
|
|
- * - using this driver and alsamixer to change the "MSD Mode" setting
|
|
- * to Off and power-cycling the device
|
|
- */
|
|
-
|
|
-#include <linux/slab.h>
|
|
-#include <linux/usb.h>
|
|
-#include <linux/moduleparam.h>
|
|
-
|
|
-#include <sound/control.h>
|
|
-#include <sound/tlv.h>
|
|
-
|
|
-#include "usbaudio.h"
|
|
-#include "mixer.h"
|
|
-#include "helper.h"
|
|
-
|
|
-#include "mixer_scarlett_gen2.h"
|
|
-
|
|
-/* device_setup value to enable */
|
|
-#define SCARLETT2_ENABLE 0x01
|
|
-
|
|
-/* device_setup value to allow turning MSD mode back on */
|
|
-#define SCARLETT2_MSD_ENABLE 0x02
|
|
-
|
|
-/* some gui mixers can't handle negative ctl values */
|
|
-#define SCARLETT2_VOLUME_BIAS 127
|
|
-
|
|
-/* mixer range from -80dB to +6dB in 0.5dB steps */
|
|
-#define SCARLETT2_MIXER_MIN_DB -80
|
|
-#define SCARLETT2_MIXER_BIAS (-SCARLETT2_MIXER_MIN_DB * 2)
|
|
-#define SCARLETT2_MIXER_MAX_DB 6
|
|
-#define SCARLETT2_MIXER_MAX_VALUE \
|
|
- ((SCARLETT2_MIXER_MAX_DB - SCARLETT2_MIXER_MIN_DB) * 2)
|
|
-#define SCARLETT2_MIXER_VALUE_COUNT (SCARLETT2_MIXER_MAX_VALUE + 1)
|
|
-
|
|
-/* map from (dB + 80) * 2 to mixer value
|
|
- * for dB in 0 .. 172: int(8192 * pow(10, ((dB - 160) / 2 / 20)))
|
|
- */
|
|
-static const u16 scarlett2_mixer_values[SCARLETT2_MIXER_VALUE_COUNT] = {
|
|
- 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
|
|
- 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8,
|
|
- 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
|
|
- 23, 24, 25, 27, 29, 30, 32, 34, 36, 38, 41, 43, 46, 48, 51,
|
|
- 54, 57, 61, 65, 68, 73, 77, 81, 86, 91, 97, 103, 109, 115,
|
|
- 122, 129, 137, 145, 154, 163, 173, 183, 194, 205, 217, 230,
|
|
- 244, 259, 274, 290, 307, 326, 345, 365, 387, 410, 434, 460,
|
|
- 487, 516, 547, 579, 614, 650, 689, 730, 773, 819, 867, 919,
|
|
- 973, 1031, 1092, 1157, 1225, 1298, 1375, 1456, 1543, 1634,
|
|
- 1731, 1833, 1942, 2057, 2179, 2308, 2445, 2590, 2744, 2906,
|
|
- 3078, 3261, 3454, 3659, 3876, 4105, 4349, 4606, 4879, 5168,
|
|
- 5475, 5799, 6143, 6507, 6892, 7301, 7733, 8192, 8677, 9191,
|
|
- 9736, 10313, 10924, 11571, 12257, 12983, 13752, 14567, 15430,
|
|
- 16345
|
|
-};
|
|
-
|
|
-/* Maximum number of analogue outputs */
|
|
-#define SCARLETT2_ANALOGUE_MAX 10
|
|
-
|
|
-/* Maximum number of level and pad switches */
|
|
-#define SCARLETT2_LEVEL_SWITCH_MAX 2
|
|
-#define SCARLETT2_PAD_SWITCH_MAX 8
|
|
-#define SCARLETT2_AIR_SWITCH_MAX 8
|
|
-#define SCARLETT2_PHANTOM_SWITCH_MAX 2
|
|
-
|
|
-/* Maximum number of inputs to the mixer */
|
|
-#define SCARLETT2_INPUT_MIX_MAX 25
|
|
-
|
|
-/* Maximum number of outputs from the mixer */
|
|
-#define SCARLETT2_OUTPUT_MIX_MAX 12
|
|
-
|
|
-/* Maximum size of the data in the USB mux assignment message:
|
|
- * 20 inputs, 20 outputs, 25 matrix inputs, 12 spare
|
|
- */
|
|
-#define SCARLETT2_MUX_MAX 77
|
|
-
|
|
-/* Maximum number of meters (sum of output port counts) */
|
|
-#define SCARLETT2_MAX_METERS 65
|
|
-
|
|
-/* There are three different sets of configuration parameters across
|
|
- * the devices
|
|
- */
|
|
-enum {
|
|
- SCARLETT2_CONFIG_SET_NO_MIXER = 0,
|
|
- SCARLETT2_CONFIG_SET_GEN_2 = 1,
|
|
- SCARLETT2_CONFIG_SET_GEN_3 = 2,
|
|
- SCARLETT2_CONFIG_SET_CLARETT = 3,
|
|
- SCARLETT2_CONFIG_SET_COUNT = 4
|
|
-};
|
|
-
|
|
-/* Hardware port types:
|
|
- * - None (no input to mux)
|
|
- * - Analogue I/O
|
|
- * - S/PDIF I/O
|
|
- * - ADAT I/O
|
|
- * - Mixer I/O
|
|
- * - PCM I/O
|
|
- */
|
|
-enum {
|
|
- SCARLETT2_PORT_TYPE_NONE = 0,
|
|
- SCARLETT2_PORT_TYPE_ANALOGUE = 1,
|
|
- SCARLETT2_PORT_TYPE_SPDIF = 2,
|
|
- SCARLETT2_PORT_TYPE_ADAT = 3,
|
|
- SCARLETT2_PORT_TYPE_MIX = 4,
|
|
- SCARLETT2_PORT_TYPE_PCM = 5,
|
|
- SCARLETT2_PORT_TYPE_COUNT = 6,
|
|
-};
|
|
-
|
|
-/* I/O count of each port type kept in struct scarlett2_ports */
|
|
-enum {
|
|
- SCARLETT2_PORT_IN = 0,
|
|
- SCARLETT2_PORT_OUT = 1,
|
|
- SCARLETT2_PORT_DIRNS = 2,
|
|
-};
|
|
-
|
|
-/* Dim/Mute buttons on the 18i20 */
|
|
-enum {
|
|
- SCARLETT2_BUTTON_MUTE = 0,
|
|
- SCARLETT2_BUTTON_DIM = 1,
|
|
- SCARLETT2_DIM_MUTE_COUNT = 2,
|
|
-};
|
|
-
|
|
-static const char *const scarlett2_dim_mute_names[SCARLETT2_DIM_MUTE_COUNT] = {
|
|
- "Mute Playback Switch", "Dim Playback Switch"
|
|
-};
|
|
-
|
|
-/* Description of each hardware port type:
|
|
- * - id: hardware ID of this port type
|
|
- * - src_descr: printf format string for mux input selections
|
|
- * - src_num_offset: added to channel number for the fprintf
|
|
- * - dst_descr: printf format string for mixer controls
|
|
- */
|
|
-struct scarlett2_port {
|
|
- u16 id;
|
|
- const char * const src_descr;
|
|
- int src_num_offset;
|
|
- const char * const dst_descr;
|
|
-};
|
|
-
|
|
-static const struct scarlett2_port scarlett2_ports[SCARLETT2_PORT_TYPE_COUNT] = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = {
|
|
- .id = 0x000,
|
|
- .src_descr = "Off"
|
|
- },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = {
|
|
- .id = 0x080,
|
|
- .src_descr = "Analogue %d",
|
|
- .src_num_offset = 1,
|
|
- .dst_descr = "Analogue Output %02d Playback"
|
|
- },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = {
|
|
- .id = 0x180,
|
|
- .src_descr = "S/PDIF %d",
|
|
- .src_num_offset = 1,
|
|
- .dst_descr = "S/PDIF Output %d Playback"
|
|
- },
|
|
- [SCARLETT2_PORT_TYPE_ADAT] = {
|
|
- .id = 0x200,
|
|
- .src_descr = "ADAT %d",
|
|
- .src_num_offset = 1,
|
|
- .dst_descr = "ADAT Output %d Playback"
|
|
- },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = {
|
|
- .id = 0x300,
|
|
- .src_descr = "Mix %c",
|
|
- .src_num_offset = 'A',
|
|
- .dst_descr = "Mixer Input %02d Capture"
|
|
- },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = {
|
|
- .id = 0x600,
|
|
- .src_descr = "PCM %d",
|
|
- .src_num_offset = 1,
|
|
- .dst_descr = "PCM %02d Capture"
|
|
- },
|
|
-};
|
|
-
|
|
-/* Number of mux tables: one for each band of sample rates
|
|
- * (44.1/48kHz, 88.2/96kHz, and 176.4/176kHz)
|
|
- */
|
|
-#define SCARLETT2_MUX_TABLES 3
|
|
-
|
|
-/* Maximum number of entries in a mux table */
|
|
-#define SCARLETT2_MAX_MUX_ENTRIES 10
|
|
-
|
|
-/* One entry within mux_assignment defines the port type and range of
|
|
- * ports to add to the set_mux message. The end of the list is marked
|
|
- * with count == 0.
|
|
- */
|
|
-struct scarlett2_mux_entry {
|
|
- u8 port_type;
|
|
- u8 start;
|
|
- u8 count;
|
|
-};
|
|
-
|
|
-struct scarlett2_device_info {
|
|
- u32 usb_id; /* USB device identifier */
|
|
-
|
|
- /* Gen 3 devices have an internal MSD mode switch that needs
|
|
- * to be disabled in order to access the full functionality of
|
|
- * the device.
|
|
- */
|
|
- u8 has_msd_mode;
|
|
-
|
|
- /* which set of configuration parameters the device uses */
|
|
- u8 config_set;
|
|
-
|
|
- /* line out hw volume is sw controlled */
|
|
- u8 line_out_hw_vol;
|
|
-
|
|
- /* support for main/alt speaker switching */
|
|
- u8 has_speaker_switching;
|
|
-
|
|
- /* support for talkback microphone */
|
|
- u8 has_talkback;
|
|
-
|
|
- /* the number of analogue inputs with a software switchable
|
|
- * level control that can be set to line or instrument
|
|
- */
|
|
- u8 level_input_count;
|
|
-
|
|
- /* the first input with a level control (0-based) */
|
|
- u8 level_input_first;
|
|
-
|
|
- /* the number of analogue inputs with a software switchable
|
|
- * 10dB pad control
|
|
- */
|
|
- u8 pad_input_count;
|
|
-
|
|
- /* the number of analogue inputs with a software switchable
|
|
- * "air" control
|
|
- */
|
|
- u8 air_input_count;
|
|
-
|
|
- /* the number of phantom (48V) software switchable controls */
|
|
- u8 phantom_count;
|
|
-
|
|
- /* the number of inputs each phantom switch controls */
|
|
- u8 inputs_per_phantom;
|
|
-
|
|
- /* the number of direct monitor options
|
|
- * (0 = none, 1 = mono only, 2 = mono/stereo)
|
|
- */
|
|
- u8 direct_monitor;
|
|
-
|
|
- /* remap analogue outputs; 18i8 Gen 3 has "line 3/4" connected
|
|
- * internally to the analogue 7/8 outputs
|
|
- */
|
|
- u8 line_out_remap_enable;
|
|
- u8 line_out_remap[SCARLETT2_ANALOGUE_MAX];
|
|
-
|
|
- /* additional description for the line out volume controls */
|
|
- const char * const line_out_descrs[SCARLETT2_ANALOGUE_MAX];
|
|
-
|
|
- /* number of sources/destinations of each port type */
|
|
- const int port_count[SCARLETT2_PORT_TYPE_COUNT][SCARLETT2_PORT_DIRNS];
|
|
-
|
|
- /* layout/order of the entries in the set_mux message */
|
|
- struct scarlett2_mux_entry mux_assignment[SCARLETT2_MUX_TABLES]
|
|
- [SCARLETT2_MAX_MUX_ENTRIES];
|
|
-};
|
|
-
|
|
-struct scarlett2_data {
|
|
- struct usb_mixer_interface *mixer;
|
|
- struct mutex usb_mutex; /* prevent sending concurrent USB requests */
|
|
- struct mutex data_mutex; /* lock access to this data */
|
|
- struct delayed_work work;
|
|
- const struct scarlett2_device_info *info;
|
|
- __u8 bInterfaceNumber;
|
|
- __u8 bEndpointAddress;
|
|
- __u16 wMaxPacketSize;
|
|
- __u8 bInterval;
|
|
- int num_mux_srcs;
|
|
- int num_mux_dsts;
|
|
- u16 scarlett2_seq;
|
|
- u8 sync_updated;
|
|
- u8 vol_updated;
|
|
- u8 input_other_updated;
|
|
- u8 monitor_other_updated;
|
|
- u8 mux_updated;
|
|
- u8 speaker_switching_switched;
|
|
- u8 sync;
|
|
- u8 master_vol;
|
|
- u8 vol[SCARLETT2_ANALOGUE_MAX];
|
|
- u8 vol_sw_hw_switch[SCARLETT2_ANALOGUE_MAX];
|
|
- u8 mute_switch[SCARLETT2_ANALOGUE_MAX];
|
|
- u8 level_switch[SCARLETT2_LEVEL_SWITCH_MAX];
|
|
- u8 pad_switch[SCARLETT2_PAD_SWITCH_MAX];
|
|
- u8 dim_mute[SCARLETT2_DIM_MUTE_COUNT];
|
|
- u8 air_switch[SCARLETT2_AIR_SWITCH_MAX];
|
|
- u8 phantom_switch[SCARLETT2_PHANTOM_SWITCH_MAX];
|
|
- u8 phantom_persistence;
|
|
- u8 direct_monitor_switch;
|
|
- u8 speaker_switching_switch;
|
|
- u8 talkback_switch;
|
|
- u8 talkback_map[SCARLETT2_OUTPUT_MIX_MAX];
|
|
- u8 msd_switch;
|
|
- u8 standalone_switch;
|
|
- struct snd_kcontrol *sync_ctl;
|
|
- struct snd_kcontrol *master_vol_ctl;
|
|
- struct snd_kcontrol *vol_ctls[SCARLETT2_ANALOGUE_MAX];
|
|
- struct snd_kcontrol *sw_hw_ctls[SCARLETT2_ANALOGUE_MAX];
|
|
- struct snd_kcontrol *mute_ctls[SCARLETT2_ANALOGUE_MAX];
|
|
- struct snd_kcontrol *dim_mute_ctls[SCARLETT2_DIM_MUTE_COUNT];
|
|
- struct snd_kcontrol *level_ctls[SCARLETT2_LEVEL_SWITCH_MAX];
|
|
- struct snd_kcontrol *pad_ctls[SCARLETT2_PAD_SWITCH_MAX];
|
|
- struct snd_kcontrol *air_ctls[SCARLETT2_AIR_SWITCH_MAX];
|
|
- struct snd_kcontrol *phantom_ctls[SCARLETT2_PHANTOM_SWITCH_MAX];
|
|
- struct snd_kcontrol *mux_ctls[SCARLETT2_MUX_MAX];
|
|
- struct snd_kcontrol *direct_monitor_ctl;
|
|
- struct snd_kcontrol *speaker_switching_ctl;
|
|
- struct snd_kcontrol *talkback_ctl;
|
|
- u8 mux[SCARLETT2_MUX_MAX];
|
|
- u8 mix[SCARLETT2_INPUT_MIX_MAX * SCARLETT2_OUTPUT_MIX_MAX];
|
|
-};
|
|
-
|
|
-/*** Model-specific data ***/
|
|
-
|
|
-static const struct scarlett2_device_info s6i6_gen2_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8203),
|
|
-
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_2,
|
|
- .level_input_count = 2,
|
|
- .pad_input_count = 2,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 6, 6 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s18i8_gen2_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8204),
|
|
-
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_2,
|
|
- .level_input_count = 2,
|
|
- .pad_input_count = 4,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Monitor L",
|
|
- "Monitor R",
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 6 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 8, 18 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 4 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s18i20_gen2_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8201),
|
|
-
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_2,
|
|
- .line_out_hw_vol = 1,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Monitor L",
|
|
- "Monitor R",
|
|
- NULL,
|
|
- NULL,
|
|
- NULL,
|
|
- NULL,
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ADAT, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 6 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info solo_gen3_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8211),
|
|
-
|
|
- .has_msd_mode = 1,
|
|
- .config_set = SCARLETT2_CONFIG_SET_NO_MIXER,
|
|
- .level_input_count = 1,
|
|
- .level_input_first = 1,
|
|
- .air_input_count = 1,
|
|
- .phantom_count = 1,
|
|
- .inputs_per_phantom = 1,
|
|
- .direct_monitor = 1,
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s2i2_gen3_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8210),
|
|
-
|
|
- .has_msd_mode = 1,
|
|
- .config_set = SCARLETT2_CONFIG_SET_NO_MIXER,
|
|
- .level_input_count = 2,
|
|
- .air_input_count = 2,
|
|
- .phantom_count = 1,
|
|
- .inputs_per_phantom = 2,
|
|
- .direct_monitor = 2,
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s4i4_gen3_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8212),
|
|
-
|
|
- .has_msd_mode = 1,
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
- .level_input_count = 2,
|
|
- .pad_input_count = 2,
|
|
- .air_input_count = 2,
|
|
- .phantom_count = 1,
|
|
- .inputs_per_phantom = 2,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Monitor L",
|
|
- "Monitor R",
|
|
- "Headphones L",
|
|
- "Headphones R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 4, 4 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 6, 8 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 4, 6 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 6 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 16 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s8i6_gen3_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8213),
|
|
-
|
|
- .has_msd_mode = 1,
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
- .level_input_count = 2,
|
|
- .pad_input_count = 2,
|
|
- .air_input_count = 2,
|
|
- .phantom_count = 1,
|
|
- .inputs_per_phantom = 2,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 6, 4 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 8, 8 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 6, 10 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 18 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s18i8_gen3_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8214),
|
|
-
|
|
- .has_msd_mode = 1,
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
- .line_out_hw_vol = 1,
|
|
- .has_speaker_switching = 1,
|
|
- .level_input_count = 2,
|
|
- .pad_input_count = 4,
|
|
- .air_input_count = 4,
|
|
- .phantom_count = 2,
|
|
- .inputs_per_phantom = 2,
|
|
-
|
|
- .line_out_remap_enable = 1,
|
|
- .line_out_remap = { 0, 1, 6, 7, 2, 3, 4, 5 },
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Monitor L",
|
|
- "Monitor R",
|
|
- "Alt Monitor L",
|
|
- "Alt Monitor R",
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 8 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_ADAT] = { 8, 0 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 10, 20 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 8, 20 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 12, 8 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 10, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 12, 4 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 10, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 6, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 2, 4 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 20 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info s18i20_gen3_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x8215),
|
|
-
|
|
- .has_msd_mode = 1,
|
|
- .config_set = SCARLETT2_CONFIG_SET_GEN_3,
|
|
- .line_out_hw_vol = 1,
|
|
- .has_speaker_switching = 1,
|
|
- .has_talkback = 1,
|
|
- .level_input_count = 2,
|
|
- .pad_input_count = 8,
|
|
- .air_input_count = 8,
|
|
- .phantom_count = 2,
|
|
- .inputs_per_phantom = 4,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Monitor 1 L",
|
|
- "Monitor 1 R",
|
|
- "Monitor 2 L",
|
|
- "Monitor 2 R",
|
|
- NULL,
|
|
- NULL,
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 9, 10 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 12, 25 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 20, 20 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 10, 10 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 25 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 12 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 10, 8 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_PCM, 8, 2 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 25 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 10 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 24 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info clarett_8pre_info = {
|
|
- .usb_id = USB_ID(0x1235, 0x820c),
|
|
-
|
|
- .config_set = SCARLETT2_CONFIG_SET_CLARETT,
|
|
- .line_out_hw_vol = 1,
|
|
- .level_input_count = 2,
|
|
- .air_input_count = 8,
|
|
-
|
|
- .line_out_descrs = {
|
|
- "Monitor L",
|
|
- "Monitor R",
|
|
- NULL,
|
|
- NULL,
|
|
- NULL,
|
|
- NULL,
|
|
- "Headphones 1 L",
|
|
- "Headphones 1 R",
|
|
- "Headphones 2 L",
|
|
- "Headphones 2 R",
|
|
- },
|
|
-
|
|
- .port_count = {
|
|
- [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 },
|
|
- [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 },
|
|
- [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 },
|
|
- [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 },
|
|
- [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 },
|
|
- [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 },
|
|
- },
|
|
-
|
|
- .mux_assignment = { {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ADAT, 0, 8 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 14 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_ADAT, 0, 4 },
|
|
- { SCARLETT2_PORT_TYPE_MIX, 0, 18 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 8 },
|
|
- { 0, 0, 0 },
|
|
- }, {
|
|
- { SCARLETT2_PORT_TYPE_PCM, 0, 12 },
|
|
- { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 },
|
|
- { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 },
|
|
- { SCARLETT2_PORT_TYPE_NONE, 0, 22 },
|
|
- { 0, 0, 0 },
|
|
- } },
|
|
-};
|
|
-
|
|
-static const struct scarlett2_device_info *scarlett2_devices[] = {
|
|
- /* Supported Gen 2 devices */
|
|
- &s6i6_gen2_info,
|
|
- &s18i8_gen2_info,
|
|
- &s18i20_gen2_info,
|
|
-
|
|
- /* Supported Gen 3 devices */
|
|
- &solo_gen3_info,
|
|
- &s2i2_gen3_info,
|
|
- &s4i4_gen3_info,
|
|
- &s8i6_gen3_info,
|
|
- &s18i8_gen3_info,
|
|
- &s18i20_gen3_info,
|
|
-
|
|
- /* Supported Clarett+ devices */
|
|
- &clarett_8pre_info,
|
|
-
|
|
- /* End of list */
|
|
- NULL
|
|
-};
|
|
-
|
|
-/* get the starting port index number for a given port type/direction */
|
|
-static int scarlett2_get_port_start_num(
|
|
- const int port_count[][SCARLETT2_PORT_DIRNS],
|
|
- int direction, int port_type)
|
|
-{
|
|
- int i, num = 0;
|
|
-
|
|
- for (i = 0; i < port_type; i++)
|
|
- num += port_count[i][direction];
|
|
-
|
|
- return num;
|
|
-}
|
|
-
|
|
-/*** USB Interactions ***/
|
|
-
|
|
-/* Notifications from the interface */
|
|
-#define SCARLETT2_USB_NOTIFY_SYNC 0x00000008
|
|
-#define SCARLETT2_USB_NOTIFY_DIM_MUTE 0x00200000
|
|
-#define SCARLETT2_USB_NOTIFY_MONITOR 0x00400000
|
|
-#define SCARLETT2_USB_NOTIFY_INPUT_OTHER 0x00800000
|
|
-#define SCARLETT2_USB_NOTIFY_MONITOR_OTHER 0x01000000
|
|
-
|
|
-/* Commands for sending/receiving requests/responses */
|
|
-#define SCARLETT2_USB_CMD_INIT 0
|
|
-#define SCARLETT2_USB_CMD_REQ 2
|
|
-#define SCARLETT2_USB_CMD_RESP 3
|
|
-
|
|
-#define SCARLETT2_USB_INIT_1 0x00000000
|
|
-#define SCARLETT2_USB_INIT_2 0x00000002
|
|
-#define SCARLETT2_USB_GET_METER 0x00001001
|
|
-#define SCARLETT2_USB_GET_MIX 0x00002001
|
|
-#define SCARLETT2_USB_SET_MIX 0x00002002
|
|
-#define SCARLETT2_USB_GET_MUX 0x00003001
|
|
-#define SCARLETT2_USB_SET_MUX 0x00003002
|
|
-#define SCARLETT2_USB_GET_SYNC 0x00006004
|
|
-#define SCARLETT2_USB_GET_DATA 0x00800000
|
|
-#define SCARLETT2_USB_SET_DATA 0x00800001
|
|
-#define SCARLETT2_USB_DATA_CMD 0x00800002
|
|
-
|
|
-#define SCARLETT2_USB_CONFIG_SAVE 6
|
|
-
|
|
-#define SCARLETT2_USB_VOLUME_STATUS_OFFSET 0x31
|
|
-#define SCARLETT2_USB_METER_LEVELS_GET_MAGIC 1
|
|
-
|
|
-/* volume status is read together (matches scarlett2_config_items[1]) */
|
|
-struct scarlett2_usb_volume_status {
|
|
- /* dim/mute buttons */
|
|
- u8 dim_mute[SCARLETT2_DIM_MUTE_COUNT];
|
|
-
|
|
- u8 pad1;
|
|
-
|
|
- /* software volume setting */
|
|
- s16 sw_vol[SCARLETT2_ANALOGUE_MAX];
|
|
-
|
|
- /* actual volume of output inc. dim (-18dB) */
|
|
- s16 hw_vol[SCARLETT2_ANALOGUE_MAX];
|
|
-
|
|
- /* internal mute buttons */
|
|
- u8 mute_switch[SCARLETT2_ANALOGUE_MAX];
|
|
-
|
|
- /* sw (0) or hw (1) controlled */
|
|
- u8 sw_hw_switch[SCARLETT2_ANALOGUE_MAX];
|
|
-
|
|
- u8 pad3[6];
|
|
-
|
|
- /* front panel volume knob */
|
|
- s16 master_vol;
|
|
-} __packed;
|
|
-
|
|
-/* Configuration parameters that can be read and written */
|
|
-enum {
|
|
- SCARLETT2_CONFIG_DIM_MUTE = 0,
|
|
- SCARLETT2_CONFIG_LINE_OUT_VOLUME = 1,
|
|
- SCARLETT2_CONFIG_MUTE_SWITCH = 2,
|
|
- SCARLETT2_CONFIG_SW_HW_SWITCH = 3,
|
|
- SCARLETT2_CONFIG_LEVEL_SWITCH = 4,
|
|
- SCARLETT2_CONFIG_PAD_SWITCH = 5,
|
|
- SCARLETT2_CONFIG_MSD_SWITCH = 6,
|
|
- SCARLETT2_CONFIG_AIR_SWITCH = 7,
|
|
- SCARLETT2_CONFIG_STANDALONE_SWITCH = 8,
|
|
- SCARLETT2_CONFIG_PHANTOM_SWITCH = 9,
|
|
- SCARLETT2_CONFIG_PHANTOM_PERSISTENCE = 10,
|
|
- SCARLETT2_CONFIG_DIRECT_MONITOR = 11,
|
|
- SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH = 12,
|
|
- SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE = 13,
|
|
- SCARLETT2_CONFIG_TALKBACK_MAP = 14,
|
|
- SCARLETT2_CONFIG_COUNT = 15
|
|
-};
|
|
-
|
|
-/* Location, size, and activation command number for the configuration
|
|
- * parameters. Size is in bits and may be 1, 8, or 16.
|
|
- */
|
|
-struct scarlett2_config {
|
|
- u8 offset;
|
|
- u8 size;
|
|
- u8 activate;
|
|
-};
|
|
-
|
|
-static const struct scarlett2_config
|
|
- scarlett2_config_items[SCARLETT2_CONFIG_SET_COUNT]
|
|
- [SCARLETT2_CONFIG_COUNT] =
|
|
-
|
|
-/* Devices without a mixer (Gen 3 Solo and 2i2) */
|
|
-{ {
|
|
- [SCARLETT2_CONFIG_MSD_SWITCH] = {
|
|
- .offset = 0x04, .size = 8, .activate = 6 },
|
|
-
|
|
- [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = {
|
|
- .offset = 0x05, .size = 8, .activate = 6 },
|
|
-
|
|
- [SCARLETT2_CONFIG_PHANTOM_SWITCH] = {
|
|
- .offset = 0x06, .size = 8, .activate = 3 },
|
|
-
|
|
- [SCARLETT2_CONFIG_DIRECT_MONITOR] = {
|
|
- .offset = 0x07, .size = 8, .activate = 4 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
- .offset = 0x08, .size = 1, .activate = 7 },
|
|
-
|
|
- [SCARLETT2_CONFIG_AIR_SWITCH] = {
|
|
- .offset = 0x09, .size = 1, .activate = 8 },
|
|
-
|
|
-/* Gen 2 devices: 6i6, 18i8, 18i20 */
|
|
-}, {
|
|
- [SCARLETT2_CONFIG_DIM_MUTE] = {
|
|
- .offset = 0x31, .size = 8, .activate = 2 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = {
|
|
- .offset = 0x34, .size = 16, .activate = 1 },
|
|
-
|
|
- [SCARLETT2_CONFIG_MUTE_SWITCH] = {
|
|
- .offset = 0x5c, .size = 8, .activate = 1 },
|
|
-
|
|
- [SCARLETT2_CONFIG_SW_HW_SWITCH] = {
|
|
- .offset = 0x66, .size = 8, .activate = 3 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
- .offset = 0x7c, .size = 8, .activate = 7 },
|
|
-
|
|
- [SCARLETT2_CONFIG_PAD_SWITCH] = {
|
|
- .offset = 0x84, .size = 8, .activate = 8 },
|
|
-
|
|
- [SCARLETT2_CONFIG_STANDALONE_SWITCH] = {
|
|
- .offset = 0x8d, .size = 8, .activate = 6 },
|
|
-
|
|
-/* Gen 3 devices: 4i4, 8i6, 18i8, 18i20 */
|
|
-}, {
|
|
- [SCARLETT2_CONFIG_DIM_MUTE] = {
|
|
- .offset = 0x31, .size = 8, .activate = 2 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = {
|
|
- .offset = 0x34, .size = 16, .activate = 1 },
|
|
-
|
|
- [SCARLETT2_CONFIG_MUTE_SWITCH] = {
|
|
- .offset = 0x5c, .size = 8, .activate = 1 },
|
|
-
|
|
- [SCARLETT2_CONFIG_SW_HW_SWITCH] = {
|
|
- .offset = 0x66, .size = 8, .activate = 3 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
- .offset = 0x7c, .size = 8, .activate = 7 },
|
|
-
|
|
- [SCARLETT2_CONFIG_PAD_SWITCH] = {
|
|
- .offset = 0x84, .size = 8, .activate = 8 },
|
|
-
|
|
- [SCARLETT2_CONFIG_AIR_SWITCH] = {
|
|
- .offset = 0x8c, .size = 8, .activate = 8 },
|
|
-
|
|
- [SCARLETT2_CONFIG_STANDALONE_SWITCH] = {
|
|
- .offset = 0x95, .size = 8, .activate = 6 },
|
|
-
|
|
- [SCARLETT2_CONFIG_PHANTOM_SWITCH] = {
|
|
- .offset = 0x9c, .size = 1, .activate = 8 },
|
|
-
|
|
- [SCARLETT2_CONFIG_MSD_SWITCH] = {
|
|
- .offset = 0x9d, .size = 8, .activate = 6 },
|
|
-
|
|
- [SCARLETT2_CONFIG_PHANTOM_PERSISTENCE] = {
|
|
- .offset = 0x9e, .size = 8, .activate = 6 },
|
|
-
|
|
- [SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH] = {
|
|
- .offset = 0x9f, .size = 1, .activate = 10 },
|
|
-
|
|
- [SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE] = {
|
|
- .offset = 0xa0, .size = 1, .activate = 10 },
|
|
-
|
|
- [SCARLETT2_CONFIG_TALKBACK_MAP] = {
|
|
- .offset = 0xb0, .size = 16, .activate = 10 },
|
|
-
|
|
-/* Clarett+ 8Pre */
|
|
-}, {
|
|
- [SCARLETT2_CONFIG_DIM_MUTE] = {
|
|
- .offset = 0x31, .size = 8, .activate = 2 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = {
|
|
- .offset = 0x34, .size = 16, .activate = 1 },
|
|
-
|
|
- [SCARLETT2_CONFIG_MUTE_SWITCH] = {
|
|
- .offset = 0x5c, .size = 8, .activate = 1 },
|
|
-
|
|
- [SCARLETT2_CONFIG_SW_HW_SWITCH] = {
|
|
- .offset = 0x66, .size = 8, .activate = 3 },
|
|
-
|
|
- [SCARLETT2_CONFIG_LEVEL_SWITCH] = {
|
|
- .offset = 0x7c, .size = 8, .activate = 7 },
|
|
-
|
|
- [SCARLETT2_CONFIG_AIR_SWITCH] = {
|
|
- .offset = 0x95, .size = 8, .activate = 8 },
|
|
-
|
|
- [SCARLETT2_CONFIG_STANDALONE_SWITCH] = {
|
|
- .offset = 0x8d, .size = 8, .activate = 6 },
|
|
-} };
|
|
-
|
|
-/* proprietary request/response format */
|
|
-struct scarlett2_usb_packet {
|
|
- __le32 cmd;
|
|
- __le16 size;
|
|
- __le16 seq;
|
|
- __le32 error;
|
|
- __le32 pad;
|
|
- u8 data[];
|
|
-};
|
|
-
|
|
-static void scarlett2_fill_request_header(struct scarlett2_data *private,
|
|
- struct scarlett2_usb_packet *req,
|
|
- u32 cmd, u16 req_size)
|
|
-{
|
|
- /* sequence must go up by 1 for each request */
|
|
- u16 seq = private->scarlett2_seq++;
|
|
-
|
|
- req->cmd = cpu_to_le32(cmd);
|
|
- req->size = cpu_to_le16(req_size);
|
|
- req->seq = cpu_to_le16(seq);
|
|
- req->error = 0;
|
|
- req->pad = 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_usb_tx(struct usb_device *dev, int interface,
|
|
- void *buf, u16 size)
|
|
-{
|
|
- return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0),
|
|
- SCARLETT2_USB_CMD_REQ,
|
|
- USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
|
|
- 0, interface, buf, size);
|
|
-}
|
|
-
|
|
-static int scarlett2_usb_rx(struct usb_device *dev, int interface,
|
|
- u32 usb_req, void *buf, u16 size)
|
|
-{
|
|
- return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
|
|
- usb_req,
|
|
- USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
|
|
- 0, interface, buf, size);
|
|
-}
|
|
-
|
|
-/* Send a proprietary format request to the Scarlett interface */
|
|
-static int scarlett2_usb(
|
|
- struct usb_mixer_interface *mixer, u32 cmd,
|
|
- void *req_data, u16 req_size, void *resp_data, u16 resp_size)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- struct usb_device *dev = mixer->chip->dev;
|
|
- struct scarlett2_usb_packet *req, *resp = NULL;
|
|
- size_t req_buf_size = struct_size(req, data, req_size);
|
|
- size_t resp_buf_size = struct_size(resp, data, resp_size);
|
|
- int err;
|
|
-
|
|
- req = kmalloc(req_buf_size, GFP_KERNEL);
|
|
- if (!req) {
|
|
- err = -ENOMEM;
|
|
- goto error;
|
|
- }
|
|
-
|
|
- resp = kmalloc(resp_buf_size, GFP_KERNEL);
|
|
- if (!resp) {
|
|
- err = -ENOMEM;
|
|
- goto error;
|
|
- }
|
|
-
|
|
- mutex_lock(&private->usb_mutex);
|
|
-
|
|
- /* build request message and send it */
|
|
-
|
|
- scarlett2_fill_request_header(private, req, cmd, req_size);
|
|
-
|
|
- if (req_size)
|
|
- memcpy(req->data, req_data, req_size);
|
|
-
|
|
- err = scarlett2_usb_tx(dev, private->bInterfaceNumber,
|
|
- req, req_buf_size);
|
|
-
|
|
- if (err != req_buf_size) {
|
|
- usb_audio_err(
|
|
- mixer->chip,
|
|
- "Scarlett Gen 2/3 USB request result cmd %x was %d\n",
|
|
- cmd, err);
|
|
- err = -EINVAL;
|
|
- goto unlock;
|
|
- }
|
|
-
|
|
- /* send a second message to get the response */
|
|
-
|
|
- err = scarlett2_usb_rx(dev, private->bInterfaceNumber,
|
|
- SCARLETT2_USB_CMD_RESP,
|
|
- resp, resp_buf_size);
|
|
-
|
|
- /* validate the response */
|
|
-
|
|
- if (err != resp_buf_size) {
|
|
- usb_audio_err(
|
|
- mixer->chip,
|
|
- "Scarlett Gen 2/3 USB response result cmd %x was %d "
|
|
- "expected %zu\n",
|
|
- cmd, err, resp_buf_size);
|
|
- err = -EINVAL;
|
|
- goto unlock;
|
|
- }
|
|
-
|
|
- /* cmd/seq/size should match except when initialising
|
|
- * seq sent = 1, response = 0
|
|
- */
|
|
- if (resp->cmd != req->cmd ||
|
|
- (resp->seq != req->seq &&
|
|
- (le16_to_cpu(req->seq) != 1 || resp->seq != 0)) ||
|
|
- resp_size != le16_to_cpu(resp->size) ||
|
|
- resp->error ||
|
|
- resp->pad) {
|
|
- usb_audio_err(
|
|
- mixer->chip,
|
|
- "Scarlett Gen 2/3 USB invalid response; "
|
|
- "cmd tx/rx %d/%d seq %d/%d size %d/%d "
|
|
- "error %d pad %d\n",
|
|
- le32_to_cpu(req->cmd), le32_to_cpu(resp->cmd),
|
|
- le16_to_cpu(req->seq), le16_to_cpu(resp->seq),
|
|
- resp_size, le16_to_cpu(resp->size),
|
|
- le32_to_cpu(resp->error),
|
|
- le32_to_cpu(resp->pad));
|
|
- err = -EINVAL;
|
|
- goto unlock;
|
|
- }
|
|
-
|
|
- if (resp_data && resp_size > 0)
|
|
- memcpy(resp_data, resp->data, resp_size);
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->usb_mutex);
|
|
-error:
|
|
- kfree(req);
|
|
- kfree(resp);
|
|
- return err;
|
|
-}
|
|
-
|
|
-/* Send a USB message to get data; result placed in *buf */
|
|
-static int scarlett2_usb_get(
|
|
- struct usb_mixer_interface *mixer,
|
|
- int offset, void *buf, int size)
|
|
-{
|
|
- struct {
|
|
- __le32 offset;
|
|
- __le32 size;
|
|
- } __packed req;
|
|
-
|
|
- req.offset = cpu_to_le32(offset);
|
|
- req.size = cpu_to_le32(size);
|
|
- return scarlett2_usb(mixer, SCARLETT2_USB_GET_DATA,
|
|
- &req, sizeof(req), buf, size);
|
|
-}
|
|
-
|
|
-/* Send a USB message to get configuration parameters; result placed in *buf */
|
|
-static int scarlett2_usb_get_config(
|
|
- struct usb_mixer_interface *mixer,
|
|
- int config_item_num, int count, void *buf)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const struct scarlett2_config *config_item =
|
|
- &scarlett2_config_items[info->config_set][config_item_num];
|
|
- int size, err, i;
|
|
- u8 *buf_8;
|
|
- u8 value;
|
|
-
|
|
- /* For byte-sized parameters, retrieve directly into buf */
|
|
- if (config_item->size >= 8) {
|
|
- size = config_item->size / 8 * count;
|
|
- err = scarlett2_usb_get(mixer, config_item->offset, buf, size);
|
|
- if (err < 0)
|
|
- return err;
|
|
- if (size == 2) {
|
|
- u16 *buf_16 = buf;
|
|
-
|
|
- for (i = 0; i < count; i++, buf_16++)
|
|
- *buf_16 = le16_to_cpu(*(__le16 *)buf_16);
|
|
- }
|
|
- return 0;
|
|
- }
|
|
-
|
|
- /* For bit-sized parameters, retrieve into value */
|
|
- err = scarlett2_usb_get(mixer, config_item->offset, &value, 1);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* then unpack from value into buf[] */
|
|
- buf_8 = buf;
|
|
- for (i = 0; i < 8 && i < count; i++, value >>= 1)
|
|
- *buf_8++ = value & 1;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Send SCARLETT2_USB_DATA_CMD SCARLETT2_USB_CONFIG_SAVE */
|
|
-static void scarlett2_config_save(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- __le32 req = cpu_to_le32(SCARLETT2_USB_CONFIG_SAVE);
|
|
-
|
|
- int err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD,
|
|
- &req, sizeof(u32),
|
|
- NULL, 0);
|
|
- if (err < 0)
|
|
- usb_audio_err(mixer->chip, "config save failed: %d\n", err);
|
|
-}
|
|
-
|
|
-/* Delayed work to save config */
|
|
-static void scarlett2_config_save_work(struct work_struct *work)
|
|
-{
|
|
- struct scarlett2_data *private =
|
|
- container_of(work, struct scarlett2_data, work.work);
|
|
-
|
|
- scarlett2_config_save(private->mixer);
|
|
-}
|
|
-
|
|
-/* Send a USB message to set a SCARLETT2_CONFIG_* parameter */
|
|
-static int scarlett2_usb_set_config(
|
|
- struct usb_mixer_interface *mixer,
|
|
- int config_item_num, int index, int value)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const struct scarlett2_config *config_item =
|
|
- &scarlett2_config_items[info->config_set][config_item_num];
|
|
- struct {
|
|
- __le32 offset;
|
|
- __le32 bytes;
|
|
- __le32 value;
|
|
- } __packed req;
|
|
- __le32 req2;
|
|
- int offset, size;
|
|
- int err;
|
|
-
|
|
- /* Cancel any pending NVRAM save */
|
|
- cancel_delayed_work_sync(&private->work);
|
|
-
|
|
- /* Convert config_item->size in bits to size in bytes and
|
|
- * calculate offset
|
|
- */
|
|
- if (config_item->size >= 8) {
|
|
- size = config_item->size / 8;
|
|
- offset = config_item->offset + index * size;
|
|
-
|
|
- /* If updating a bit, retrieve the old value, set/clear the
|
|
- * bit as needed, and update value
|
|
- */
|
|
- } else {
|
|
- u8 tmp;
|
|
-
|
|
- size = 1;
|
|
- offset = config_item->offset;
|
|
-
|
|
- err = scarlett2_usb_get(mixer, offset, &tmp, 1);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- if (value)
|
|
- tmp |= (1 << index);
|
|
- else
|
|
- tmp &= ~(1 << index);
|
|
-
|
|
- value = tmp;
|
|
- }
|
|
-
|
|
- /* Send the configuration parameter data */
|
|
- req.offset = cpu_to_le32(offset);
|
|
- req.bytes = cpu_to_le32(size);
|
|
- req.value = cpu_to_le32(value);
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_SET_DATA,
|
|
- &req, sizeof(u32) * 2 + size,
|
|
- NULL, 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Activate the change */
|
|
- req2 = cpu_to_le32(config_item->activate);
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_DATA_CMD,
|
|
- &req2, sizeof(req2), NULL, 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Schedule the change to be written to NVRAM */
|
|
- if (config_item->activate != SCARLETT2_USB_CONFIG_SAVE)
|
|
- schedule_delayed_work(&private->work, msecs_to_jiffies(2000));
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Send a USB message to get sync status; result placed in *sync */
|
|
-static int scarlett2_usb_get_sync_status(
|
|
- struct usb_mixer_interface *mixer,
|
|
- u8 *sync)
|
|
-{
|
|
- __le32 data;
|
|
- int err;
|
|
-
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_GET_SYNC,
|
|
- NULL, 0, &data, sizeof(data));
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- *sync = !!data;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Send a USB message to get volume status; result placed in *buf */
|
|
-static int scarlett2_usb_get_volume_status(
|
|
- struct usb_mixer_interface *mixer,
|
|
- struct scarlett2_usb_volume_status *buf)
|
|
-{
|
|
- return scarlett2_usb_get(mixer, SCARLETT2_USB_VOLUME_STATUS_OFFSET,
|
|
- buf, sizeof(*buf));
|
|
-}
|
|
-
|
|
-/* Send a USB message to get the volumes for all inputs of one mix
|
|
- * and put the values into private->mix[]
|
|
- */
|
|
-static int scarlett2_usb_get_mix(struct usb_mixer_interface *mixer,
|
|
- int mix_num)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- int num_mixer_in =
|
|
- info->port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
- int err, i, j, k;
|
|
-
|
|
- struct {
|
|
- __le16 mix_num;
|
|
- __le16 count;
|
|
- } __packed req;
|
|
-
|
|
- __le16 data[SCARLETT2_INPUT_MIX_MAX];
|
|
-
|
|
- req.mix_num = cpu_to_le16(mix_num);
|
|
- req.count = cpu_to_le16(num_mixer_in);
|
|
-
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_GET_MIX,
|
|
- &req, sizeof(req),
|
|
- data, num_mixer_in * sizeof(u16));
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++) {
|
|
- u16 mixer_value = le16_to_cpu(data[i]);
|
|
-
|
|
- for (k = 0; k < SCARLETT2_MIXER_VALUE_COUNT; k++)
|
|
- if (scarlett2_mixer_values[k] >= mixer_value)
|
|
- break;
|
|
- if (k == SCARLETT2_MIXER_VALUE_COUNT)
|
|
- k = SCARLETT2_MIXER_MAX_VALUE;
|
|
- private->mix[j] = k;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Send a USB message to set the volumes for all inputs of one mix
|
|
- * (values obtained from private->mix[])
|
|
- */
|
|
-static int scarlett2_usb_set_mix(struct usb_mixer_interface *mixer,
|
|
- int mix_num)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- struct {
|
|
- __le16 mix_num;
|
|
- __le16 data[SCARLETT2_INPUT_MIX_MAX];
|
|
- } __packed req;
|
|
-
|
|
- int i, j;
|
|
- int num_mixer_in =
|
|
- info->port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
-
|
|
- req.mix_num = cpu_to_le16(mix_num);
|
|
-
|
|
- for (i = 0, j = mix_num * num_mixer_in; i < num_mixer_in; i++, j++)
|
|
- req.data[i] = cpu_to_le16(
|
|
- scarlett2_mixer_values[private->mix[j]]
|
|
- );
|
|
-
|
|
- return scarlett2_usb(mixer, SCARLETT2_USB_SET_MIX,
|
|
- &req, (num_mixer_in + 1) * sizeof(u16),
|
|
- NULL, 0);
|
|
-}
|
|
-
|
|
-/* Convert a port number index (per info->port_count) to a hardware ID */
|
|
-static u32 scarlett2_mux_src_num_to_id(
|
|
- const int port_count[][SCARLETT2_PORT_DIRNS], int num)
|
|
-{
|
|
- int port_type;
|
|
-
|
|
- for (port_type = 0;
|
|
- port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
- port_type++) {
|
|
- if (num < port_count[port_type][SCARLETT2_PORT_IN])
|
|
- return scarlett2_ports[port_type].id | num;
|
|
- num -= port_count[port_type][SCARLETT2_PORT_IN];
|
|
- }
|
|
-
|
|
- /* Oops */
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Convert a hardware ID to a port number index */
|
|
-static u32 scarlett2_mux_id_to_num(
|
|
- const int port_count[][SCARLETT2_PORT_DIRNS], int direction, u32 id)
|
|
-{
|
|
- int port_type;
|
|
- int port_num = 0;
|
|
-
|
|
- for (port_type = 0;
|
|
- port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
- port_type++) {
|
|
- int base = scarlett2_ports[port_type].id;
|
|
- int count = port_count[port_type][direction];
|
|
-
|
|
- if (id >= base && id < base + count)
|
|
- return port_num + id - base;
|
|
- port_num += count;
|
|
- }
|
|
-
|
|
- /* Oops */
|
|
- return -1;
|
|
-}
|
|
-
|
|
-/* Convert one mux entry from the interface and load into private->mux[] */
|
|
-static void scarlett2_usb_populate_mux(struct scarlett2_data *private,
|
|
- u32 mux_entry)
|
|
-{
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
-
|
|
- int dst_idx, src_idx;
|
|
-
|
|
- dst_idx = scarlett2_mux_id_to_num(port_count, SCARLETT2_PORT_OUT,
|
|
- mux_entry & 0xFFF);
|
|
- if (dst_idx < 0)
|
|
- return;
|
|
-
|
|
- if (dst_idx >= private->num_mux_dsts) {
|
|
- usb_audio_err(private->mixer->chip,
|
|
- "BUG: scarlett2_mux_id_to_num(%06x, OUT): %d >= %d",
|
|
- mux_entry, dst_idx, private->num_mux_dsts);
|
|
- return;
|
|
- }
|
|
-
|
|
- src_idx = scarlett2_mux_id_to_num(port_count, SCARLETT2_PORT_IN,
|
|
- mux_entry >> 12);
|
|
- if (src_idx < 0)
|
|
- return;
|
|
-
|
|
- if (src_idx >= private->num_mux_srcs) {
|
|
- usb_audio_err(private->mixer->chip,
|
|
- "BUG: scarlett2_mux_id_to_num(%06x, IN): %d >= %d",
|
|
- mux_entry, src_idx, private->num_mux_srcs);
|
|
- return;
|
|
- }
|
|
-
|
|
- private->mux[dst_idx] = src_idx;
|
|
-}
|
|
-
|
|
-/* Send USB message to get mux inputs and then populate private->mux[] */
|
|
-static int scarlett2_usb_get_mux(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int count = private->num_mux_dsts;
|
|
- int err, i;
|
|
-
|
|
- struct {
|
|
- __le16 num;
|
|
- __le16 count;
|
|
- } __packed req;
|
|
-
|
|
- __le32 data[SCARLETT2_MUX_MAX];
|
|
-
|
|
- private->mux_updated = 0;
|
|
-
|
|
- req.num = 0;
|
|
- req.count = cpu_to_le16(count);
|
|
-
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_GET_MUX,
|
|
- &req, sizeof(req),
|
|
- data, count * sizeof(u32));
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- for (i = 0; i < count; i++)
|
|
- scarlett2_usb_populate_mux(private, le32_to_cpu(data[i]));
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Send USB messages to set mux inputs */
|
|
-static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int table;
|
|
-
|
|
- struct {
|
|
- __le16 pad;
|
|
- __le16 num;
|
|
- __le32 data[SCARLETT2_MUX_MAX];
|
|
- } __packed req;
|
|
-
|
|
- req.pad = 0;
|
|
-
|
|
- /* set mux settings for each rate */
|
|
- for (table = 0; table < SCARLETT2_MUX_TABLES; table++) {
|
|
- const struct scarlett2_mux_entry *entry;
|
|
-
|
|
- /* i counts over the output array */
|
|
- int i = 0, err;
|
|
-
|
|
- req.num = cpu_to_le16(table);
|
|
-
|
|
- /* loop through each entry */
|
|
- for (entry = info->mux_assignment[table];
|
|
- entry->count;
|
|
- entry++) {
|
|
- int j;
|
|
- int port_type = entry->port_type;
|
|
- int port_idx = entry->start;
|
|
- int mux_idx = scarlett2_get_port_start_num(port_count,
|
|
- SCARLETT2_PORT_OUT, port_type) + port_idx;
|
|
- int dst_id = scarlett2_ports[port_type].id + port_idx;
|
|
-
|
|
- /* Empty slots */
|
|
- if (!dst_id) {
|
|
- for (j = 0; j < entry->count; j++)
|
|
- req.data[i++] = 0;
|
|
- continue;
|
|
- }
|
|
-
|
|
- /* Non-empty mux slots use the lower 12 bits
|
|
- * for the destination and next 12 bits for
|
|
- * the source
|
|
- */
|
|
- for (j = 0; j < entry->count; j++) {
|
|
- int src_id = scarlett2_mux_src_num_to_id(
|
|
- port_count, private->mux[mux_idx++]);
|
|
- req.data[i++] = cpu_to_le32(dst_id |
|
|
- src_id << 12);
|
|
- dst_id++;
|
|
- }
|
|
- }
|
|
-
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_SET_MUX,
|
|
- &req, (i + 1) * sizeof(u32),
|
|
- NULL, 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* Send USB message to get meter levels */
|
|
-static int scarlett2_usb_get_meter_levels(struct usb_mixer_interface *mixer,
|
|
- u16 num_meters, u16 *levels)
|
|
-{
|
|
- struct {
|
|
- __le16 pad;
|
|
- __le16 num_meters;
|
|
- __le32 magic;
|
|
- } __packed req;
|
|
- u32 resp[SCARLETT2_MAX_METERS];
|
|
- int i, err;
|
|
-
|
|
- req.pad = 0;
|
|
- req.num_meters = cpu_to_le16(num_meters);
|
|
- req.magic = cpu_to_le32(SCARLETT2_USB_METER_LEVELS_GET_MAGIC);
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_GET_METER,
|
|
- &req, sizeof(req), resp, num_meters * sizeof(u32));
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* copy, convert to u16 */
|
|
- for (i = 0; i < num_meters; i++)
|
|
- levels[i] = resp[i];
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Control Functions ***/
|
|
-
|
|
-/* helper function to create a new control */
|
|
-static int scarlett2_add_new_ctl(struct usb_mixer_interface *mixer,
|
|
- const struct snd_kcontrol_new *ncontrol,
|
|
- int index, int channels, const char *name,
|
|
- struct snd_kcontrol **kctl_return)
|
|
-{
|
|
- struct snd_kcontrol *kctl;
|
|
- struct usb_mixer_elem_info *elem;
|
|
- int err;
|
|
-
|
|
- elem = kzalloc(sizeof(*elem), GFP_KERNEL);
|
|
- if (!elem)
|
|
- return -ENOMEM;
|
|
-
|
|
- /* We set USB_MIXER_BESPOKEN type, so that the core USB mixer code
|
|
- * ignores them for resume and other operations.
|
|
- * Also, the head.id field is set to 0, as we don't use this field.
|
|
- */
|
|
- elem->head.mixer = mixer;
|
|
- elem->control = index;
|
|
- elem->head.id = 0;
|
|
- elem->channels = channels;
|
|
- elem->val_type = USB_MIXER_BESPOKEN;
|
|
-
|
|
- kctl = snd_ctl_new1(ncontrol, elem);
|
|
- if (!kctl) {
|
|
- kfree(elem);
|
|
- return -ENOMEM;
|
|
- }
|
|
- kctl->private_free = snd_usb_mixer_elem_free;
|
|
-
|
|
- strscpy(kctl->id.name, name, sizeof(kctl->id.name));
|
|
-
|
|
- err = snd_usb_mixer_add_control(&elem->head, kctl);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- if (kctl_return)
|
|
- *kctl_return = kctl;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Sync Control ***/
|
|
-
|
|
-/* Update sync control after receiving notification that the status
|
|
- * has changed
|
|
- */
|
|
-static int scarlett2_update_sync(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- private->sync_updated = 0;
|
|
- return scarlett2_usb_get_sync_status(mixer, &private->sync);
|
|
-}
|
|
-
|
|
-static int scarlett2_sync_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- static const char *texts[2] = {
|
|
- "Unlocked", "Locked"
|
|
- };
|
|
- return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
|
-}
|
|
-
|
|
-static int scarlett2_sync_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->sync_updated) {
|
|
- err = scarlett2_update_sync(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.enumerated.item[0] = private->sync;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_sync_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
- .name = "",
|
|
- .info = scarlett2_sync_ctl_info,
|
|
- .get = scarlett2_sync_ctl_get
|
|
-};
|
|
-
|
|
-static int scarlett2_add_sync_ctl(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- /* devices without a mixer also don't support reporting sync status */
|
|
- if (private->info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
- return 0;
|
|
-
|
|
- return scarlett2_add_new_ctl(mixer, &scarlett2_sync_ctl,
|
|
- 0, 1, "Sync Status", &private->sync_ctl);
|
|
-}
|
|
-
|
|
-/*** Analogue Line Out Volume Controls ***/
|
|
-
|
|
-/* Update hardware volume controls after receiving notification that
|
|
- * they have changed
|
|
- */
|
|
-static int scarlett2_update_volumes(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- struct scarlett2_usb_volume_status volume_status;
|
|
- int num_line_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
- int err, i;
|
|
- int mute;
|
|
-
|
|
- private->vol_updated = 0;
|
|
-
|
|
- err = scarlett2_usb_get_volume_status(mixer, &volume_status);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- private->master_vol = clamp(
|
|
- volume_status.master_vol + SCARLETT2_VOLUME_BIAS,
|
|
- 0, SCARLETT2_VOLUME_BIAS);
|
|
-
|
|
- if (info->line_out_hw_vol)
|
|
- for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++)
|
|
- private->dim_mute[i] = !!volume_status.dim_mute[i];
|
|
-
|
|
- mute = private->dim_mute[SCARLETT2_BUTTON_MUTE];
|
|
-
|
|
- for (i = 0; i < num_line_out; i++)
|
|
- if (private->vol_sw_hw_switch[i]) {
|
|
- private->vol[i] = private->master_vol;
|
|
- private->mute_switch[i] = mute;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_volume_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
-
|
|
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
- uinfo->count = elem->channels;
|
|
- uinfo->value.integer.min = 0;
|
|
- uinfo->value.integer.max = SCARLETT2_VOLUME_BIAS;
|
|
- uinfo->value.integer.step = 1;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_master_volume_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->vol_updated) {
|
|
- err = scarlett2_update_volumes(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] = private->master_vol;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int line_out_remap(struct scarlett2_data *private, int index)
|
|
-{
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int line_out_count =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
-
|
|
- if (!info->line_out_remap_enable)
|
|
- return index;
|
|
-
|
|
- if (index >= line_out_count)
|
|
- return index;
|
|
-
|
|
- return info->line_out_remap[index];
|
|
-}
|
|
-
|
|
-static int scarlett2_volume_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->vol_updated) {
|
|
- err = scarlett2_update_volumes(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] = private->vol[index];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_volume_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->vol[index];
|
|
- val = ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->vol[index] = val;
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME,
|
|
- index, val - SCARLETT2_VOLUME_BIAS);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const DECLARE_TLV_DB_MINMAX(
|
|
- db_scale_scarlett2_gain, -SCARLETT2_VOLUME_BIAS * 100, 0
|
|
-);
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_master_volume_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .access = SNDRV_CTL_ELEM_ACCESS_READ |
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
|
- .name = "",
|
|
- .info = scarlett2_volume_ctl_info,
|
|
- .get = scarlett2_master_volume_ctl_get,
|
|
- .private_value = 0, /* max value */
|
|
- .tlv = { .p = db_scale_scarlett2_gain }
|
|
-};
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_line_out_volume_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
|
- .name = "",
|
|
- .info = scarlett2_volume_ctl_info,
|
|
- .get = scarlett2_volume_ctl_get,
|
|
- .put = scarlett2_volume_ctl_put,
|
|
- .private_value = 0, /* max value */
|
|
- .tlv = { .p = db_scale_scarlett2_gain }
|
|
-};
|
|
-
|
|
-/*** Mute Switch Controls ***/
|
|
-
|
|
-static int scarlett2_mute_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->vol_updated) {
|
|
- err = scarlett2_update_volumes(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] = private->mute_switch[index];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_mute_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->mute_switch[index];
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->mute_switch[index] = val;
|
|
-
|
|
- /* Send mute change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MUTE_SWITCH,
|
|
- index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_mute_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_mute_ctl_get,
|
|
- .put = scarlett2_mute_ctl_put,
|
|
-};
|
|
-
|
|
-/*** HW/SW Volume Switch Controls ***/
|
|
-
|
|
-static void scarlett2_sw_hw_ctl_ro(struct scarlett2_data *private, int index)
|
|
-{
|
|
- private->sw_hw_ctls[index]->vd[0].access &=
|
|
- ~SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
-}
|
|
-
|
|
-static void scarlett2_sw_hw_ctl_rw(struct scarlett2_data *private, int index)
|
|
-{
|
|
- private->sw_hw_ctls[index]->vd[0].access |=
|
|
- SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
-}
|
|
-
|
|
-static int scarlett2_sw_hw_enum_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- static const char *const values[2] = {
|
|
- "SW", "HW"
|
|
- };
|
|
-
|
|
- return snd_ctl_enum_info(uinfo, 1, 2, values);
|
|
-}
|
|
-
|
|
-static int scarlett2_sw_hw_enum_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
-
|
|
- ucontrol->value.enumerated.item[0] = private->vol_sw_hw_switch[index];
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static void scarlett2_vol_ctl_set_writable(struct usb_mixer_interface *mixer,
|
|
- int index, int value)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- struct snd_card *card = mixer->chip->card;
|
|
-
|
|
- /* Set/Clear write bits */
|
|
- if (value) {
|
|
- private->vol_ctls[index]->vd[0].access |=
|
|
- SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
- private->mute_ctls[index]->vd[0].access |=
|
|
- SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
- } else {
|
|
- private->vol_ctls[index]->vd[0].access &=
|
|
- ~SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
- private->mute_ctls[index]->vd[0].access &=
|
|
- ~SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
- }
|
|
-
|
|
- /* Notify of write bit and possible value change */
|
|
- snd_ctl_notify(card,
|
|
- SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
|
|
- &private->vol_ctls[index]->id);
|
|
- snd_ctl_notify(card,
|
|
- SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
|
|
- &private->mute_ctls[index]->id);
|
|
-}
|
|
-
|
|
-static int scarlett2_sw_hw_change(struct usb_mixer_interface *mixer,
|
|
- int ctl_index, int val)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, ctl_index);
|
|
- int err;
|
|
-
|
|
- private->vol_sw_hw_switch[index] = val;
|
|
-
|
|
- /* Change access mode to RO (hardware controlled volume)
|
|
- * or RW (software controlled volume)
|
|
- */
|
|
- scarlett2_vol_ctl_set_writable(mixer, ctl_index, !val);
|
|
-
|
|
- /* Reset volume/mute to master volume/mute */
|
|
- private->vol[index] = private->master_vol;
|
|
- private->mute_switch[index] = private->dim_mute[SCARLETT2_BUTTON_MUTE];
|
|
-
|
|
- /* Set SW volume to current HW volume */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_LINE_OUT_VOLUME,
|
|
- index, private->master_vol - SCARLETT2_VOLUME_BIAS);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Set SW mute to current HW mute */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_MUTE_SWITCH,
|
|
- index, private->dim_mute[SCARLETT2_BUTTON_MUTE]);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Send SW/HW switch change to the device */
|
|
- return scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_SW_HW_SWITCH,
|
|
- index, val);
|
|
-}
|
|
-
|
|
-static int scarlett2_sw_hw_enum_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int ctl_index = elem->control;
|
|
- int index = line_out_remap(private, ctl_index);
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->vol_sw_hw_switch[index];
|
|
- val = !!ucontrol->value.enumerated.item[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- err = scarlett2_sw_hw_change(mixer, ctl_index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_sw_hw_enum_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = scarlett2_sw_hw_enum_ctl_info,
|
|
- .get = scarlett2_sw_hw_enum_ctl_get,
|
|
- .put = scarlett2_sw_hw_enum_ctl_put,
|
|
-};
|
|
-
|
|
-/*** Line Level/Instrument Level Switch Controls ***/
|
|
-
|
|
-static int scarlett2_update_input_other(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- private->input_other_updated = 0;
|
|
-
|
|
- if (info->level_input_count) {
|
|
- int err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_LEVEL_SWITCH,
|
|
- info->level_input_count + info->level_input_first,
|
|
- private->level_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- if (info->pad_input_count) {
|
|
- int err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_PAD_SWITCH,
|
|
- info->pad_input_count, private->pad_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- if (info->air_input_count) {
|
|
- int err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_AIR_SWITCH,
|
|
- info->air_input_count, private->air_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- if (info->phantom_count) {
|
|
- int err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_PHANTOM_SWITCH,
|
|
- info->phantom_count, private->phantom_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE,
|
|
- 1, &private->phantom_persistence);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_level_enum_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- static const char *const values[2] = {
|
|
- "Line", "Inst"
|
|
- };
|
|
-
|
|
- return snd_ctl_enum_info(uinfo, 1, 2, values);
|
|
-}
|
|
-
|
|
-static int scarlett2_level_enum_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- int index = elem->control + info->level_input_first;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->input_other_updated) {
|
|
- err = scarlett2_update_input_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.enumerated.item[0] = private->level_switch[index];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_level_enum_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- int index = elem->control + info->level_input_first;
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->level_switch[index];
|
|
- val = !!ucontrol->value.enumerated.item[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->level_switch[index] = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_LEVEL_SWITCH,
|
|
- index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_level_enum_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = scarlett2_level_enum_ctl_info,
|
|
- .get = scarlett2_level_enum_ctl_get,
|
|
- .put = scarlett2_level_enum_ctl_put,
|
|
-};
|
|
-
|
|
-/*** Pad Switch Controls ***/
|
|
-
|
|
-static int scarlett2_pad_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->input_other_updated) {
|
|
- err = scarlett2_update_input_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] =
|
|
- private->pad_switch[elem->control];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_pad_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->pad_switch[index];
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->pad_switch[index] = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PAD_SWITCH,
|
|
- index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_pad_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_pad_ctl_get,
|
|
- .put = scarlett2_pad_ctl_put,
|
|
-};
|
|
-
|
|
-/*** Air Switch Controls ***/
|
|
-
|
|
-static int scarlett2_air_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->input_other_updated) {
|
|
- err = scarlett2_update_input_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] = private->air_switch[elem->control];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_air_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->air_switch[index];
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->air_switch[index] = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_AIR_SWITCH,
|
|
- index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_air_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_air_ctl_get,
|
|
- .put = scarlett2_air_ctl_put,
|
|
-};
|
|
-
|
|
-/*** Phantom Switch Controls ***/
|
|
-
|
|
-static int scarlett2_phantom_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->input_other_updated) {
|
|
- err = scarlett2_update_input_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] =
|
|
- private->phantom_switch[elem->control];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_phantom_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->phantom_switch[index];
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->phantom_switch[index] = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_PHANTOM_SWITCH,
|
|
- index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_phantom_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_phantom_ctl_get,
|
|
- .put = scarlett2_phantom_ctl_put,
|
|
-};
|
|
-
|
|
-/*** Phantom Persistence Control ***/
|
|
-
|
|
-static int scarlett2_phantom_persistence_ctl_get(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
-
|
|
- ucontrol->value.integer.value[0] = private->phantom_persistence;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_phantom_persistence_ctl_put(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->phantom_persistence;
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->phantom_persistence = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_PHANTOM_PERSISTENCE, index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_phantom_persistence_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_phantom_persistence_ctl_get,
|
|
- .put = scarlett2_phantom_persistence_ctl_put,
|
|
-};
|
|
-
|
|
-/*** Direct Monitor Control ***/
|
|
-
|
|
-static int scarlett2_update_monitor_other(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- int err;
|
|
-
|
|
- /* monitor_other_enable[0] enables speaker switching
|
|
- * monitor_other_enable[1] enables talkback
|
|
- */
|
|
- u8 monitor_other_enable[2];
|
|
-
|
|
- /* monitor_other_switch[0] activates the alternate speakers
|
|
- * monitor_other_switch[1] activates talkback
|
|
- */
|
|
- u8 monitor_other_switch[2];
|
|
-
|
|
- private->monitor_other_updated = 0;
|
|
-
|
|
- if (info->direct_monitor)
|
|
- return scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_DIRECT_MONITOR,
|
|
- 1, &private->direct_monitor_switch);
|
|
-
|
|
- /* if it doesn't do speaker switching then it also doesn't do
|
|
- * talkback
|
|
- */
|
|
- if (!info->has_speaker_switching)
|
|
- return 0;
|
|
-
|
|
- err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
|
|
- 2, monitor_other_enable);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
|
|
- 2, monitor_other_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- if (!monitor_other_enable[0])
|
|
- private->speaker_switching_switch = 0;
|
|
- else
|
|
- private->speaker_switching_switch = monitor_other_switch[0] + 1;
|
|
-
|
|
- if (info->has_talkback) {
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] =
|
|
- info->port_count;
|
|
- int num_mixes =
|
|
- port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
- u16 bitmap;
|
|
- int i;
|
|
-
|
|
- if (!monitor_other_enable[1])
|
|
- private->talkback_switch = 0;
|
|
- else
|
|
- private->talkback_switch = monitor_other_switch[1] + 1;
|
|
-
|
|
- err = scarlett2_usb_get_config(mixer,
|
|
- SCARLETT2_CONFIG_TALKBACK_MAP,
|
|
- 1, &bitmap);
|
|
- if (err < 0)
|
|
- return err;
|
|
- for (i = 0; i < num_mixes; i++, bitmap >>= 1)
|
|
- private->talkback_map[i] = bitmap & 1;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_direct_monitor_ctl_get(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->monitor_other_updated) {
|
|
- err = scarlett2_update_monitor_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.enumerated.item[0] = private->direct_monitor_switch;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_direct_monitor_ctl_put(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->direct_monitor_switch;
|
|
- val = min(ucontrol->value.enumerated.item[0], 2U);
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->direct_monitor_switch = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_DIRECT_MONITOR, index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_direct_monitor_stereo_enum_ctl_info(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- static const char *const values[3] = {
|
|
- "Off", "Mono", "Stereo"
|
|
- };
|
|
-
|
|
- return snd_ctl_enum_info(uinfo, 1, 3, values);
|
|
-}
|
|
-
|
|
-/* Direct Monitor for Solo is mono-only and only needs a boolean control
|
|
- * Direct Monitor for 2i2 is selectable between Off/Mono/Stereo
|
|
- */
|
|
-static const struct snd_kcontrol_new scarlett2_direct_monitor_ctl[2] = {
|
|
- {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_direct_monitor_ctl_get,
|
|
- .put = scarlett2_direct_monitor_ctl_put,
|
|
- },
|
|
- {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = scarlett2_direct_monitor_stereo_enum_ctl_info,
|
|
- .get = scarlett2_direct_monitor_ctl_get,
|
|
- .put = scarlett2_direct_monitor_ctl_put,
|
|
- }
|
|
-};
|
|
-
|
|
-static int scarlett2_add_direct_monitor_ctl(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const char *s;
|
|
-
|
|
- if (!info->direct_monitor)
|
|
- return 0;
|
|
-
|
|
- s = info->direct_monitor == 1
|
|
- ? "Direct Monitor Playback Switch"
|
|
- : "Direct Monitor Playback Enum";
|
|
-
|
|
- return scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_direct_monitor_ctl[info->direct_monitor - 1],
|
|
- 0, 1, s, &private->direct_monitor_ctl);
|
|
-}
|
|
-
|
|
-/*** Speaker Switching Control ***/
|
|
-
|
|
-static int scarlett2_speaker_switch_enum_ctl_info(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- static const char *const values[3] = {
|
|
- "Off", "Main", "Alt"
|
|
- };
|
|
-
|
|
- return snd_ctl_enum_info(uinfo, 1, 3, values);
|
|
-}
|
|
-
|
|
-static int scarlett2_speaker_switch_enum_ctl_get(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->monitor_other_updated) {
|
|
- err = scarlett2_update_monitor_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.enumerated.item[0] = private->speaker_switching_switch;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-/* when speaker switching gets enabled, switch the main/alt speakers
|
|
- * to HW volume and disable those controls
|
|
- */
|
|
-static int scarlett2_speaker_switch_enable(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_card *card = mixer->chip->card;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int i, err;
|
|
-
|
|
- for (i = 0; i < 4; i++) {
|
|
- int index = line_out_remap(private, i);
|
|
-
|
|
- /* switch the main/alt speakers to HW volume */
|
|
- if (!private->vol_sw_hw_switch[index]) {
|
|
- err = scarlett2_sw_hw_change(private->mixer, i, 1);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- /* disable the line out SW/HW switch */
|
|
- scarlett2_sw_hw_ctl_ro(private, i);
|
|
- snd_ctl_notify(card,
|
|
- SNDRV_CTL_EVENT_MASK_VALUE |
|
|
- SNDRV_CTL_EVENT_MASK_INFO,
|
|
- &private->sw_hw_ctls[i]->id);
|
|
- }
|
|
-
|
|
- /* when the next monitor-other notify comes in, update the mux
|
|
- * configuration
|
|
- */
|
|
- private->speaker_switching_switched = 1;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/* when speaker switching gets disabled, reenable the hw/sw controls
|
|
- * and invalidate the routing
|
|
- */
|
|
-static void scarlett2_speaker_switch_disable(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_card *card = mixer->chip->card;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int i;
|
|
-
|
|
- /* enable the line out SW/HW switch */
|
|
- for (i = 0; i < 4; i++) {
|
|
- scarlett2_sw_hw_ctl_rw(private, i);
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO,
|
|
- &private->sw_hw_ctls[i]->id);
|
|
- }
|
|
-
|
|
- /* when the next monitor-other notify comes in, update the mux
|
|
- * configuration
|
|
- */
|
|
- private->speaker_switching_switched = 1;
|
|
-}
|
|
-
|
|
-static int scarlett2_speaker_switch_enum_ctl_put(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->speaker_switching_switch;
|
|
- val = min(ucontrol->value.enumerated.item[0], 2U);
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->speaker_switching_switch = val;
|
|
-
|
|
- /* enable/disable speaker switching */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
|
|
- 0, !!val);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
-
|
|
- /* if speaker switching is enabled, select main or alt */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
|
|
- 0, val == 2);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
-
|
|
- /* update controls if speaker switching gets enabled or disabled */
|
|
- if (!oval && val)
|
|
- err = scarlett2_speaker_switch_enable(mixer);
|
|
- else if (oval && !val)
|
|
- scarlett2_speaker_switch_disable(mixer);
|
|
-
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_speaker_switch_enum_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = scarlett2_speaker_switch_enum_ctl_info,
|
|
- .get = scarlett2_speaker_switch_enum_ctl_get,
|
|
- .put = scarlett2_speaker_switch_enum_ctl_put,
|
|
-};
|
|
-
|
|
-static int scarlett2_add_speaker_switch_ctl(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- if (!info->has_speaker_switching)
|
|
- return 0;
|
|
-
|
|
- return scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_speaker_switch_enum_ctl,
|
|
- 0, 1, "Speaker Switching Playback Enum",
|
|
- &private->speaker_switching_ctl);
|
|
-}
|
|
-
|
|
-/*** Talkback and Talkback Map Controls ***/
|
|
-
|
|
-static int scarlett2_talkback_enum_ctl_info(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- static const char *const values[3] = {
|
|
- "Disabled", "Off", "On"
|
|
- };
|
|
-
|
|
- return snd_ctl_enum_info(uinfo, 1, 3, values);
|
|
-}
|
|
-
|
|
-static int scarlett2_talkback_enum_ctl_get(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->monitor_other_updated) {
|
|
- err = scarlett2_update_monitor_other(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.enumerated.item[0] = private->talkback_switch;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_talkback_enum_ctl_put(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->talkback_switch;
|
|
- val = min(ucontrol->value.enumerated.item[0], 2U);
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->talkback_switch = val;
|
|
-
|
|
- /* enable/disable talkback */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_MONITOR_OTHER_ENABLE,
|
|
- 1, !!val);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
-
|
|
- /* if talkback is enabled, select main or alt */
|
|
- err = scarlett2_usb_set_config(
|
|
- mixer, SCARLETT2_CONFIG_MONITOR_OTHER_SWITCH,
|
|
- 1, val == 2);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_talkback_enum_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = scarlett2_talkback_enum_ctl_info,
|
|
- .get = scarlett2_talkback_enum_ctl_get,
|
|
- .put = scarlett2_talkback_enum_ctl_put,
|
|
-};
|
|
-
|
|
-static int scarlett2_talkback_map_ctl_get(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = elem->control;
|
|
-
|
|
- ucontrol->value.integer.value[0] = private->talkback_map[index];
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_talkback_map_ctl_put(
|
|
- struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] =
|
|
- private->info->port_count;
|
|
- int num_mixes = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0, i;
|
|
- u16 bitmap = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->talkback_map[index];
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->talkback_map[index] = val;
|
|
-
|
|
- for (i = 0; i < num_mixes; i++)
|
|
- bitmap |= private->talkback_map[i] << i;
|
|
-
|
|
- /* Send updated bitmap to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_TALKBACK_MAP,
|
|
- 0, bitmap);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_talkback_map_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_talkback_map_ctl_get,
|
|
- .put = scarlett2_talkback_map_ctl_put,
|
|
-};
|
|
-
|
|
-static int scarlett2_add_talkback_ctls(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int num_mixes = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
- int err, i;
|
|
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
-
|
|
- if (!info->has_talkback)
|
|
- return 0;
|
|
-
|
|
- err = scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_talkback_enum_ctl,
|
|
- 0, 1, "Talkback Playback Enum",
|
|
- &private->talkback_ctl);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- for (i = 0; i < num_mixes; i++) {
|
|
- snprintf(s, sizeof(s),
|
|
- "Talkback Mix %c Playback Switch", i + 'A');
|
|
- err = scarlett2_add_new_ctl(mixer, &scarlett2_talkback_map_ctl,
|
|
- i, 1, s, NULL);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Dim/Mute Controls ***/
|
|
-
|
|
-static int scarlett2_dim_mute_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->vol_updated) {
|
|
- err = scarlett2_update_volumes(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.integer.value[0] = private->dim_mute[elem->control];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_dim_mute_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int num_line_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
-
|
|
- int index = elem->control;
|
|
- int oval, val, err = 0, i;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->dim_mute[index];
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->dim_mute[index] = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_DIM_MUTE,
|
|
- index, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
- if (index == SCARLETT2_BUTTON_MUTE)
|
|
- for (i = 0; i < num_line_out; i++) {
|
|
- int line_index = line_out_remap(private, i);
|
|
-
|
|
- if (private->vol_sw_hw_switch[line_index]) {
|
|
- private->mute_switch[line_index] = val;
|
|
- snd_ctl_notify(mixer->chip->card,
|
|
- SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->mute_ctls[i]->id);
|
|
- }
|
|
- }
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_dim_mute_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_dim_mute_ctl_get,
|
|
- .put = scarlett2_dim_mute_ctl_put
|
|
-};
|
|
-
|
|
-/*** Create the analogue output controls ***/
|
|
-
|
|
-static int scarlett2_add_line_out_ctls(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int num_line_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
- int err, i;
|
|
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
-
|
|
- /* Add R/O HW volume control */
|
|
- if (info->line_out_hw_vol) {
|
|
- snprintf(s, sizeof(s), "Master HW Playback Volume");
|
|
- err = scarlett2_add_new_ctl(mixer,
|
|
- &scarlett2_master_volume_ctl,
|
|
- 0, 1, s, &private->master_vol_ctl);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- /* Add volume controls */
|
|
- for (i = 0; i < num_line_out; i++) {
|
|
- int index = line_out_remap(private, i);
|
|
-
|
|
- /* Fader */
|
|
- if (info->line_out_descrs[i])
|
|
- snprintf(s, sizeof(s),
|
|
- "Line %02d (%s) Playback Volume",
|
|
- i + 1, info->line_out_descrs[i]);
|
|
- else
|
|
- snprintf(s, sizeof(s),
|
|
- "Line %02d Playback Volume",
|
|
- i + 1);
|
|
- err = scarlett2_add_new_ctl(mixer,
|
|
- &scarlett2_line_out_volume_ctl,
|
|
- i, 1, s, &private->vol_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Mute Switch */
|
|
- snprintf(s, sizeof(s),
|
|
- "Line %02d Mute Playback Switch",
|
|
- i + 1);
|
|
- err = scarlett2_add_new_ctl(mixer,
|
|
- &scarlett2_mute_ctl,
|
|
- i, 1, s,
|
|
- &private->mute_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Make the fader and mute controls read-only if the
|
|
- * SW/HW switch is set to HW
|
|
- */
|
|
- if (private->vol_sw_hw_switch[index])
|
|
- scarlett2_vol_ctl_set_writable(mixer, i, 0);
|
|
-
|
|
- /* SW/HW Switch */
|
|
- if (info->line_out_hw_vol) {
|
|
- snprintf(s, sizeof(s),
|
|
- "Line Out %02d Volume Control Playback Enum",
|
|
- i + 1);
|
|
- err = scarlett2_add_new_ctl(mixer,
|
|
- &scarlett2_sw_hw_enum_ctl,
|
|
- i, 1, s,
|
|
- &private->sw_hw_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Make the switch read-only if the line is
|
|
- * involved in speaker switching
|
|
- */
|
|
- if (private->speaker_switching_switch && i < 4)
|
|
- scarlett2_sw_hw_ctl_ro(private, i);
|
|
- }
|
|
- }
|
|
-
|
|
- /* Add dim/mute controls */
|
|
- if (info->line_out_hw_vol)
|
|
- for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++) {
|
|
- err = scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_dim_mute_ctl,
|
|
- i, 1, scarlett2_dim_mute_names[i],
|
|
- &private->dim_mute_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Create the analogue input controls ***/
|
|
-
|
|
-static int scarlett2_add_line_in_ctls(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- int err, i;
|
|
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
- const char *fmt = "Line In %d %s Capture %s";
|
|
- const char *fmt2 = "Line In %d-%d %s Capture %s";
|
|
-
|
|
- /* Add input level (line/inst) controls */
|
|
- for (i = 0; i < info->level_input_count; i++) {
|
|
- snprintf(s, sizeof(s), fmt, i + 1 + info->level_input_first,
|
|
- "Level", "Enum");
|
|
- err = scarlett2_add_new_ctl(mixer, &scarlett2_level_enum_ctl,
|
|
- i, 1, s, &private->level_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- /* Add input pad controls */
|
|
- for (i = 0; i < info->pad_input_count; i++) {
|
|
- snprintf(s, sizeof(s), fmt, i + 1, "Pad", "Switch");
|
|
- err = scarlett2_add_new_ctl(mixer, &scarlett2_pad_ctl,
|
|
- i, 1, s, &private->pad_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- /* Add input air controls */
|
|
- for (i = 0; i < info->air_input_count; i++) {
|
|
- snprintf(s, sizeof(s), fmt, i + 1, "Air", "Switch");
|
|
- err = scarlett2_add_new_ctl(mixer, &scarlett2_air_ctl,
|
|
- i, 1, s, &private->air_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- /* Add input phantom controls */
|
|
- if (info->inputs_per_phantom == 1) {
|
|
- for (i = 0; i < info->phantom_count; i++) {
|
|
- scnprintf(s, sizeof(s), fmt, i + 1,
|
|
- "Phantom Power", "Switch");
|
|
- err = scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_phantom_ctl,
|
|
- i, 1, s, &private->phantom_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
- } else if (info->inputs_per_phantom > 1) {
|
|
- for (i = 0; i < info->phantom_count; i++) {
|
|
- int from = i * info->inputs_per_phantom + 1;
|
|
- int to = (i + 1) * info->inputs_per_phantom;
|
|
-
|
|
- scnprintf(s, sizeof(s), fmt2, from, to,
|
|
- "Phantom Power", "Switch");
|
|
- err = scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_phantom_ctl,
|
|
- i, 1, s, &private->phantom_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
- }
|
|
- if (info->phantom_count) {
|
|
- err = scarlett2_add_new_ctl(
|
|
- mixer, &scarlett2_phantom_persistence_ctl, 0, 1,
|
|
- "Phantom Power Persistence Capture Switch", NULL);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Mixer Volume Controls ***/
|
|
-
|
|
-static int scarlett2_mixer_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
-
|
|
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
- uinfo->count = elem->channels;
|
|
- uinfo->value.integer.min = 0;
|
|
- uinfo->value.integer.max = SCARLETT2_MIXER_MAX_VALUE;
|
|
- uinfo->value.integer.step = 1;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_mixer_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
-
|
|
- ucontrol->value.integer.value[0] = private->mix[elem->control];
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_mixer_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int oval, val, num_mixer_in, mix_num, err = 0;
|
|
- int index = elem->control;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->mix[index];
|
|
- val = clamp(ucontrol->value.integer.value[0],
|
|
- 0L, (long)SCARLETT2_MIXER_MAX_VALUE);
|
|
- num_mixer_in = port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
- mix_num = index / num_mixer_in;
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->mix[index] = val;
|
|
- err = scarlett2_usb_set_mix(mixer, mix_num);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const DECLARE_TLV_DB_MINMAX(
|
|
- db_scale_scarlett2_mixer,
|
|
- SCARLETT2_MIXER_MIN_DB * 100,
|
|
- SCARLETT2_MIXER_MAX_DB * 100
|
|
-);
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_mixer_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
|
|
- SNDRV_CTL_ELEM_ACCESS_TLV_READ,
|
|
- .name = "",
|
|
- .info = scarlett2_mixer_ctl_info,
|
|
- .get = scarlett2_mixer_ctl_get,
|
|
- .put = scarlett2_mixer_ctl_put,
|
|
- .private_value = SCARLETT2_MIXER_MAX_DB, /* max value */
|
|
- .tlv = { .p = db_scale_scarlett2_mixer }
|
|
-};
|
|
-
|
|
-static int scarlett2_add_mixer_ctls(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int err, i, j;
|
|
- int index;
|
|
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
-
|
|
- int num_inputs =
|
|
- port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_OUT];
|
|
- int num_outputs =
|
|
- port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
-
|
|
- for (i = 0, index = 0; i < num_outputs; i++)
|
|
- for (j = 0; j < num_inputs; j++, index++) {
|
|
- snprintf(s, sizeof(s),
|
|
- "Mix %c Input %02d Playback Volume",
|
|
- 'A' + i, j + 1);
|
|
- err = scarlett2_add_new_ctl(mixer, &scarlett2_mixer_ctl,
|
|
- index, 1, s, NULL);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Mux Source Selection Controls ***/
|
|
-
|
|
-static int scarlett2_mux_src_enum_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- unsigned int item = uinfo->value.enumerated.item;
|
|
- int items = private->num_mux_srcs;
|
|
- int port_type;
|
|
-
|
|
- uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
|
|
- uinfo->count = elem->channels;
|
|
- uinfo->value.enumerated.items = items;
|
|
-
|
|
- if (item >= items)
|
|
- item = uinfo->value.enumerated.item = items - 1;
|
|
-
|
|
- for (port_type = 0;
|
|
- port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
- port_type++) {
|
|
- if (item < port_count[port_type][SCARLETT2_PORT_IN]) {
|
|
- const struct scarlett2_port *port =
|
|
- &scarlett2_ports[port_type];
|
|
-
|
|
- sprintf(uinfo->value.enumerated.name,
|
|
- port->src_descr, item + port->src_num_offset);
|
|
- return 0;
|
|
- }
|
|
- item -= port_count[port_type][SCARLETT2_PORT_IN];
|
|
- }
|
|
-
|
|
- return -EINVAL;
|
|
-}
|
|
-
|
|
-static int scarlett2_mux_src_enum_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
- int err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- if (private->mux_updated) {
|
|
- err = scarlett2_usb_get_mux(mixer);
|
|
- if (err < 0)
|
|
- goto unlock;
|
|
- }
|
|
- ucontrol->value.enumerated.item[0] = private->mux[index];
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static int scarlett2_mux_src_enum_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- int index = line_out_remap(private, elem->control);
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->mux[index];
|
|
- val = min(ucontrol->value.enumerated.item[0],
|
|
- private->num_mux_srcs - 1U);
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->mux[index] = val;
|
|
- err = scarlett2_usb_set_mux(mixer);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_mux_src_enum_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = scarlett2_mux_src_enum_ctl_info,
|
|
- .get = scarlett2_mux_src_enum_ctl_get,
|
|
- .put = scarlett2_mux_src_enum_ctl_put,
|
|
-};
|
|
-
|
|
-static int scarlett2_add_mux_enums(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int port_type, channel, i;
|
|
-
|
|
- for (i = 0, port_type = 0;
|
|
- port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
- port_type++) {
|
|
- for (channel = 0;
|
|
- channel < port_count[port_type][SCARLETT2_PORT_OUT];
|
|
- channel++, i++) {
|
|
- int err;
|
|
- char s[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
- const char *const descr =
|
|
- scarlett2_ports[port_type].dst_descr;
|
|
-
|
|
- snprintf(s, sizeof(s) - 5, descr, channel + 1);
|
|
- strcat(s, " Enum");
|
|
-
|
|
- err = scarlett2_add_new_ctl(mixer,
|
|
- &scarlett2_mux_src_enum_ctl,
|
|
- i, 1, s,
|
|
- &private->mux_ctls[i]);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
- }
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-/*** Meter Controls ***/
|
|
-
|
|
-static int scarlett2_meter_ctl_info(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_info *uinfo)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
-
|
|
- uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
- uinfo->count = elem->channels;
|
|
- uinfo->value.integer.min = 0;
|
|
- uinfo->value.integer.max = 4095;
|
|
- uinfo->value.integer.step = 1;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_meter_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- u16 meter_levels[SCARLETT2_MAX_METERS];
|
|
- int i, err;
|
|
-
|
|
- err = scarlett2_usb_get_meter_levels(elem->head.mixer, elem->channels,
|
|
- meter_levels);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- for (i = 0; i < elem->channels; i++)
|
|
- ucontrol->value.integer.value[i] = meter_levels[i];
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_meter_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
- .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
|
|
- .name = "",
|
|
- .info = scarlett2_meter_ctl_info,
|
|
- .get = scarlett2_meter_ctl_get
|
|
-};
|
|
-
|
|
-static int scarlett2_add_meter_ctl(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- /* devices without a mixer also don't support reporting levels */
|
|
- if (private->info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
- return 0;
|
|
-
|
|
- return scarlett2_add_new_ctl(mixer, &scarlett2_meter_ctl,
|
|
- 0, private->num_mux_dsts,
|
|
- "Level Meter", NULL);
|
|
-}
|
|
-
|
|
-/*** MSD Controls ***/
|
|
-
|
|
-static int scarlett2_msd_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
-
|
|
- ucontrol->value.integer.value[0] = private->msd_switch;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_msd_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->msd_switch;
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->msd_switch = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer, SCARLETT2_CONFIG_MSD_SWITCH,
|
|
- 0, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_msd_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_msd_ctl_get,
|
|
- .put = scarlett2_msd_ctl_put,
|
|
-};
|
|
-
|
|
-static int scarlett2_add_msd_ctl(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- if (!info->has_msd_mode)
|
|
- return 0;
|
|
-
|
|
- /* If MSD mode is off, hide the switch by default */
|
|
- if (!private->msd_switch && !(mixer->chip->setup & SCARLETT2_MSD_ENABLE))
|
|
- return 0;
|
|
-
|
|
- /* Add MSD control */
|
|
- return scarlett2_add_new_ctl(mixer, &scarlett2_msd_ctl,
|
|
- 0, 1, "MSD Mode Switch", NULL);
|
|
-}
|
|
-
|
|
-/*** Standalone Control ***/
|
|
-
|
|
-static int scarlett2_standalone_ctl_get(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct scarlett2_data *private = elem->head.mixer->private_data;
|
|
-
|
|
- ucontrol->value.integer.value[0] = private->standalone_switch;
|
|
- return 0;
|
|
-}
|
|
-
|
|
-static int scarlett2_standalone_ctl_put(struct snd_kcontrol *kctl,
|
|
- struct snd_ctl_elem_value *ucontrol)
|
|
-{
|
|
- struct usb_mixer_elem_info *elem = kctl->private_data;
|
|
- struct usb_mixer_interface *mixer = elem->head.mixer;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- int oval, val, err = 0;
|
|
-
|
|
- mutex_lock(&private->data_mutex);
|
|
-
|
|
- oval = private->standalone_switch;
|
|
- val = !!ucontrol->value.integer.value[0];
|
|
-
|
|
- if (oval == val)
|
|
- goto unlock;
|
|
-
|
|
- private->standalone_switch = val;
|
|
-
|
|
- /* Send switch change to the device */
|
|
- err = scarlett2_usb_set_config(mixer,
|
|
- SCARLETT2_CONFIG_STANDALONE_SWITCH,
|
|
- 0, val);
|
|
- if (err == 0)
|
|
- err = 1;
|
|
-
|
|
-unlock:
|
|
- mutex_unlock(&private->data_mutex);
|
|
- return err;
|
|
-}
|
|
-
|
|
-static const struct snd_kcontrol_new scarlett2_standalone_ctl = {
|
|
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
- .name = "",
|
|
- .info = snd_ctl_boolean_mono_info,
|
|
- .get = scarlett2_standalone_ctl_get,
|
|
- .put = scarlett2_standalone_ctl_put,
|
|
-};
|
|
-
|
|
-static int scarlett2_add_standalone_ctl(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- if (private->info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
- return 0;
|
|
-
|
|
- /* Add standalone control */
|
|
- return scarlett2_add_new_ctl(mixer, &scarlett2_standalone_ctl,
|
|
- 0, 1, "Standalone Switch", NULL);
|
|
-}
|
|
-
|
|
-/*** Cleanup/Suspend Callbacks ***/
|
|
-
|
|
-static void scarlett2_private_free(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- cancel_delayed_work_sync(&private->work);
|
|
- kfree(private);
|
|
- mixer->private_data = NULL;
|
|
-}
|
|
-
|
|
-static void scarlett2_private_suspend(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- if (cancel_delayed_work_sync(&private->work))
|
|
- scarlett2_config_save(private->mixer);
|
|
-}
|
|
-
|
|
-/*** Initialisation ***/
|
|
-
|
|
-static void scarlett2_count_mux_io(struct scarlett2_data *private)
|
|
-{
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int port_type, srcs = 0, dsts = 0;
|
|
-
|
|
- for (port_type = 0;
|
|
- port_type < SCARLETT2_PORT_TYPE_COUNT;
|
|
- port_type++) {
|
|
- srcs += port_count[port_type][SCARLETT2_PORT_IN];
|
|
- dsts += port_count[port_type][SCARLETT2_PORT_OUT];
|
|
- }
|
|
-
|
|
- private->num_mux_srcs = srcs;
|
|
- private->num_mux_dsts = dsts;
|
|
-}
|
|
-
|
|
-/* Look through the interface descriptors for the Focusrite Control
|
|
- * interface (bInterfaceClass = 255 Vendor Specific Class) and set
|
|
- * bInterfaceNumber, bEndpointAddress, wMaxPacketSize, and bInterval
|
|
- * in private
|
|
- */
|
|
-static int scarlett2_find_fc_interface(struct usb_device *dev,
|
|
- struct scarlett2_data *private)
|
|
-{
|
|
- struct usb_host_config *config = dev->actconfig;
|
|
- int i;
|
|
-
|
|
- for (i = 0; i < config->desc.bNumInterfaces; i++) {
|
|
- struct usb_interface *intf = config->interface[i];
|
|
- struct usb_interface_descriptor *desc =
|
|
- &intf->altsetting[0].desc;
|
|
- struct usb_endpoint_descriptor *epd;
|
|
-
|
|
- if (desc->bInterfaceClass != 255)
|
|
- continue;
|
|
-
|
|
- epd = get_endpoint(intf->altsetting, 0);
|
|
- private->bInterfaceNumber = desc->bInterfaceNumber;
|
|
- private->bEndpointAddress = epd->bEndpointAddress &
|
|
- USB_ENDPOINT_NUMBER_MASK;
|
|
- private->wMaxPacketSize = le16_to_cpu(epd->wMaxPacketSize);
|
|
- private->bInterval = epd->bInterval;
|
|
- return 0;
|
|
- }
|
|
-
|
|
- return -EINVAL;
|
|
-}
|
|
-
|
|
-/* Initialise private data */
|
|
-static int scarlett2_init_private(struct usb_mixer_interface *mixer,
|
|
- const struct scarlett2_device_info *info)
|
|
-{
|
|
- struct scarlett2_data *private =
|
|
- kzalloc(sizeof(struct scarlett2_data), GFP_KERNEL);
|
|
-
|
|
- if (!private)
|
|
- return -ENOMEM;
|
|
-
|
|
- mutex_init(&private->usb_mutex);
|
|
- mutex_init(&private->data_mutex);
|
|
- INIT_DELAYED_WORK(&private->work, scarlett2_config_save_work);
|
|
-
|
|
- mixer->private_data = private;
|
|
- mixer->private_free = scarlett2_private_free;
|
|
- mixer->private_suspend = scarlett2_private_suspend;
|
|
-
|
|
- private->info = info;
|
|
- scarlett2_count_mux_io(private);
|
|
- private->scarlett2_seq = 0;
|
|
- private->mixer = mixer;
|
|
-
|
|
- return scarlett2_find_fc_interface(mixer->chip->dev, private);
|
|
-}
|
|
-
|
|
-/* Cargo cult proprietary initialisation sequence */
|
|
-static int scarlett2_usb_init(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct usb_device *dev = mixer->chip->dev;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- u8 buf[24];
|
|
- int err;
|
|
-
|
|
- if (usb_pipe_type_check(dev, usb_sndctrlpipe(dev, 0)))
|
|
- return -EINVAL;
|
|
-
|
|
- /* step 0 */
|
|
- err = scarlett2_usb_rx(dev, private->bInterfaceNumber,
|
|
- SCARLETT2_USB_CMD_INIT, buf, sizeof(buf));
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* step 1 */
|
|
- private->scarlett2_seq = 1;
|
|
- err = scarlett2_usb(mixer, SCARLETT2_USB_INIT_1, NULL, 0, NULL, 0);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* step 2 */
|
|
- private->scarlett2_seq = 1;
|
|
- return scarlett2_usb(mixer, SCARLETT2_USB_INIT_2, NULL, 0, NULL, 84);
|
|
-}
|
|
-
|
|
-/* Read configuration from the interface on start */
|
|
-static int scarlett2_read_configs(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int num_line_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
- int num_mixer_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_MIX][SCARLETT2_PORT_IN];
|
|
- struct scarlett2_usb_volume_status volume_status;
|
|
- int err, i;
|
|
-
|
|
- if (info->has_msd_mode) {
|
|
- err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_MSD_SWITCH,
|
|
- 1, &private->msd_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* no other controls are created if MSD mode is on */
|
|
- if (private->msd_switch)
|
|
- return 0;
|
|
- }
|
|
-
|
|
- err = scarlett2_update_input_other(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = scarlett2_update_monitor_other(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* the rest of the configuration is for devices with a mixer */
|
|
- if (info->config_set == SCARLETT2_CONFIG_SET_NO_MIXER)
|
|
- return 0;
|
|
-
|
|
- err = scarlett2_usb_get_config(
|
|
- mixer, SCARLETT2_CONFIG_STANDALONE_SWITCH,
|
|
- 1, &private->standalone_switch);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = scarlett2_update_sync(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- err = scarlett2_usb_get_volume_status(mixer, &volume_status);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- if (info->line_out_hw_vol)
|
|
- for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++)
|
|
- private->dim_mute[i] = !!volume_status.dim_mute[i];
|
|
-
|
|
- private->master_vol = clamp(
|
|
- volume_status.master_vol + SCARLETT2_VOLUME_BIAS,
|
|
- 0, SCARLETT2_VOLUME_BIAS);
|
|
-
|
|
- for (i = 0; i < num_line_out; i++) {
|
|
- int volume, mute;
|
|
-
|
|
- private->vol_sw_hw_switch[i] =
|
|
- info->line_out_hw_vol
|
|
- && volume_status.sw_hw_switch[i];
|
|
-
|
|
- volume = private->vol_sw_hw_switch[i]
|
|
- ? volume_status.master_vol
|
|
- : volume_status.sw_vol[i];
|
|
- volume = clamp(volume + SCARLETT2_VOLUME_BIAS,
|
|
- 0, SCARLETT2_VOLUME_BIAS);
|
|
- private->vol[i] = volume;
|
|
-
|
|
- mute = private->vol_sw_hw_switch[i]
|
|
- ? private->dim_mute[SCARLETT2_BUTTON_MUTE]
|
|
- : volume_status.mute_switch[i];
|
|
- private->mute_switch[i] = mute;
|
|
- }
|
|
-
|
|
- for (i = 0; i < num_mixer_out; i++) {
|
|
- err = scarlett2_usb_get_mix(mixer, i);
|
|
- if (err < 0)
|
|
- return err;
|
|
- }
|
|
-
|
|
- return scarlett2_usb_get_mux(mixer);
|
|
-}
|
|
-
|
|
-/* Notify on sync change */
|
|
-static void scarlett2_notify_sync(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
-
|
|
- private->sync_updated = 1;
|
|
-
|
|
- snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->sync_ctl->id);
|
|
-}
|
|
-
|
|
-/* Notify on monitor change */
|
|
-static void scarlett2_notify_monitor(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_card *card = mixer->chip->card;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int num_line_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
- int i;
|
|
-
|
|
- /* if line_out_hw_vol is 0, there are no controls to update */
|
|
- if (!info->line_out_hw_vol)
|
|
- return;
|
|
-
|
|
- private->vol_updated = 1;
|
|
-
|
|
- snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->master_vol_ctl->id);
|
|
-
|
|
- for (i = 0; i < num_line_out; i++)
|
|
- if (private->vol_sw_hw_switch[line_out_remap(private, i)])
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->vol_ctls[i]->id);
|
|
-}
|
|
-
|
|
-/* Notify on dim/mute change */
|
|
-static void scarlett2_notify_dim_mute(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_card *card = mixer->chip->card;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count;
|
|
- int num_line_out =
|
|
- port_count[SCARLETT2_PORT_TYPE_ANALOGUE][SCARLETT2_PORT_OUT];
|
|
- int i;
|
|
-
|
|
- private->vol_updated = 1;
|
|
-
|
|
- if (!info->line_out_hw_vol)
|
|
- return;
|
|
-
|
|
- for (i = 0; i < SCARLETT2_DIM_MUTE_COUNT; i++)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->dim_mute_ctls[i]->id);
|
|
-
|
|
- for (i = 0; i < num_line_out; i++)
|
|
- if (private->vol_sw_hw_switch[line_out_remap(private, i)])
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->mute_ctls[i]->id);
|
|
-}
|
|
-
|
|
-/* Notify on "input other" change (level/pad/air) */
|
|
-static void scarlett2_notify_input_other(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_card *card = mixer->chip->card;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
- int i;
|
|
-
|
|
- private->input_other_updated = 1;
|
|
-
|
|
- for (i = 0; i < info->level_input_count; i++)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->level_ctls[i]->id);
|
|
- for (i = 0; i < info->pad_input_count; i++)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->pad_ctls[i]->id);
|
|
- for (i = 0; i < info->air_input_count; i++)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->air_ctls[i]->id);
|
|
- for (i = 0; i < info->phantom_count; i++)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->phantom_ctls[i]->id);
|
|
-}
|
|
-
|
|
-/* Notify on "monitor other" change (direct monitor, speaker
|
|
- * switching, talkback)
|
|
- */
|
|
-static void scarlett2_notify_monitor_other(
|
|
- struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_card *card = mixer->chip->card;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- const struct scarlett2_device_info *info = private->info;
|
|
-
|
|
- private->monitor_other_updated = 1;
|
|
-
|
|
- if (info->direct_monitor) {
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->direct_monitor_ctl->id);
|
|
- return;
|
|
- }
|
|
-
|
|
- if (info->has_speaker_switching)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->speaker_switching_ctl->id);
|
|
-
|
|
- if (info->has_talkback)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->talkback_ctl->id);
|
|
-
|
|
- /* if speaker switching was recently enabled or disabled,
|
|
- * invalidate the dim/mute and mux enum controls
|
|
- */
|
|
- if (private->speaker_switching_switched) {
|
|
- int i;
|
|
-
|
|
- scarlett2_notify_dim_mute(mixer);
|
|
-
|
|
- private->speaker_switching_switched = 0;
|
|
- private->mux_updated = 1;
|
|
-
|
|
- for (i = 0; i < private->num_mux_dsts; i++)
|
|
- snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
|
|
- &private->mux_ctls[i]->id);
|
|
- }
|
|
-}
|
|
-
|
|
-/* Interrupt callback */
|
|
-static void scarlett2_notify(struct urb *urb)
|
|
-{
|
|
- struct usb_mixer_interface *mixer = urb->context;
|
|
- int len = urb->actual_length;
|
|
- int ustatus = urb->status;
|
|
- u32 data;
|
|
-
|
|
- if (ustatus != 0 || len != 8)
|
|
- goto requeue;
|
|
-
|
|
- data = le32_to_cpu(*(__le32 *)urb->transfer_buffer);
|
|
- if (data & SCARLETT2_USB_NOTIFY_SYNC)
|
|
- scarlett2_notify_sync(mixer);
|
|
- if (data & SCARLETT2_USB_NOTIFY_MONITOR)
|
|
- scarlett2_notify_monitor(mixer);
|
|
- if (data & SCARLETT2_USB_NOTIFY_DIM_MUTE)
|
|
- scarlett2_notify_dim_mute(mixer);
|
|
- if (data & SCARLETT2_USB_NOTIFY_INPUT_OTHER)
|
|
- scarlett2_notify_input_other(mixer);
|
|
- if (data & SCARLETT2_USB_NOTIFY_MONITOR_OTHER)
|
|
- scarlett2_notify_monitor_other(mixer);
|
|
-
|
|
-requeue:
|
|
- if (ustatus != -ENOENT &&
|
|
- ustatus != -ECONNRESET &&
|
|
- ustatus != -ESHUTDOWN) {
|
|
- urb->dev = mixer->chip->dev;
|
|
- usb_submit_urb(urb, GFP_ATOMIC);
|
|
- }
|
|
-}
|
|
-
|
|
-static int scarlett2_init_notify(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct usb_device *dev = mixer->chip->dev;
|
|
- struct scarlett2_data *private = mixer->private_data;
|
|
- unsigned int pipe = usb_rcvintpipe(dev, private->bEndpointAddress);
|
|
- void *transfer_buffer;
|
|
-
|
|
- if (mixer->urb) {
|
|
- usb_audio_err(mixer->chip,
|
|
- "%s: mixer urb already in use!\n", __func__);
|
|
- return 0;
|
|
- }
|
|
-
|
|
- if (usb_pipe_type_check(dev, pipe))
|
|
- return -EINVAL;
|
|
-
|
|
- mixer->urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
- if (!mixer->urb)
|
|
- return -ENOMEM;
|
|
-
|
|
- transfer_buffer = kmalloc(private->wMaxPacketSize, GFP_KERNEL);
|
|
- if (!transfer_buffer)
|
|
- return -ENOMEM;
|
|
-
|
|
- usb_fill_int_urb(mixer->urb, dev, pipe,
|
|
- transfer_buffer, private->wMaxPacketSize,
|
|
- scarlett2_notify, mixer, private->bInterval);
|
|
-
|
|
- return usb_submit_urb(mixer->urb, GFP_KERNEL);
|
|
-}
|
|
-
|
|
-static int snd_scarlett_gen2_controls_create(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- const struct scarlett2_device_info **info = scarlett2_devices;
|
|
- int err;
|
|
-
|
|
- /* Find device in scarlett2_devices */
|
|
- while (*info && (*info)->usb_id != mixer->chip->usb_id)
|
|
- info++;
|
|
- if (!*info)
|
|
- return -EINVAL;
|
|
-
|
|
- /* Initialise private data */
|
|
- err = scarlett2_init_private(mixer, *info);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Send proprietary USB initialisation sequence */
|
|
- err = scarlett2_usb_init(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Read volume levels and controls from the interface */
|
|
- err = scarlett2_read_configs(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the MSD control */
|
|
- err = scarlett2_add_msd_ctl(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* If MSD mode is enabled, don't create any other controls */
|
|
- if (((struct scarlett2_data *)mixer->private_data)->msd_switch)
|
|
- return 0;
|
|
-
|
|
- /* Create the analogue output controls */
|
|
- err = scarlett2_add_line_out_ctls(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the analogue input controls */
|
|
- err = scarlett2_add_line_in_ctls(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the input, output, and mixer mux input selections */
|
|
- err = scarlett2_add_mux_enums(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the matrix mixer controls */
|
|
- err = scarlett2_add_mixer_ctls(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the level meter controls */
|
|
- err = scarlett2_add_meter_ctl(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the sync control */
|
|
- err = scarlett2_add_sync_ctl(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the direct monitor control */
|
|
- err = scarlett2_add_direct_monitor_ctl(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the speaker switching control */
|
|
- err = scarlett2_add_speaker_switch_ctl(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the talkback controls */
|
|
- err = scarlett2_add_talkback_ctls(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Create the standalone control */
|
|
- err = scarlett2_add_standalone_ctl(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- /* Set up the interrupt polling */
|
|
- err = scarlett2_init_notify(mixer);
|
|
- if (err < 0)
|
|
- return err;
|
|
-
|
|
- return 0;
|
|
-}
|
|
-
|
|
-int snd_scarlett_gen2_init(struct usb_mixer_interface *mixer)
|
|
-{
|
|
- struct snd_usb_audio *chip = mixer->chip;
|
|
- int err;
|
|
-
|
|
- /* only use UAC_VERSION_2 */
|
|
- if (!mixer->protocol)
|
|
- return 0;
|
|
-
|
|
- if (!(chip->setup & SCARLETT2_ENABLE)) {
|
|
- usb_audio_info(chip,
|
|
- "Focusrite Scarlett Gen 2/3 Mixer Driver disabled; "
|
|
- "use options snd_usb_audio vid=0x%04x pid=0x%04x "
|
|
- "device_setup=1 to enable and report any issues "
|
|
- "to g@b4.vu",
|
|
- USB_ID_VENDOR(chip->usb_id),
|
|
- USB_ID_PRODUCT(chip->usb_id));
|
|
- return 0;
|
|
- }
|
|
-
|
|
- usb_audio_info(chip,
|
|
- "Focusrite Scarlett Gen 2/3 Mixer Driver enabled pid=0x%04x",
|
|
- USB_ID_PRODUCT(chip->usb_id));
|
|
-
|
|
- err = snd_scarlett_gen2_controls_create(mixer);
|
|
- if (err < 0)
|
|
- usb_audio_err(mixer->chip,
|
|
- "Error initialising Scarlett Mixer Driver: %d",
|
|
- err);
|
|
-
|
|
- return err;
|
|
-}
|
|
diff --git a/sound/usb/mixer_scarlett_gen2.h b/sound/usb/mixer_scarlett_gen2.h
|
|
deleted file mode 100644
|
|
index 668c6b0cb50a6..0000000000000
|
|
--- a/sound/usb/mixer_scarlett_gen2.h
|
|
+++ /dev/null
|
|
@@ -1,7 +0,0 @@
|
|
-/* SPDX-License-Identifier: GPL-2.0 */
|
|
-#ifndef __USB_MIXER_SCARLETT_GEN2_H
|
|
-#define __USB_MIXER_SCARLETT_GEN2_H
|
|
-
|
|
-int snd_scarlett_gen2_init(struct usb_mixer_interface *mixer);
|
|
-
|
|
-#endif /* __USB_MIXER_SCARLETT_GEN2_H */
|
|
diff --git a/tools/perf/util/bpf_skel/lock_contention.bpf.c b/tools/perf/util/bpf_skel/lock_contention.bpf.c
|
|
index 8d3cfbb3cc65b..473106c72c695 100644
|
|
--- a/tools/perf/util/bpf_skel/lock_contention.bpf.c
|
|
+++ b/tools/perf/util/bpf_skel/lock_contention.bpf.c
|
|
@@ -238,6 +238,7 @@ static inline __u32 check_lock_type(__u64 lock, __u32 flags)
|
|
struct task_struct *curr;
|
|
struct mm_struct___old *mm_old;
|
|
struct mm_struct___new *mm_new;
|
|
+ struct sighand_struct *sighand;
|
|
|
|
switch (flags) {
|
|
case LCB_F_READ: /* rwsem */
|
|
@@ -259,7 +260,9 @@ static inline __u32 check_lock_type(__u64 lock, __u32 flags)
|
|
break;
|
|
case LCB_F_SPIN: /* spinlock */
|
|
curr = bpf_get_current_task_btf();
|
|
- if (&curr->sighand->siglock == (void *)lock)
|
|
+ sighand = curr->sighand;
|
|
+
|
|
+ if (sighand && &sighand->siglock == (void *)lock)
|
|
return LCD_F_SIGHAND_LOCK;
|
|
break;
|
|
default:
|
|
diff --git a/tools/testing/selftests/ftrace/test.d/event/subsystem-enable.tc b/tools/testing/selftests/ftrace/test.d/event/subsystem-enable.tc
|
|
index b1ede62498667..b7c8f29c09a97 100644
|
|
--- a/tools/testing/selftests/ftrace/test.d/event/subsystem-enable.tc
|
|
+++ b/tools/testing/selftests/ftrace/test.d/event/subsystem-enable.tc
|
|
@@ -18,7 +18,7 @@ echo 'sched:*' > set_event
|
|
|
|
yield
|
|
|
|
-count=`cat trace | grep -v ^# | awk '{ print $5 }' | sort -u | wc -l`
|
|
+count=`head -n 100 trace | grep -v ^# | awk '{ print $5 }' | sort -u | wc -l`
|
|
if [ $count -lt 3 ]; then
|
|
fail "at least fork, exec and exit events should be recorded"
|
|
fi
|
|
@@ -29,7 +29,7 @@ echo 1 > events/sched/enable
|
|
|
|
yield
|
|
|
|
-count=`cat trace | grep -v ^# | awk '{ print $5 }' | sort -u | wc -l`
|
|
+count=`head -n 100 trace | grep -v ^# | awk '{ print $5 }' | sort -u | wc -l`
|
|
if [ $count -lt 3 ]; then
|
|
fail "at least fork, exec and exit events should be recorded"
|
|
fi
|
|
@@ -40,7 +40,7 @@ echo 0 > events/sched/enable
|
|
|
|
yield
|
|
|
|
-count=`cat trace | grep -v ^# | awk '{ print $5 }' | sort -u | wc -l`
|
|
+count=`head -n 100 trace | grep -v ^# | awk '{ print $5 }' | sort -u | wc -l`
|
|
if [ $count -ne 0 ]; then
|
|
fail "any of scheduler events should not be recorded"
|
|
fi
|
|
diff --git a/tools/testing/selftests/kselftest.h b/tools/testing/selftests/kselftest.h
|
|
index 529d29a359002..e8eecbc83a60e 100644
|
|
--- a/tools/testing/selftests/kselftest.h
|
|
+++ b/tools/testing/selftests/kselftest.h
|
|
@@ -49,6 +49,7 @@
|
|
#include <unistd.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
+#include <sys/utsname.h>
|
|
#endif
|
|
|
|
#ifndef ARRAY_SIZE
|
|
@@ -327,4 +328,21 @@ static inline int ksft_exit_skip(const char *msg, ...)
|
|
exit(KSFT_SKIP);
|
|
}
|
|
|
|
+static inline int ksft_min_kernel_version(unsigned int min_major,
|
|
+ unsigned int min_minor)
|
|
+{
|
|
+#ifdef NOLIBC
|
|
+ ksft_print_msg("NOLIBC: Can't check kernel version: Function not implemented\n");
|
|
+ return 0;
|
|
+#else
|
|
+ unsigned int major, minor;
|
|
+ struct utsname info;
|
|
+
|
|
+ if (uname(&info) || sscanf(info.release, "%u.%u.", &major, &minor) != 2)
|
|
+ ksft_exit_fail_msg("Can't parse kernel version\n");
|
|
+
|
|
+ return major > min_major || (major == min_major && minor >= min_minor);
|
|
+#endif
|
|
+}
|
|
+
|
|
#endif /* __KSELFTEST_H */
|
|
diff --git a/tools/testing/selftests/net/udpgso.c b/tools/testing/selftests/net/udpgso.c
|
|
index 7badaf215de28..b02080d09fbc0 100644
|
|
--- a/tools/testing/selftests/net/udpgso.c
|
|
+++ b/tools/testing/selftests/net/udpgso.c
|
|
@@ -34,7 +34,7 @@
|
|
#endif
|
|
|
|
#ifndef UDP_MAX_SEGMENTS
|
|
-#define UDP_MAX_SEGMENTS (1 << 6UL)
|
|
+#define UDP_MAX_SEGMENTS (1 << 7UL)
|
|
#endif
|
|
|
|
#define CONST_MTU_TEST 1500
|
|
diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
|
|
index 9a42403eaff70..c001dd79179d5 100644
|
|
--- a/tools/testing/selftests/timers/posix_timers.c
|
|
+++ b/tools/testing/selftests/timers/posix_timers.c
|
|
@@ -76,22 +76,21 @@ static int check_diff(struct timeval start, struct timeval end)
|
|
|
|
static int check_itimer(int which)
|
|
{
|
|
+ const char *name;
|
|
int err;
|
|
struct timeval start, end;
|
|
struct itimerval val = {
|
|
.it_value.tv_sec = DELAY,
|
|
};
|
|
|
|
- printf("Check itimer ");
|
|
-
|
|
if (which == ITIMER_VIRTUAL)
|
|
- printf("virtual... ");
|
|
+ name = "ITIMER_VIRTUAL";
|
|
else if (which == ITIMER_PROF)
|
|
- printf("prof... ");
|
|
+ name = "ITIMER_PROF";
|
|
else if (which == ITIMER_REAL)
|
|
- printf("real... ");
|
|
-
|
|
- fflush(stdout);
|
|
+ name = "ITIMER_REAL";
|
|
+ else
|
|
+ return -1;
|
|
|
|
done = 0;
|
|
|
|
@@ -104,13 +103,13 @@ static int check_itimer(int which)
|
|
|
|
err = gettimeofday(&start, NULL);
|
|
if (err < 0) {
|
|
- perror("Can't call gettimeofday()\n");
|
|
+ ksft_perror("Can't call gettimeofday()");
|
|
return -1;
|
|
}
|
|
|
|
err = setitimer(which, &val, NULL);
|
|
if (err < 0) {
|
|
- perror("Can't set timer\n");
|
|
+ ksft_perror("Can't set timer");
|
|
return -1;
|
|
}
|
|
|
|
@@ -123,20 +122,18 @@ static int check_itimer(int which)
|
|
|
|
err = gettimeofday(&end, NULL);
|
|
if (err < 0) {
|
|
- perror("Can't call gettimeofday()\n");
|
|
+ ksft_perror("Can't call gettimeofday()");
|
|
return -1;
|
|
}
|
|
|
|
- if (!check_diff(start, end))
|
|
- printf("[OK]\n");
|
|
- else
|
|
- printf("[FAIL]\n");
|
|
+ ksft_test_result(check_diff(start, end) == 0, "%s\n", name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_timer_create(int which)
|
|
{
|
|
+ const char *type;
|
|
int err;
|
|
timer_t id;
|
|
struct timeval start, end;
|
|
@@ -144,31 +141,32 @@ static int check_timer_create(int which)
|
|
.it_value.tv_sec = DELAY,
|
|
};
|
|
|
|
- printf("Check timer_create() ");
|
|
if (which == CLOCK_THREAD_CPUTIME_ID) {
|
|
- printf("per thread... ");
|
|
+ type = "thread";
|
|
} else if (which == CLOCK_PROCESS_CPUTIME_ID) {
|
|
- printf("per process... ");
|
|
+ type = "process";
|
|
+ } else {
|
|
+ ksft_print_msg("Unknown timer_create() type %d\n", which);
|
|
+ return -1;
|
|
}
|
|
- fflush(stdout);
|
|
|
|
done = 0;
|
|
err = timer_create(which, NULL, &id);
|
|
if (err < 0) {
|
|
- perror("Can't create timer\n");
|
|
+ ksft_perror("Can't create timer");
|
|
return -1;
|
|
}
|
|
signal(SIGALRM, sig_handler);
|
|
|
|
err = gettimeofday(&start, NULL);
|
|
if (err < 0) {
|
|
- perror("Can't call gettimeofday()\n");
|
|
+ ksft_perror("Can't call gettimeofday()");
|
|
return -1;
|
|
}
|
|
|
|
err = timer_settime(id, 0, &val, NULL);
|
|
if (err < 0) {
|
|
- perror("Can't set timer\n");
|
|
+ ksft_perror("Can't set timer");
|
|
return -1;
|
|
}
|
|
|
|
@@ -176,96 +174,90 @@ static int check_timer_create(int which)
|
|
|
|
err = gettimeofday(&end, NULL);
|
|
if (err < 0) {
|
|
- perror("Can't call gettimeofday()\n");
|
|
+ ksft_perror("Can't call gettimeofday()");
|
|
return -1;
|
|
}
|
|
|
|
- if (!check_diff(start, end))
|
|
- printf("[OK]\n");
|
|
- else
|
|
- printf("[FAIL]\n");
|
|
+ ksft_test_result(check_diff(start, end) == 0,
|
|
+ "timer_create() per %s\n", type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
-int remain;
|
|
-__thread int got_signal;
|
|
+static pthread_t ctd_thread;
|
|
+static volatile int ctd_count, ctd_failed;
|
|
|
|
-static void *distribution_thread(void *arg)
|
|
+static void ctd_sighandler(int sig)
|
|
{
|
|
- while (__atomic_load_n(&remain, __ATOMIC_RELAXED));
|
|
- return NULL;
|
|
+ if (pthread_self() != ctd_thread)
|
|
+ ctd_failed = 1;
|
|
+ ctd_count--;
|
|
}
|
|
|
|
-static void distribution_handler(int nr)
|
|
+static void *ctd_thread_func(void *arg)
|
|
{
|
|
- if (!__atomic_exchange_n(&got_signal, 1, __ATOMIC_RELAXED))
|
|
- __atomic_fetch_sub(&remain, 1, __ATOMIC_RELAXED);
|
|
-}
|
|
-
|
|
-/*
|
|
- * Test that all running threads _eventually_ receive CLOCK_PROCESS_CPUTIME_ID
|
|
- * timer signals. This primarily tests that the kernel does not favour any one.
|
|
- */
|
|
-static int check_timer_distribution(void)
|
|
-{
|
|
- int err, i;
|
|
- timer_t id;
|
|
- const int nthreads = 10;
|
|
- pthread_t threads[nthreads];
|
|
struct itimerspec val = {
|
|
.it_value.tv_sec = 0,
|
|
.it_value.tv_nsec = 1000 * 1000,
|
|
.it_interval.tv_sec = 0,
|
|
.it_interval.tv_nsec = 1000 * 1000,
|
|
};
|
|
+ timer_t id;
|
|
|
|
- printf("Check timer_create() per process signal distribution... ");
|
|
- fflush(stdout);
|
|
+ /* 1/10 seconds to ensure the leader sleeps */
|
|
+ usleep(10000);
|
|
|
|
- remain = nthreads + 1; /* worker threads + this thread */
|
|
- signal(SIGALRM, distribution_handler);
|
|
- err = timer_create(CLOCK_PROCESS_CPUTIME_ID, NULL, &id);
|
|
- if (err < 0) {
|
|
- perror("Can't create timer\n");
|
|
- return -1;
|
|
- }
|
|
- err = timer_settime(id, 0, &val, NULL);
|
|
- if (err < 0) {
|
|
- perror("Can't set timer\n");
|
|
- return -1;
|
|
- }
|
|
+ ctd_count = 100;
|
|
+ if (timer_create(CLOCK_PROCESS_CPUTIME_ID, NULL, &id))
|
|
+ return "Can't create timer\n";
|
|
+ if (timer_settime(id, 0, &val, NULL))
|
|
+ return "Can't set timer\n";
|
|
|
|
- for (i = 0; i < nthreads; i++) {
|
|
- if (pthread_create(&threads[i], NULL, distribution_thread, NULL)) {
|
|
- perror("Can't create thread\n");
|
|
- return -1;
|
|
- }
|
|
- }
|
|
+ while (ctd_count > 0 && !ctd_failed)
|
|
+ ;
|
|
|
|
- /* Wait for all threads to receive the signal. */
|
|
- while (__atomic_load_n(&remain, __ATOMIC_RELAXED));
|
|
+ if (timer_delete(id))
|
|
+ return "Can't delete timer\n";
|
|
|
|
- for (i = 0; i < nthreads; i++) {
|
|
- if (pthread_join(threads[i], NULL)) {
|
|
- perror("Can't join thread\n");
|
|
- return -1;
|
|
- }
|
|
- }
|
|
+ return NULL;
|
|
+}
|
|
|
|
- if (timer_delete(id)) {
|
|
- perror("Can't delete timer\n");
|
|
- return -1;
|
|
- }
|
|
+/*
|
|
+ * Test that only the running thread receives the timer signal.
|
|
+ */
|
|
+static int check_timer_distribution(void)
|
|
+{
|
|
+ const char *errmsg;
|
|
+
|
|
+ signal(SIGALRM, ctd_sighandler);
|
|
|
|
- printf("[OK]\n");
|
|
+ errmsg = "Can't create thread\n";
|
|
+ if (pthread_create(&ctd_thread, NULL, ctd_thread_func, NULL))
|
|
+ goto err;
|
|
+
|
|
+ errmsg = "Can't join thread\n";
|
|
+ if (pthread_join(ctd_thread, (void **)&errmsg) || errmsg)
|
|
+ goto err;
|
|
+
|
|
+ if (!ctd_failed)
|
|
+ ksft_test_result_pass("check signal distribution\n");
|
|
+ else if (ksft_min_kernel_version(6, 3))
|
|
+ ksft_test_result_fail("check signal distribution\n");
|
|
+ else
|
|
+ ksft_test_result_skip("check signal distribution (old kernel)\n");
|
|
return 0;
|
|
+err:
|
|
+ ksft_print_msg("%s", errmsg);
|
|
+ return -1;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
- printf("Testing posix timers. False negative may happen on CPU execution \n");
|
|
- printf("based timers if other threads run on the CPU...\n");
|
|
+ ksft_print_header();
|
|
+ ksft_set_plan(6);
|
|
+
|
|
+ ksft_print_msg("Testing posix timers. False negative may happen on CPU execution \n");
|
|
+ ksft_print_msg("based timers if other threads run on the CPU...\n");
|
|
|
|
if (check_itimer(ITIMER_VIRTUAL) < 0)
|
|
return ksft_exit_fail();
|
|
@@ -294,5 +286,5 @@ int main(int argc, char **argv)
|
|
if (check_timer_distribution() < 0)
|
|
return ksft_exit_fail();
|
|
|
|
- return ksft_exit_pass();
|
|
+ ksft_finished();
|
|
}
|