东风夜放花千树。更吹落,星如雨。

记一次内核之旅--修复板载声卡前置放大器驱动

2023-05-26

问题

笔记本电脑在Linux下一直没有办法正常使用内置音响,初步判断是声卡驱动的问题。

寻找解题

  1. 首先通过Arch Linux Wiki,偶然间看到一个sof-firmware,安装之后可以正常检测到Realtek声卡。耳机孔,蓝牙运作正常。但内置音响声音非常小,且底部低音音响不出声。据此判断,应该是有模拟放大器未激活,导致模拟电路输出功率不足。
  1. alsa-info脚本进行信息搜集:用于解码的声卡应该为Realtek ALC287, 放大器芯片应该为Cirrus cs35l41

  2. 进行广泛搜索后,发现了一篇Gist,作者的amp与我相同。但其解决方案为直接对BIOS进行反编译,然后修改_DSD entry。作者电脑为ASUS的Zenbook UX3402与我不同。且我在对自己的BIOS进行反编译时,发现Lenovo的编码方式无法通过Intel的反编译器完整解析,便作罢。

  3. Arch Linux Bug反馈,求助。按照Debugging Regressions进行相应测试,无果。于是转战kernel bugzilla

  4. 结合对kernel邮件组的潜伏观察,锁定了一个thread。这里有位大佬Cameron Berkenpas自己写了patch并在自己的电脑上进行测试,通过了半年的稳定性测试。而另一位大佬确认了和我相同型号设备也可使用同patch。于是准备自己动手打内核补丁。

思路

  1. 参照经社区验证的补丁,进行自己相应的修改

  2. 下载stable源码,打补丁,编译内核及对应内核模块,安装内核和内核模块,生成initramfs,用dkms安装树外模块(nvidia驱动等),最后更新grub配置,重启,进入新内核。

步骤

// 修改社区的补丁lenovo-7i-gen7-sound-6.2.0-rc3-0.0.5b-002.patch (https://bugzilla.kernel.org/attachment.cgi?id=303828)
// (https://gist.github.com/levihuayuzhang/6137ae4ae46301a355fd37c63e0d876a)
diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c
index f7815ee24f83..93d86c5a9d53 100644
--- a/sound/pci/hda/cs35l41_hda.c
+++ b/sound/pci/hda/cs35l41_hda.c
@@ -1270,6 +1270,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
 	size_t nval;
 	int i, ret;
 
