当前位置: 首页 > 工具软件 > X-Pipe > 使用案例 >

goldfish_pipe分析

年健
2023-12-01

goldfish_pipe为在平台总线上的一个misc类型设备

下来说一下goldfish_pipe的工作原理

首先该硬件设备要完成的功能是一个双向管道

从用户层的角度来看设备实现的功能如下
1 打开管道
2 写入数据
3 读取数据

设备支持的寄存器和功能如下
PIPE_REG_COMMAND 用于通知硬件用户要执行的操作类型
PIPE_REG_STATUS 返回用户发起操作的执行结果
PIPE_REG_CHANNEL 通道标识低32位和PIPE_REG_CHANNEL_HIGH寄存器配合使用,用于设置和读取通道的唯一标识,用户每次打开管道设备会创建一个通道,通过将该标示写入寄存器,在设备中断的时候通过该寄存器读取通道标示,找到要操作的通道
PIPE_REG_CHANNEL_HIGH 通道高32位
PIPE_REG_SIZE 读取或者写入数据过程中告知设备用户buffer的大小
PIPE_REG_ADDRESS 读取或者写入的用户buffer物理地址低32位
PIPE_REG_ADDRESS_HIGH 读取或者写入的用户buffer物理地址高32位

用户去读写数据的操作分为两种
1 批量操作
在驱动注册的时候会将参数物理地址通过PIPE_REG_PARAMS_ADDR_LOW|PIPE_REG_PARAMS_ADDR_HEIGH寄存器写到设备中,然后设备就知道该物理地址了,对于批量读写使用access_with_param函数进行

/* A value that will not be set by qemu emulator */
#define INITIAL_BATCH_RESULT (0xdeadbeaf)
static int access_with_param(struct goldfish_pipe_dev *dev, const int cmd,
				unsigned long address, unsigned long avail,
				struct goldfish_pipe *pipe, int *status)
{
	struct access_params *aps = dev->aps;

	if (aps == NULL)
		return -1;

	aps->result = INITIAL_BATCH_RESULT;
	aps->channel = (unsigned long)pipe;
	aps->size = avail;
	aps->address = address;
	aps->cmd = cmd;
	writel(cmd, dev->base + PIPE_REG_ACCESS_PARAMS);
	/*
	 * If the aps->result has not changed, that means
	 * that the batch command failed
	 */
	if (aps->result == INITIAL_BATCH_RESULT)
		return -1;
	*status = aps->result;
	return 0;
}

其实就是通过参数设置要buffer的信息, 然后通过参数的result获取写结果。
2 非批量操作
非批量操作和批量操作的区别在于批量操作通过一个命令将所有信息写入应将,而非批量操作则每次写入一个信息 ,代码如下

           gf_write64((u64)(unsigned long)pipe,
				   dev->base + PIPE_REG_CHANNEL,
				   dev->base + PIPE_REG_CHANNEL_HIGH);
			writel(avail, dev->base + PIPE_REG_SIZE);
			gf_write64(xaddr, dev->base + PIPE_REG_ADDRESS,
				dev->base + PIPE_REG_ADDRESS_HIGH);
			writel(is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER,
				   dev->base + PIPE_REG_COMMAND);
			status = readl(dev->base + PIPE_REG_STATUS);

只有批量操作失败后才会专用非批量操作。

数据读写阻塞的通知操作:
1 在等待队列上等待
CMD_WAKE_ON_WRITE 或者 CMD_WAKE_ON_READ命令通知设备该管道需要接收数据可写/可读通知,然后挂起
2 通过中断通知, 当通道数据满足后发起中断,具体哪种数据满足(可读,可写,或者管道关闭)通过PIPE_REG_WAKES寄存器读取

另外需要注意的是大多数命令操作都需要传递channel id。

具体代码分析如下

我们今天就分析下它是如何工作的

