andestech / ICEman

4 stars 1 forks source link

macOS 下需要 sudo 权限才能打开设备 #4

Open wang723-stack opened 11 months ago

wang723-stack commented 11 months ago

macOS 下的 ICEman 需要 sudo 权限才能打开设备(linux 也是),但奇怪的是,在 macOS 下,我自己写的 libusb demo 可以打开 andes 设备,没有使用 sudo 权限,这说明该设备的控制权限是被释放给普通用户的,ICEman 控制设备不也是使用的 libusb 吗,这个问题可能出在哪里

wujiheng commented 11 months ago

好奇你的libusb demo是怎麼寫的? 因為Official OpenOCD也有類似問題! 目前測試是因為libusb_get_device_list不會返回對應的設備清單,會讓開啟的過程中找不到該裝置。 libusb官網是有提到可能會有macOS kernel extension占用裝置(Ref: https://github.com/libusb/libusb/wiki/FAQ#how-can-i-run-libusb-applications-under-mac-os-x-if-there-is-already-a-kernel-extension-installed-for-the-device-and-claim-exclusive-access ),不過目前還沒走到那邊就會因為找不到裝置而結束,詳細原因還未知!

wang723-stack commented 11 months ago

最下面的代码这个 demo 的全部代码,这个 demo 几乎是 libusb api 最基础的用法,其中,get_interface_no 函数是为了找到支持 bulk transfer 的接口,用到了 libusb_get_active_config_descriptor 函数;这个 demo 运行下来似乎表明,对于 libusb,list usb设备,得到一个 handle 并且 claim 一个接口的过程中需要用到的 api 都不存在权限问题并且能正常运行

程序流程大概是:

  1. libusb_get_device_list 列举详细信息,列举详细信息的时候其实已经 open 了一次,因为想获得字符串形式的 Manufacturer 和 Product 字段,然后再 close;
  2. libusb_open 选择想要打开的设备;
  3. libusb_get_active_config_descriptor 得到 usb 配置描述符,并根据 libusb 提供的结构体找到支持 bulk transfer 的接口;
  4. libusb_claim_interface 获取上面得到的支持 bulk transfer 的接口的通信权;

利用 libusb 打开 usb 设备并通信应该都是这个流程?还是说 ICEman 用到了其他的方式。

这是运行截图:

Screenshot 2023-07-31 at 15 32 16

这是用 lldb 运行 ICEman 的截图,这似乎提示在调用 libusb_get_active_config_descriptor 时出现了问题?

Screenshot 2023-07-31 at 15 32 47
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>
// #include <mcheck.h>

typedef struct libusb_interface_descriptor interface_t;
typedef struct libusb_endpoint_descriptor endpoint_t;

libusb_device **devList = NULL;
ssize_t devNum = 0;
libusb_device_handle *devHandle = NULL;

typedef struct {
    uint8_t endpoint_address_in;
    uint8_t endpoint_address_out;
    unsigned int interface_no;
} usb_config;

static int get_interface_no(libusb_device *dev, usb_config *usb) {

    struct libusb_config_descriptor *config;
    const interface_t *interface;
    const endpoint_t *endpoint;
    int count = 0;
    int ret = -1;

    libusb_get_active_config_descriptor(dev, &config);
    interface = config->interface->altsetting;

    for(int i = 0; i < config->interface->num_altsetting; i++) {
        count = 0;
        endpoint = interface[i].endpoint;
        for(int j = 0; j < interface[i].bNumEndpoints; j++) {
            uint8_t type = endpoint[j].bmAttributes;
            // printf("bmAttributes : %02x\n", type);   
            uint8_t direct = endpoint[j].bEndpointAddress;
            // printf("bEndpointAddress : %02x\n", direct);
            if((type & (0x0f >> (4 - 2))) == LIBUSB_TRANSFER_TYPE_BULK) {
                if((direct & (0xf0 << (4 - 1))) == LIBUSB_ENDPOINT_OUT) {
                    usb->endpoint_address_out = endpoint[j].bEndpointAddress;
                    count++;
                    if(count == 2) {
                        usb->interface_no = i;
                        break;
                    }
                }
                else {
                    usb->endpoint_address_in = endpoint[j].bEndpointAddress;
                    count++;
                    if(count == 2) {
                        usb->interface_no = i;
                        break;
                    }
                }
            }
        }

        if(count == 2) {
            usb->interface_no = i;
            ret = 0;
            break;
        }
    }

    libusb_free_config_descriptor(config);

    // printf("out : %02x\nin : %02x\nno : %02x\n", usb->endpoint_address_out, usb->endpoint_address_in, 
    //         usb->interface_no);
    return ret;
}

int main(int argc, char **argv) {

    // setenv("MALLOC_TRACE", "output", 1);  
    // mtrace(); 

    usb_config *usbConfig = (usb_config *)malloc(sizeof(usb_config));

    int ret = libusb_init(NULL);
    if(ret < 0) {
        printf("libusb init fail : ret : %d\n", ret);
        return -1;
    }

    int index = 0;
    int dev_n = 0, bus_n = 0;
    char Manufacturer[128] = {0};
    char Product[128] = {0};
    libusb_device_handle *handle = NULL;
    devNum = libusb_get_device_list(NULL, &devList);

    for(int i = 0; i < devNum; i++) {
        libusb_open(devList[i], &handle);
        struct libusb_device_descriptor desc;
        libusb_get_device_descriptor(devList[i], &desc);

        bus_n = libusb_get_bus_number(devList[i]);
        dev_n = libusb_get_device_address(devList[i]);

        if(handle != NULL) {
            libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, Manufacturer, 128);
            libusb_get_string_descriptor_ascii(handle, desc.iProduct, Product, 128);
        }

        printf("USB Device[%d] : ", i + 1);
        printf("Bus %03d Device %03d: ID %04x:%04x %s %s\n",
                bus_n, dev_n, desc.idVendor, desc.idProduct, Manufacturer, Product);

        if(handle != NULL)
            libusb_close(handle);
    }

    printf("Input the index of usb that you wanna use (0 to exit) : ");
    scanf("%d", &index);
    if(index > (devNum - 1) || index == 0) {
        goto exit;
    }

    ret = libusb_open(devList[index - 1], &devHandle);
    if(ret != 0) {
        printf("libusb open fail : ret : %d\n", ret);
        goto exit;
    }

    if(libusb_kernel_driver_active(devHandle, 0) == 1) {
            // printf("tlk open : Kernel Driver Active\n");
            libusb_detach_kernel_driver(devHandle, 0);
        }
        ret = get_interface_no(devList[index - 1], usbConfig);

        if(ret != 0) {
            printf("get end addr fail\n");
            goto exit;
        }

        ret = libusb_claim_interface(devHandle, usbConfig->interface_no);
        if(ret < 0) {
            printf("Cannot Claim Interface : ret : %d\n", ret);
            libusb_attach_kernel_driver(devHandle, 0);
            libusb_close(devHandle);
            goto exit;
        }

        printf("Get usb handle and Claim Interface!\n");
        libusb_close(devHandle);

exit:
    libusb_free_device_list(devList, 1);
    libusb_exit(NULL);

    // muntrace();
    return 0;
}
wang723-stack commented 11 months ago