+	printk("CSC3551: probing %s\n", hid);
+
 	adev = acpi_dev_get_first_match_dev(hid, NULL, -1);
 	if (!adev) {
 		dev_err(cs35l41->dev, "Failed to find an ACPI device for %s\n", hid);
@@ -1287,8 +1289,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
 	property = "cirrus,dev-index";
 	ret = device_property_count_u32(physdev, property);
 	if (ret <= 0) {
-		ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid);
-		goto err_put_physdev;
+	    //ret = cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid);
+	    //goto err_put_physdev;
+	    goto no_acpi_dsd;
 	}
 	if (ret > ARRAY_SIZE(values)) {
 		ret = -EINVAL;
@@ -1383,6 +1386,92 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i
 	put_device(physdev);
 
 	return ret;
+
+no_acpi_dsd:
+	/*
+	 * Device CLSA0100 doesn't have _DSD so a gpiod_get by the label reset won't work.
+	 * And devices created by i2c-multi-instantiate don't have their device struct pointing to
+	 * the correct fwnode, so acpi_dev must be used here.
+	 * And devm functions expect that the device requesting the resource has the correct
+	 * fwnode.
+	 */
+
+	printk("CSC3551: no_acpi_dsd: %s\n", hid);
+
+	/* TODO: This is a hack. */
+	if (strncmp(hid, "CSC3551", 7) == 0) {
+	    goto csc3551;
+	}
+
+	if (strncmp(hid, "CLSA0100", 8) != 0)
+		return -EINVAL;
+
+	/* check I2C address to assign the index */
+	cs35l41->index = id == 0x40 ? 0 : 1;
+	cs35l41->hw_cfg.spk_pos = cs35l41->index;
+	cs35l41->channel_index = 0;
+	cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH);
+	cs35l41->hw_cfg.bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH;
+	hw_cfg->gpio2.func = CS35L41_GPIO2_INT_OPEN_DRAIN;
+	hw_cfg->gpio2.valid = true;
+	cs35l41->hw_cfg.valid = true;
+	put_device(physdev);
+
+	return 0;
+
+ csc3551:
+
+	printk("CSC3551: id == 0x%x\n", id);
+
+	// cirrus,dev-index
+	if(id == 0x40)
+	    cs35l41->index = 0;
+	else
+	    cs35l41->index = 1;
+
+	cs35l41->channel_index = 0;
+
+	cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, cs35l41->index, GPIOD_OUT_LOW);
+
+	printk("CS3551: reset_gpio == 0x%x\n", cs35l41->reset_gpio);
+
+	// cirrus,speaker-position
+	if(cs35l41->index == 0)
+	    hw_cfg->spk_pos = 0;
+	else
+	    hw_cfg->spk_pos = 1;
+
+	// cirrus,gpio1-func
+	hw_cfg->gpio1.func = 1;
+        hw_cfg->gpio1.valid = true;
+
+	// cirrus,gpio2-func
+	hw_cfg->gpio2.func = 0x02;
+        hw_cfg->gpio2.valid = true;
+
+	// cirrus,boost-peak-milliamp
+	hw_cfg->bst_ipk = -1;
+
+	// cirrus,boost-ind-nanohenry
+	hw_cfg->bst_ind = -1;
+
+	// cirrus,boost-cap-microfarad
+	hw_cfg->bst_cap = -1;
+
+	cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, cs35l41->index, nval, -1);
+
+        if (hw_cfg->bst_ind > 0 || hw_cfg->bst_cap > 0 || hw_cfg->bst_ipk > 0)
+                hw_cfg->bst_type = CS35L41_INT_BOOST;
+        else
+                hw_cfg->bst_type = CS35L41_EXT_BOOST;
+
+	hw_cfg->valid = true;
+
+	put_device(physdev);
+
+	printk("CSC3551: Done.\n");
+
+	return 0;
 }
 
 int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq,
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index e103bb3693c0..8ec2b0f99d8c 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -9734,6 +9734,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
 	SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS),
 	SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6),
 	SND_PCI_QUIRK(0x17aa, 0x3869, "Lenovo Yoga7 14IAL7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN),
+	SND_PCI_QUIRK(0x17aa, 0x38a9, "Lenovo ThinkBook 16p Gen4 IRH", ALC287_FIXUP_CS35L41_I2C_2),
 	SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
 	SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC),
 	SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI),
# https://wiki.archlinux.org/title/Kernel/Traditional_compilation

# download, verifym and extract source code
# modify the patch, then
make mrproper # clean up

zcat /proc/config.gz  > .config # use running kernel config

patch -p1 < ./lenovo-7i-gen7-sound-6.2.0-rc3-0.0.5b-002.patch # applying patch

make -j$(nproc) # max parallel compile

make modules # compile in tree modules

sudo make INSTALL_MOD_STRIP=1 modules_install # install corresponding module

make bzImage # compile kernle image

# copy image to /boot directory and rename it
cp -v arch/x86/boot/bzImage /boot/vmlinuz-linux6.3.4
# make a new mkinitcpio preset for new kernel
cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux6.3.4.preset

# edit preset of mkinitcpio
vim /etc/mkinitcpio.d/linux6.3.4.preset
-------------------------------------
...
ALL_kver="/boot/vmlinuz-linux6.3.4"
...
default_image="/boot/initramfs-linux6.3.4.img"
...
fallback_image="/boot/initramfs-linux6.3.4-fallback.img"
# generate new initramfs for new kernel
mkinitcpio -p linux6.3.4

# update grub config
sudo grub-mkconfig  -o /boot/grub/grub.cfg 