static struct platform_driver goldfish_pipe = {
    .probe = goldfish_pipe_probe,
    .remove = goldfish_pipe_remove,
    .driver = {
        .name = "goldfish_pipe",
        .owner = THIS_MODULE,
        .of_match_table = goldfish_pipe_of_match,
        .acpi_match_table = ACPI_PTR(goldfish_pipe_acpi_match),
    }
};

module_platform_driver(goldfish_pipe);

首先做为platform_bus上的驱动注册到平台总线
我们知道平台总线在添加设备的或者驱动的过程会进行设备驱动匹配,然后调用驱动的probe函数,不知道的可以参考文章platform driver注册过程

我们直接从goldfish_pipe_probe函数进行分析

static int goldfish_pipe_probe(struct platform_device *pdev)
{
    DPRINT("%s: call. platform_device=0x%lx\n", __FUNCTION__, pdev);
    int err;
    struct resource *r;
    struct goldfish_pipe_dev *dev = pipe_dev;

    /* not thread safe, but this should not happen */
    WARN_ON(dev->base != NULL);

    spin_lock_init(&dev->lock);

    r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (r == NULL || resource_size(r) < PAGE_SIZE) {
        dev_err(&pdev->dev, "can't allocate i/o page\n");
        return -EINVAL;
    }
    dev->base = devm_ioremap(&pdev->dev, r->start, PAGE_SIZE);
    if (dev->base == NULL) {
        dev_err(&pdev->dev, "ioremap failed\n");
        return -EINVAL;
    }

    r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (r == NULL) {
        err = -EINVAL;
        goto error;
    }
    dev->irq = r->start;

    err = devm_request_irq(&pdev->dev, dev->irq, goldfish_pipe_interrupt,
                IRQF_SHARED, "goldfish_pipe", dev);
    if (err) {
        dev_err(&pdev->dev, "unable to allocate IRQ\n");
        goto error;
    }

    err = misc_register(&goldfish_pipe_device);
    if (err) {
        dev_err(&pdev->dev, "unable to register device\n");
          goto error;
    }
    setup_access_params_addr(pdev, dev);

    /* Although the pipe device in the classic Android emulator does not
     * recognize the 'version' register, it won't treat this as an error
     * either and will simply return 0, which is fine. */
    dev->version = readl(dev->base + PIPE_REG_VERSION);
    return 0;

error:
    dev->base = NULL;
    return err;
}

函数主要的工作流程如下
1 获取设备iomem信息,然用用ioremap将iomem映射到一块虚拟内存上
2 获取设备irq中断信息,注册设备中断处理函数goldfish_pipe_interrupt
3 注册goldfish_pipe_device
4 setup_access_params_addr 初始化参数访问地址

我们先来分析setup_access_params_addr函数,因为这个函数和设备初始化关系较大
再来分析goldfish_pipe_device,它和用户空间联系比较紧密,最后分析goldfish_pipe_interrupt,这和硬件设备的工作方式联系紧密

/* 0 on success */
static int setup_access_params_addr(struct platform_device *pdev,
                    struct goldfish_pipe_dev *dev)
{
    u64 paddr;
    struct access_params *aps;

    aps = devm_kzalloc(&pdev->dev, sizeof(struct access_params), GFP_KERNEL);
    if (!aps)
        return -1;

    /* FIXME */
    paddr = __pa(aps);
    writel((u32)(paddr >> 32), dev->base + PIPE_REG_PARAMS_ADDR_HIGH);
    writel((u32)paddr, dev->base + PIPE_REG_PARAMS_ADDR_LOW);

    if (valid_batchbuffer_addr(dev, aps)) {
        dev->aps = aps;
        return 0;
    } else {
        devm_kfree(&pdev->dev, aps);
        return -1;
    }
}

函数很简单,使用devm_kzalloc分配一篇内存作为访问参数传递的内存地址,然后写道iomem的PIPE_REG_PARAMS_ADDR_HIGH和PIPE_REG_PARAMS_ADDR_LOW地址,通知硬件设备参数地址。