@wujiheng 对了,这是在我的 macOS 中运行时 ICEman,不使用 sudo 情况下的 log 信息

Screenshot 2023-07-31 at 15 33 23
wang723-stack commented 10 months ago

@wujiheng 您好,由于 ICEman 的代码量有些大,之前没有接触过 openocd 类似的项目,可以请教一下 ICEman 程序从启动到找到并打开 AICE USB 设备的具体流程吗,就是具体的函数调用顺序;我也需要测试一下为什么 libusb 的接口会工作异常,因为在我本地的其他程序测试中是没有异常的,十分感谢!

wujiheng commented 10 months ago

@YuraGakki 抱歉沒看到這則訊息,如果需要參考FTDI 打開裝置的流程,可以參考這邊 mpsse.c中的open_matching_device(),下面是簡單的流程:

  1. libusb_get_device_list() 建立device清單
  2. 針對清單中的每個裝置做以下事項 2a. libusb_get_device_descriptor(),並比對vid/pid 2b. libusb_open(),嘗試打開該裝置 2c. string_descriptor_equal(),比對serial number/device名稱(非必要) 2d. string_device_address_equal(),比對裝置位置(非必要,這是ICEman提供的功能,用於一台PC連接多個adapter,並開啟指定裝置) 2e. libusb_detach_kernel_driver(),這邊會嘗試退掉kernel module中對於FTDI的支援(ftdi_sio) 2f. libusb_claim_interface(),打開接口
  3. libusb_free_device_list(),釋放上面的清單
  4. libusb_get_config_descriptor(),取得裝置訊息
  5. libusb_control_transfer(),初始化FTDI裝置
  6. 剩下就是一堆檢查,包含檢查FTDI不同型號的裝置/usb packet size/endpoint address(in_ep, out_ep)

大概是這樣

wang723-stack commented 10 months ago

@wujiheng 谢谢,我利用 lldb 也追踪到了这个流程,不过我的测试中,libusb_get_device_list 这个接口是可以正常工作的,程序可以正确的找到指定 vid pid 的设备;不过有一个很明显的事情是,在没有 sudo 权限的情况下,libusb_detach_kernel_driver() 这个接口几乎肯定会返回 LIBUSB_ERROR_ACCESS 的错误码(没有权限),而 ICEman 中对于这个函数没能成功执行的处理是直接 continue,这相当于直接忽略了该设备,哪怕它能够被成功打开,这也就导致没有 sudo 的情况下,几乎所有的设备都被忽略;所以我把这句 continue 注释掉,发现 ICEman 在没有 sudo的情况下也可以正常工作了。具体测试是 ICEman 配合 riscv32-elf-gdb ,可是实现对 mcu 的 remote debug,这应该可以说明 在没有 detach kernel 的情况下 ICEman 和 USB 设备也可以正常通信,这可能是因为我的环境中,除了 ICEman 应该没有别的进程会试图和 Andes 的设备通信。不知道这能否成为你们测试该问题时的参考。

另外,我不清楚 _NDS_V5ONLY 这个宏在哪里被定义,但至少根据 build.sh 编译出的程序中,这个宏应该是存在的。上面说的 continue 的处理,只在 #if _NDS_V5ONLY 成立时是这么执行的,在 #if _NDS_V5ONLY 不成立时,代码中也调用了 libusb_detach_kernel_driver(),但此时即使失败也没有 continue,不知道这么区分是出于哪一种考虑?不过这是否意味着 libusb_detach_kernel_driver() 不是一定要成功执行才可以?如果是的话,看起来去掉 mpsse.c 第 280 行的 continue 似乎能够解决问题。

image

image

wujiheng commented 10 months ago

當初會這樣設計是基於Linux中的ftdi_sio會占用FTDI相關的adapter,所以在程式中先呼叫libusb_detach_kernel_driver將相關的kernel module退掉,當退出失敗後,libusb_claim_interface會直接失敗,不確定是否為Red Hat才有的問題還是其他distribution也會遇到,所以才改flow成這樣的狀況。

不過更奇怪的是,只要跑過一次你提供的demo,ICEman也就不需要sudo,這個現象我到現在還沒有想明白,不是很熟macOS底層的操作