# out of tree moudle install using dkms
sudo dkms autoinstall -k 6.3.4

# reboot the system and select new kernel at grub entry during booting
reboot now

总结

  1. 这是一个非常危险的patch(仅仅时一个来自社区的workaround而不是fix),未经过Cirrus(硬件制造商)和Lenovo(设备制造商)的官方确认。由社区人员经猜测在另一Levono型号上测试成功,并经猜想应该可以使用到有着相同芯片和类似设计的电脑上。

  2. 声卡电路唯一的保护是BIOS中的ACPI控制程序,但恰恰Lenovo在出厂时未在其中合理配置。(Windows中应该是硬编码在了驱动程序中,而Linux社区则还未得到支持)

后记

  1. 在Cirrus或Lenovo把quirk投入内核树前,如果还想继续使用内置音响,必须自己打patch编译内核。(中低音量低音频音响发声,高音量高音频音响发声,应该是存在分频问题)(这导致音响分频不正确,且无法正确调节音量,至少是Gnome中)

  2. 或者买个usb音响先凑合

  3. 这个问题只能由芯片制造商或者设备制造商解决。因为只有他们才知道芯片的具体设计和相应的接口参数。已在mail list看到Cirrus的工程师了,希望社区能早日得到支持。

更新

2024-04-11:

由笔者报告给Linux mailing list,并与Cirrus的工程师商讨的第一版fix已由Stefan Binding <sbinding@opensource.cirrus.com>提交给Linux Sound team,并预计由V6.8发布。

  1. merge commit: https://github.com/tiwai/sound/commit/37d9d5ff5216df1908a41e6ddd72460c5d938b8a
  2. 邮件历史:https://lore.kernel.org/linux-sound/?q=Huayu+Zhang

2024-04-13:

感谢Stefan大佬的帮助修复了音量控制问题。原因是之前的patch使用了解码器和amp之间的错误配置。

2024-04-18:

提交的修复patch进入Linux Sound子系统https://github.com/tiwai/sound/commit/dca5f4dfa925b51becee65031869e917e6229620

2024-04-22:

patch进入Linux main line tree v6.9-rc5.

2024-04-28:

正式发布(v6.8.8)https://git.kernel.org/stable/ds/v6.8.8/v6.8.7

Ref:

  1. Linux kernel关于Cirrus的文档:https://www.kernel.org/doc/Documentation/devicetree/bindings/sound/cirrus%2Ccs35l41.yaml
  2. alsa-info输出:http://alsa-project.org/db/?f=1ad9f2709886f0ec5d2d87d5d8e59a0ac05384be
  3. Arch Linux kernel编译:https://wiki.archlinux.org/title/Kernel/Traditional_compilation
  4. 参考的fix:https://bugzilla.kernel.org/attachment.cgi?id=303828&action=diff
  5. 发布在Gists的个人补丁与社区讨论:https://gist.github.com/levihuayuzhang/6137ae4ae46301a355fd37c63e0d876a

Classic Sorting Algorithms

LeetCode 912

Complexity info

Bubble sorting

The whole process look like bubbles going up to surface. While sorting (target to acending order), the most right element becomes the largest one.

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        if (nums.size() == 0) {
            return nums;
        }

        // bubble sort
        for (int i = 0; i < nums.size(); i++) {
            // -1 because compares to next element
            // -i because the right most element is the least int after every loop
            for (int j = 0; j < nums.size() - 1 - i; j++) {
                if (nums[j] > nums[j+1]) {
                    int tmp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = tmp;
                }
            }
        }

        return nums;
    }
};

Select Sort

Keep finding the most less element and swap with the most left one in sub array (ascending order).

/*select sort*/
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        if (nums.size() == 0) { // no element for sorting
            return nums;
        }

        for (int i = 0; i < nums.size(); i++) {
            int min = i; // assume the left is the least

            for (int j = i; j < nums.size(); j++) { // start from i not 0
                if (nums[j] < nums[min]) { // keep finding the least one
                    min = j;
                }
            }
            // swap the left most and the min one in the sub array
            int tmp = nums[i];
            nums[i] = nums[min];
            nums[min] = tmp;
        }

        return nums;
    }
};