下面分析goldfish_pipe_device设备提供什么功能,首先它被注册为杂项设备
它的定义如下

static struct miscdevice goldfish_pipe_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "goldfish_pipe",
	.fops = &goldfish_pipe_fops,
};

自动分配次设备号,名称为goldfish_pipe, fops为goldfish_pipe_fops

大家应该知道用户操作设备的回调函数被放在fops结构中,我们来看下它的定义

static const struct file_operations goldfish_pipe_fops = {
	.owner = THIS_MODULE,
	.read = goldfish_pipe_read,
	.write = goldfish_pipe_write,
	.poll = goldfish_pipe_poll,
	.open = goldfish_pipe_open,
	.release = goldfish_pipe_release,
};

首先open函数

/**
 *	goldfish_pipe_open	-	open a channel to the AVD
 *	@inode: inode of device
 *	@file: file struct of opener
 *
 *	Create a new pipe link between the emulator and the use application.
 *	Each new request produces a new pipe.
 *
 *	Note: we use the pipe ID as a mux. All goldfish emulations are 32bit
 *	right now so this is fine. A move to 64bit will need this addressing
 */
static int goldfish_pipe_open(struct inode *inode, struct file *file)
{
	struct goldfish_pipe *pipe;
	struct goldfish_pipe_dev *dev = pipe_dev;
	int32_t status;

	/* Allocate new pipe kernel object */
	pipe = kzalloc(sizeof(*pipe), GFP_KERNEL);
	if (pipe == NULL)
		return -ENOMEM;

	pipe->dev = dev;
	mutex_init(&pipe->lock);
	DPRINT("%s: call. pipe_dev pipe_dev=0x%lx new_pipe_addr=0x%lx file=0x%lx\n", __FUNCTION__, pipe_dev, pipe, file);
	// spin lock init, write head of list, i guess
	init_waitqueue_head(&pipe->wake_queue);

	/*
	 * Now, tell the emulator we're opening a new pipe. We use the
	 * pipe object's address as the channel identifier for simplicity.
	 */

	status = goldfish_cmd_status(pipe, CMD_OPEN);
	if (status < 0) {
		kfree(pipe);
		return status;
	}

	/* All is done, save the pipe into the file's private data field */
	file->private_data = pipe;
	return 0;
}

打开设备的操作主要是创建描述打开实例的数据结构goldfish_pipe, 该结构描述如下

/* This data type models a given pipe instance */
struct goldfish_pipe {
    struct goldfish_pipe_dev *dev;
    struct mutex lock;
    unsigned long flags;
    wait_queue_head_t wake_queue;
};

包含指向goldfish_pipe_dev的指针,和一个mutex互斥锁,一个long类型的flags,和一个等待队列

open函数主要做的工作就是给dev变量指向goldfish_pipe_device,并初始化等待队列,将goldfish_pipe实例放到打开的文件描述结构file变量中,方便后续调用read,write等函数的时候找到对应实例. 另外最关键的地方是它调用一个函数goldfish_cmd_status函数去操作硬件对打开实例做一些处理


static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd)
{
	unsigned long flags;
	u32 status;
	struct goldfish_pipe_dev *dev = pipe->dev;

	spin_lock_irqsave(&dev->lock, flags);
	gf_write64((u64)(unsigned long)pipe, dev->base + PIPE_REG_CHANNEL,
				dev->base + PIPE_REG_CHANNEL_HIGH);
	writel(cmd, dev->base + PIPE_REG_COMMAND);
	status = readl(dev->base + PIPE_REG_STATUS);
	spin_unlock_irqrestore(&dev->lock, flags);
	return status;
}

这个函数主要是把打开的goldfish_pipe地址和 CMD_OPEN命令写出去了,然后从PIPE_REG_STATUS 地址读回结果

这里涉及到了三个寄存器,这里总结下功能
PIPE_REG_CHANNEL ,PIPE_REG_CHANNEL_HIGH 分别表示通道的低32位地址和高32位地址(注意这里为虚拟地址,所以只是启到标示的作用)
PIPE_REG_COMMAND寄存器表示对上面的通道要执行的操作,CMD_OPEN为打开
PIPE_REG_STATUS 用于读取操作的结果

对打开的设备执行读取操作的函数为goldfish_pipe_read

static ssize_t goldfish_pipe_read(struct file *filp, char __user *buffer,
			      size_t bufflen, loff_t *ppos)
{
	return goldfish_pipe_read_write(filp, buffer, bufflen, 0);
}

写操作为

static ssize_t goldfish_pipe_write(struct file *filp,
				const char __user *buffer, size_t bufflen,
				loff_t *ppos)
{
	return goldfish_pipe_read_write(filp, (char __user *)buffer,
								bufflen, 1);
}

二者都调用了goldfish_pipe_read_write函数,区别在于第四个参数,0代表读,1 代表写


static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer,
				       size_t bufflen, int is_write)
{
	unsigned long irq_flags;
	struct goldfish_pipe *pipe = filp->private_data;
	struct goldfish_pipe_dev *dev = pipe->dev;
	unsigned long address, address_end;
	struct page* pages[MAX_PAGES_TO_GRAB] = {};
	int count = 0, ret = -EINVAL;

	/* If the emulator already closed the pipe, no need to go further */
	if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
		return -EIO;

	/* Null reads or writes succeeds */
	if (unlikely(bufflen) == 0)
		return 0;

	/* Check the buffer range for access */
	if (!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ,
			buffer, bufflen))
		return -EFAULT;

	/* Serialize access to the pipe */
	if (mutex_lock_interruptible(&pipe->lock))
		return -ERESTARTSYS;

	address = (unsigned long)(void *)buffer;
	address_end = address + bufflen;

	while (address < address_end) {
		unsigned long page_end = (address & PAGE_MASK) + PAGE_SIZE;
		unsigned long next, avail;
		int status, wakeBit, page_i, num_contiguous_pages;
		long first_page, last_page, requested_pages;
		unsigned long xaddr, xaddr_prev, xaddr_i;

		/*
		 * Attempt to grab multiple physically contiguous pages.
		 */
		first_page = address & PAGE_MASK;
		last_page = (address_end - 1) & PAGE_MASK;
		requested_pages = ((last_page - first_page) >> PAGE_SHIFT) + 1;
		if (requested_pages > MAX_PAGES_TO_GRAB) {
			requested_pages = MAX_PAGES_TO_GRAB;
		}
		ret = get_user_pages_fast(first_page, requested_pages,
				!is_write, pages);

		DPRINT("%s: requested pages: %d %d\n", __FUNCTION__, ret, requested_pages);
		if (ret == 0) {
			DPRINT("%s: error: (requested pages == 0) (wanted %d)\n",
					__FUNCTION__, requested_pages);
			return ret;
		}
		if (ret < 0) {
			DPRINT("%s: (requested pages < 0) %d \n",
				 	__FUNCTION__, requested_pages);
			return ret;
		}

		xaddr = page_to_phys(pages[0]) | (address & ~PAGE_MASK);
		xaddr_prev = xaddr;
		num_contiguous_pages = ret == 0 ? 0 : 1;
		for (page_i = 1; page_i < ret; page_i++) {
			xaddr_i = page_to_phys(pages[page_i]) | (address & ~PAGE_MASK);
			if (xaddr_i == xaddr_prev + PAGE_SIZE) {
				page_end += PAGE_SIZE;
				xaddr_prev = xaddr_i;
				num_contiguous_pages++;
			} else {
				DPRINT("%s: discontinuous page boundary: %d pages instead\n",
						__FUNCTION__, page_i);
				break;
			}
		}
		next = page_end < address_end ? page_end : address_end;
		avail = next - address;

		/* Now, try to transfer the bytes in the current page */
		spin_lock_irqsave(&dev->lock, irq_flags);

		if (access_with_param(dev,
					is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER,
					xaddr, avail, pipe, &status)) {
			gf_write64((u64)(unsigned long)pipe,
				   dev->base + PIPE_REG_CHANNEL,
				   dev->base + PIPE_REG_CHANNEL_HIGH);
			writel(avail, dev->base + PIPE_REG_SIZE);
			gf_write64(xaddr, dev->base + PIPE_REG_ADDRESS,
				dev->base + PIPE_REG_ADDRESS_HIGH);
			writel(is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER,
				   dev->base + PIPE_REG_COMMAND);
			status = readl(dev->base + PIPE_REG_STATUS);
		}
		spin_unlock_irqrestore(&dev->lock, irq_flags);
		goldfish_pipe_read
		for (page_i = 0; page_i < ret; page_i++) {
			if (status > 0 && !is_write &&
				page_i < num_contiguous_pages) {
				set_page_dirty(pages[page_i]);
			}
			put_page(pages[page_i]);
		}

		if (status > 0) { /* Correct transfer */
			count += status;
			address += status;
			continue;
		} else if (status == 0) { /* EOF */
			ret = 0;
			break;
		} else if (status < 0 && count > 0) {
			/*
			 * An error occured and we already transfered
			 * something on one of the previous pages.
			 * Just return what we already copied and log this
			 * err.
			 *
			 * Note: This seems like an incorrect approach but
			 * cannot change it until we check if any user space
			 * ABI relies on this behavior.
			 */
			if (status != PIPE_ERROR_AGAIN)
				pr_info_ratelimited("goldfish_pipe: backend returned error %d on %s\n",
						status, is_write ? "write" : "read");
			ret = 0;
			break;
		}

		/*
		 * If the error is not PIPE_ERROR_AGAIN, or if we are not in
		 * non-blocking mode, just return the error code.
		 */
		if (status != PIPE_ERROR_AGAIN ||
				(filp->f_flags & O_NONBLOCK) != 0) {
			ret = goldfish_pipe_error_convert(status);
			break;
		}

		/*
		 * The backend blocked the read/write, wait until the backend
		 * tells us it's ready to process more data.
		 */
		wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ;
		set_bit(wakeBit, &pipe->flags);

		/* Tell the emulator we're going to wait for a wake event */
		goldfish_cmd(pipe,
				is_write ? CMD_WAKE_ON_WRITE : CMD_WAKE_ON_READ);

		/* Unlock the pipe, then wait for the wake signal */
		mutex_unlock(&pipe->lock);

		while (test_bit(wakeBit, &pipe->flags)) {
			if (wait_event_interruptible(
					pipe->wake_queue,
					!test_bit(wakeBit, &pipe->flags)))
				return -ERESTARTSYS;

			if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
				return -EIO;
		}

		/* Try to re-acquire the lock */
		if (mutex_lock_interruptible(&pipe->lock)) {
			ret = -ERESTARTSYS;
			break;
		}
	}
	mutex_unlock(&pipe->lock);

	if (ret < 0)
		return ret;
	else
		return count;
}