Insertion Sort

Use double pointer, one points to last element of sorted sub array, one for the comparing element [i+1].

If the element is larger, move backward. If not, insert the current value into the position.

// insertion sort
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        if (nums.size() == 0) {
            return nums;
        }

        int cur; // record current value

        for (int i = 0; i < nums.size() - 1; i++) { // no need to compare last element
            // compare start from nums[i + 1]
            // i stand for the compared sub array
            int cur = nums[i + 1]; 
            int tmpIndex = i; // the right index for current element in sub array
            // move elements backward
            while (tmpIndex >= 0 && cur < nums[tmpIndex]) {
                nums[tmpIndex+1] = nums[tmpIndex];
                tmpIndex--;
            }
            // right position (tmpIndex + 1) in sub array
            nums[tmpIndex + 1] = cur;
        }

        return nums;
    }
};

Quick Sort

classic (one pivot)

  1. select a pivot (randomly or just pick as you like).
  2. Sort the array, put the elemtnts that less than pivot to left and larger to right, then we have two sub array divided by pivot
  3. sort recursively sort the sub array until the array size go to zero
  4. do all above in the original array to save space:
    • move pivot to right first
    • sort element in left to right - 1
    • swap middle first element of larger array (i+1) with pivot (right)
    • then, i+1 is the middle point the 'true' pivot, return it as the divide point for next recursion
// quick sort
class Solution {
    int partition(vector<int>& nums, int left, int right) {
        int index = (right - left) / 2 + left; // middle point as pivot
        swap(nums[right], nums[index]); // move pivot to last position
        
        int pivot =  nums[right];
        int i = left - 1; // i for last element in left sub array (less than pivot)

        // j for index for comparing
        // right - 1 that left most right element (pivot) for swaping at last step
        for (int j = left; j <= right - 1; ++j) { 
            // swap all elements that smaller than pivot to front sub array
            if (nums[j] <= pivot) { 
                swap(nums[++i], nums[j]);
            }
        }
        // for first elements that lager than pivot in right sub array,
        // which should be the position of pivot
        swap(nums[i+1], nums[right]); // move pivot to middle point
        return i+1;
    }


    void quick_sort(vector<int>& nums, int left, int right) {
        if (left < right) {
            int pos = partition(nums, left, right); 
            quick_sort(nums, left, pos - 1);
            quick_sort(nums, pos + 1, right);
        }
    }

public:
    vector<int> sortArray(vector<int>& nums) {
        quick_sort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};

Shell Sort

Best time complexity: \( O(nlogn) \)

Add a gap compare to insertion sort:

  • every time compare the element that has distance of gap
  • reduce the gap (by dividing 2 or using other method) until the gap equal to 1 (when gap euqal to 1, it becomes the classic insertion sort).
  • the introduce of gap make the insertion "faster".
// shell sort
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int cur;
        int gap = nums.size();
        while (gap > 0) { // reduce gap until gap equal to 1
            // perform insertion sort within the gap
            for (int i = gap; i < nums.size(); i++) {
                cur = nums[i];
                int pre = i - gap;
                while (pre >= 0 && cur < nums[pre]) {
                    nums[pre+gap] = nums[pre];
                    pre -= gap;
                }
                nums[pre+gap] = cur;
            }
            gap /= 2; // reduce the gap
        }
        return nums;
    }
};

Merge Sort

Divide and Conquer:

  1. recursively divide the original array into two parts
  2. sort the sub array and merge the result into one
// merge sort
class Solution {
    vector<int> merge(vector<int> left, vector<int> right) {
        vector<int> result;

        for (int index=0, i=0, j=0; index < left.size()+right.size(); index++) {
            if(i >= left.size()) { // left merge finished
                result.push_back(right[j++]);
            } else if(j >= right.size()) { // right merge finished
                result.push_back(left[i++]);
            } else if (left[i] < right[j]) { // put the smaller into the result
                result.push_back(left[i++]);
            } else {
                result.push_back(right[j++]);
            }
        }
        
        return result;
    }

public:
    vector<int> sortArray(vector<int>& nums) {
        if (nums.size() < 2) {
            return nums;
        }

        // keep devide the array into two sub array
        int mid = nums.size() / 2;
        vector<int> left (nums.begin(), nums.begin()+mid);
        vector<int> right (nums.begin()+mid, nums.end());
        
        return merge(sortArray(left), sortArray(right)); // merge the result
    }
};

Heap Sort

Use heap to maintain the array.

Heap: complete binary tree, besides, each root node of sub tree larger or less than its child nodes.

  1. make the array to a heap (if ascending, make a big top one)
  2. swap the root in the heap with the last element in the array (so that the last element is at the target position)
  3. adjust the array again to a heap (so that the root is always the largest or the least value)
  4. keep doing step 2 and 3

The root node always record the largest or least value, so it can be swap to the end of the array to make the array ordered.

// heap sort
/*  for element at k: 
    left node index is 2*k+1
    right node index is 2*(k+1), which is 2*k+2

    last none leaf node index is N/2-1 (N is the length of array)
*/
class Solution {
    // adjust sub trees to make the binary tree to a heap (again)
    // i for the root node of sub tree (last non leaf node)
    void maxHeapify(vector<int>& nums, int i, int len) {
        while ((i<<1)+1 <= len){ // i<<1 is i*2, which is faster
            int lson = (i<<1) +1;
            int rson = (i<<1) +2;
            int large;

            if (lson <= len && nums[lson] > nums[i]) {
                large = lson;
            } else {
                large = i;
            }

            if (rson<= len && nums[rson] > nums[large]) {
                large = rson;
            }
            // the root node of sub tree is not larger than child node, swap,
            // so that it becomes a heap
            if (large != i) {  
                swap(nums[i], nums[large]);
                i = large; // update index in the array
            } else {
                break;
            }
        } 
    }

    // initialize the heap
    void buildMaxHeap(vector<int>& nums, int len) {
        for (int i = len / 2; i >= 0; --i) {
            maxHeapify(nums, i, len); // i is the last non leaf node
        }
    }

    void heapSort(vector<int>& nums) {
        int len = nums.size() - 1;
        buildMaxHeap(nums, len); // initialize the heap first
        
        for (int i = len; i>= 1; --i) {
            // swap the largest nums[0] with the last element (becomes ascending order)
            swap(nums[i], nums[0]); 
            len--; // reduce range
            maxHeapify(nums, 0, len); // keep adjust the heap
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        heapSort(nums);
        return nums;
    }
};

Counting Sort (Bucket Sort)

  1. use an additional array to record the quantity of elements that shows up
  2. use the counting array to put the elements in order

Limites:

  1. only suite for the data set that in a tiny range with repeated value (if large range, space complexity could be bad)
  2. only suite for counting integer (sorting integer)

Bucket Sort

  1. assume the data are evenly separate.
  2. divide the range into multipule bucket (for range of 1 to 100, have buckets 1~10, 11~20...)
  3. sort each buckets

Limites:

  1. need to know the data range so can design the suitable size of bucket, otherwise waste the space and make bucket meaningless.
  2. better combine with other sorting algorithm to divide large task to small one

Radix Sort

  1. find largest element and calculate the highest place
  2. put ones digit into bucket and sort each bucket, then put back
  3. put tenes place into bucket and sort each bucket, then put back...

Integer, radix for 10; binary radix for 2; 8 bits ASCII char radix for 256.

Best practice in most situation

Use quick sort first and when data size becomes small, use non-recursion algorithm like insertion sort, etc.

Heap vs quick: refactor the heap will consume more resource in the real situation.

IndustryBB

test runnable codes

#![allow(unused)]
fn main() {
    println!("Hello World!");

   // You can even hide lines! :D
}

读后感

读《国富论》

社论

信息论与文明