函数实现的功能如下
1 找到用户buf对应的物理页面get_user_pages_fast
2 找到连续的物理页面
3 把连续的物理页面
4 把连续的页面通过写寄存器的方式告诉设备
5 读取操作结果,更新buf地址到尚需要操作的地址
6 重复1-5过程
7 后端堵住了,没有更多数据,或者写操作堵住了之后执行如下操作
(1) 通知硬件 CMD_WAKE_ON_WRITE 或者 CMD_WAKE_ON_READ命令,告诉硬件数据满足的时候weakup当前进程,同时设置pipe->flags为需要等待唤醒状态,当设备数据满足的时候就会通过中断设置该flags。
(2) 进入阻塞队列,等待被唤醒,唤醒后检查设备flags,看数据是否得到满足,满足后执行上层1-5读取数据逻辑

注意进入休眠后会释放pipe->lock.

另外goldfish_pipe也支持poll模型的io

static unsigned int goldfish_pipe_poll(struct file *filp, poll_table *wait)
{
	struct goldfish_pipe *pipe = filp->private_data;
	unsigned int mask = 0;
	int status;

	mutex_lock(&pipe->lock);

	poll_wait(filp, &pipe->wake_queue, wait);

	status = goldfish_cmd_status(pipe, CMD_POLL);

	mutex_unlock(&pipe->lock);

	if (status & PIPE_POLL_IN)
		mask |= POLLIN | POLLRDNORM;

	if (status & PIPE_POLL_OUT)
		mask |= POLLOUT | POLLWRNORM;

	if (status & PIPE_POLL_HUP)
		mask |= POLLHUP;

	if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
		mask |= POLLERR;

	return mask;
}

主要是用CMD_POLL命令获取数据状态,通知事件。

对于用户和设备交互的过程我们看完了,就来看下中断处理的过程

static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id)
{
	struct goldfish_pipe_dev *dev = dev_id;
	unsigned long irq_flags;
	int count = 0;

	/*
	 * We're going to read from the emulator a list of (channel,flags)
	 * pairs corresponding to the wake events that occured on each
	 * blocked pipe (i.e. channel).
	 */
	spin_lock_irqsave(&dev->lock, irq_flags);
	for (;;) {
		/* First read the channel, 0 means the end of the list */
		struct goldfish_pipe *pipe;
		unsigned long wakes;
		unsigned long channel = 0;

#ifdef CONFIG_64BIT
		channel = (u64)readl(dev->base + PIPE_REG_CHANNEL_HIGH) << 32;

		if (channel == 0)
			break;
#endif
		channel |= readl(dev->base + PIPE_REG_CHANNEL);

		if (channel == 0)
			break;

		/* Convert channel to struct pipe pointer + read wake flags */
		wakes = readl(dev->base + PIPE_REG_WAKES);
		pipe  = (struct goldfish_pipe *)(ptrdiff_t)channel;

		/* Did the emulator just closed a pipe? */
		if (wakes & PIPE_WAKE_CLOSED) {
			set_bit(BIT_CLOSED_ON_HOST, &pipe->flags);
			wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE;
		}
		if (wakes & PIPE_WAKE_READ)
			clear_bit(BIT_WAKE_ON_READ, &pipe->flags);
		if (wakes & PIPE_WAKE_WRITE)
			clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags);

		wake_up_interruptible(&pipe->wake_queue);
		count++;
	}
	spin_unlock_irqrestore(&dev->lock, irq_flags);

	return (count == 0) ? IRQ_NONE : IRQ_HANDLED;
}

函数执行流程如下
1 通过PIPE_REG_CHANNEL寄存器获取channel地址
2 读取PIPE_REG_WAKES寄存器,根据寄存器值设置channel对应的pipe的flags变量。包括下面几种状态

  • PIPE_WAKE_CLOSED 标示链接已经关闭,设置flags的标志为BIT_CLOSED_ON_HOST
  • PIPE_WAKE_READ 标志有可读数据,清空BIT_WAKE_ON_READ标志
  • PIPE_WAKE_WRITE 标示数据可写,清空BIT_WAKE_ON_WRITE标志

3 wake_up_interruptible 通知唤醒该pipe的唤醒队列上的进程
4 读取下一个channel状态信息,直到所有channel都读取完成后退出
5 PIPE_REG_WAKES 用于设备数据可读可写或者通道关闭时通知用户,包含三个值

  • PIPE_WAKE_CLOSED 标示通道关闭
  • PIPE_WAKE_READ 标示通道可读
  • PIPE_WAKE_READ 标示通道可写
    6 PIPE_REG_PARAMS_ADDR_LOW 参数的低32位地址
    7 PIPE_REG_PARAMS_ADDR_HIGH 参数的高32位地址
    9 PIPE_REG_ACCESS_PARAMS 执行批量操作的寄存器
    10 PIPE_REG_VERSION 设备版本寄存器
 类似资料: