深入分析vfio-user设备实现原理 —— Client侧

news2024/7/4 23:24:01

文章目录

  • 前言
  • 数据结构
    • protocol
      • VFIOUserHdr
      • vfio_user_command
      • VFIOUserHdr flags
    • 设备模型
      • VFIODevice
        • VFIODevIO
        • VFIOContIO
      • VFIOPCIDevice
      • VFIOKernPCIDevice
      • VFIOUserPCIDevice
    • proxy
      • VFIOUserMsg
      • VFIOProxy
  • 流程详解
    • 消息发松流程
    • DMA映射流程

前言

数据结构

protocol

  • VFIO User PCI设备,其PCI配置空间信息及BAR空间的数据都由远端Server提供,可以说VFIO User PCI设备由Client和Server共同模拟,向Guest提供一个完正的PCI设备。Client和Server之间传输的主要信息分为两类,一类是控制面的信息,包括DMA、Region相关配置,另一类是数据面信息,包括Region的读写和DMA的读写。
  • VFIO User协议如下所示:
    请添加图片描述

VFIOUserHdr

  • VFIO User协议的每条消息,无论请求还是回复,都有相同格式的头部信息,其格式如下:
typedef struct {
    uint16_t id;		/* 标识一条请求或者回复消息 */
    uint16_t command;	/* 命令,enum vfio_user_command */
    uint32_t size;		/* 消息长度,包括消息头和消息体整个长度 */
    uint32_t flags;		/* 消息标志,见VFIOUserHdr flags */
    uint32_t error_reply;	/* 包含报错的回复消息 */
} VFIOUserHdr;

vfio_user_command

  • vfio_user_command是整个VFIO规范最核心的数据结构,它定义了Client从Server获取一个PCI设备信息所需要的所有命令,对于kernel驱动,由kernel提供这些信息,对于user驱动,由远端的socket服务进程提供这些信息,如下:
/* VFIOUserHdr commands */
enum vfio_user_command {
    VFIO_USER_VERSION                   = 1,
    VFIO_USER_DMA_MAP                   = 2,
    VFIO_USER_DMA_UNMAP                 = 3,
    VFIO_USER_DEVICE_GET_INFO           = 4,
    VFIO_USER_DEVICE_GET_REGION_INFO    = 5,
    VFIO_USER_DEVICE_GET_REGION_IO_FDS  = 6,
    VFIO_USER_DEVICE_GET_IRQ_INFO       = 7,
    VFIO_USER_DEVICE_SET_IRQS           = 8,
    VFIO_USER_REGION_READ               = 9,
    VFIO_USER_REGION_WRITE              = 10,
    VFIO_USER_DMA_READ                  = 11,
    VFIO_USER_DMA_WRITE                 = 12,
    VFIO_USER_DEVICE_RESET              = 13,
    VFIO_USER_DIRTY_PAGES               = 14,
    VFIO_USER_MAX,
};

VFIOUserHdr flags

  • VFIOUserHdr的头部标志位用于指明一条消息是请求类型的还是回复类型(request/reply),而且可以指示一条请求类型的消息是否需要回复(no_reply),或者一条消息是否携带错误信息,如果error位被置位,表明头部信息中error字段携带有错误信息。
/* VFIOUserHdr flags */
#define VFIO_USER_REQUEST       0x0
#define VFIO_USER_REPLY         0x1
#define VFIO_USER_TYPE          0xF

#define VFIO_USER_NO_REPLY      0x10
#define VFIO_USER_ERROR         0x20

设备模型

  • VFIO-User是VFIO协议的用户态实现(TYPE_VFIO_USER_PCI),它与内核态的实现(TYPE_VFIO_PCI)在协议上相同,因此可以共享协议相关的代码,Qemu在设计时将此部分抽象为TYPE_VFIO_PCI_BASE,作为两者的基类,如下图所示:
    请添加图片描述
  • VFIO-User的设备模型较简单,它在Qemu中是一个简单设备,其热插拔都通过device_add和device_del实现,命令行如下:
-device vfio-user-pci,socket=/var/run/xxx/cntrl,x-enable-migration=off,bus=pci.0,addr=0x9,id=ram-disk3
  • 从上面可以看到,VFIO-User设备的属性相比传统的VFIO-PCI设备(Kernel Driver),增加了一个socket的属性,用于指定Server侧监听的unix socket路径,同时它也是一个PCI设备,因此可以配置PCI设备的BDF号,其它未使能的新增属性如下:
static Property vfio_user_pci_dev_properties[] = {
    DEFINE_PROP_STRING("socket", VFIOUserPCIDevice, sock_name),
    DEFINE_PROP_BOOL("secure-dma", VFIOUserPCIDevice, secure_dma, false),
    DEFINE_PROP_BOOL("x-send-queued", VFIOUserPCIDevice, send_queued, false),
    DEFINE_PROP_BOOL("x-no-posted-writes", VFIOUserPCIDevice, no_post, false),
    DEFINE_PROP_UINT32("x-msg-timeout", VFIOUserPCIDevice, wait_time, 0),
    DEFINE_PROP_END_OF_LIST(),
};

VFIODevice

  • VFIODevice定义了实现VFIO协议所必须的信息,它将kernel和user两种驱动在具体实现上有所不同,这里QEMU通过定义两个接口(Interface)来实现,分别是VFIODevIO(设备接口)和VFIOContIO(容器接口)。
typedef struct VFIODevice {
    QLIST_ENTRY(VFIODevice) next;
    struct VFIOGroup *group;
	......
    bool enable_migration;		/* 是否使能迁移 */
    VFIODeviceOps *ops;			/* VFIO设备相关接口,kernel和user两种驱动有具体的实现,参考VFIODevIO结构体的注释 */
    VFIODevIO *io_ops;			/* VFIO容器相关接口,kernel和user两种驱动有具体的实现,同上 */
	......
    VFIOMigration *migration;	/* 迁移相关信息 */
    Error *migration_blocker;		/* 版本是否支持迁移,如果blocker被设置,标记不支持 */
    OnOffAuto pre_copy_dirty_page_tracking;
    VFIOProxy *proxy;				/* vfio-user驱动将基于socket通信实现的protocol封装到proxy中,这个字段是vfio-user特有的 */
    struct vfio_region_info **regions;	/* vfio设备的region信息 */
	......
} VFIODevice;

VFIODevIO

  • Qemu向后端获取PCI设备信息,通过此接口实现,对于kernel驱动,接口通过ioctl实现,对于user驱动,接口通过socket通信实现。
/*
 * The next 2 ops vectors are how Devices and Containers
 * communicate with the server.  The default option is
 * through ioctl() to the kernel VFIO driver, but vfio-user
 * can use a socket to a remote process.
 */
struct VFIODevIO {
	/* 获取pci设备信息,对应vfio-user VFIO_USER_DEVICE_GET_INFO命令 */
    int (*get_info)(VFIODevice *vdev, struct vfio_device_info *info);
    /* 获取pci region信息,对应vfio-user VFIO_USER_DEVICE_GET_REGION_INFO命令 */
    int (*get_region_info)(VFIODevice *vdev, struct vfio_region_info *info, int *fd);                      
    int (*get_irq_info)(VFIODevice *vdev, struct vfio_irq_info *irq);
    int (*set_irqs)(VFIODevice *vdev, struct vfio_irq_set *irqs);
    int (*region_read)(VFIODevice *vdev, uint8_t nr, off_t off, uint32_t size,
                       void *data);
    int (*region_write)(VFIODevice *vdev, uint8_t nr, off_t off, uint32_t size,
                        void *data, bool post);
};
/* 不同driver实现了不同的VFIODevIO:
 *  kernel-> vfio_dev_io_ioctl
 *  user->vfio_dev_io_sock
 */
#define VDEV_GET_INFO(vdev, info) \
    ((vdev)->io_ops->get_info((vdev), (info)))
#define VDEV_GET_REGION_INFO(vdev, info, fd) \
    ((vdev)->io_ops->get_region_info((vdev), (info), (fd)))
#define VDEV_GET_IRQ_INFO(vdev, irq) \
    ((vdev)->io_ops->get_irq_info((vdev), (irq)))
#define VDEV_SET_IRQS(vdev, irqs) \
    ((vdev)->io_ops->set_irqs((vdev), (irqs)))
#define VDEV_REGION_READ(vdev, nr, off, size, data) \
    ((vdev)->io_ops->region_read((vdev), (nr), (off), (size), (data)))
#define VDEV_REGION_WRITE(vdev, nr, off, size, data, post) \
    ((vdev)->io_ops->region_write((vdev), (nr), (off), (size), (data), (post)))

VFIOContIO

  • 与VFIODevIO类似,容器的IO接口,kernel和user驱动有不同实现。
struct VFIOContIO {
    int (*dma_map)(VFIOContainer *container, MemoryRegion *mr,
                   struct vfio_iommu_type1_dma_map *map);
    int (*dma_unmap)(VFIOContainer *container,
                     struct vfio_iommu_type1_dma_unmap *unmap,
                     struct vfio_bitmap *bitmap);
    int (*dirty_bitmap)(VFIOContainer *container, MemoryRegion *mr,
                        struct vfio_iommu_type1_dirty_bitmap *bitmap,
                        struct vfio_iommu_type1_dirty_bitmap_get *range);
    void (*wait_commit)(VFIOContainer *container);
};
/* 不同driver实现了不同的VFIOContIO:
 *  kernel-> vfio_cont_io_ioctl
 *  user->vfio_cont_io_sock
 */
#define CONT_DMA_MAP(cont, mr, map) \
    ((cont)->io_ops->dma_map((cont), (mr), (map)))
#define CONT_DMA_UNMAP(cont, unmap, bitmap) \
    ((cont)->io_ops->dma_unmap((cont), (unmap), (bitmap)))
#define CONT_DIRTY_BITMAP(cont, mr, bitmap, range) \
    ((cont)->io_ops->dirty_bitmap((cont), (mr), (bitmap), (range)))
#define CONT_WAIT_COMMIT(cont) ((cont)->io_ops->wait_commit(cont))

VFIOPCIDevice

  • VFIOPCIDevice作为vfio-kernel和vfio-user的基类,提供PCI设备的标准信息(比如bar信息),呈现到guest,
typedef struct VFIOPCIDevice {
    PCIDevice pdev;	   			/* PCI设备基类 */
    VFIODevice vbasedev;	/* 包含实现kernel和user驱动所需的VFIO框架必需的信息,TODO */
    VFIOINTx intx;				/* 中断相关信息,TODO */
    unsigned int config_size;	/* 配置空间大小 */
 	......
 	/* 设备bar信息,不同driver,获取bar信息方式不同,kernel通过ioctl实现,user通过socket通信实现 */
    VFIOBAR bars[PCI_NUM_REGIONS - 1]; /* No ROM */
	......
	/* pci设备ID信息 */
    uint32_t vendor_id;
    uint32_t device_id;
    uint32_t sub_vendor_id;
    uint32_t sub_device_id;
	......
}

VFIOKernPCIDevice

  • VFIOKernPCIDevice抽象了vfio-kernel设备,这里直接继承自VFIOPCIDevice且没有多余的字段,kernel驱动相关信息在父类的vbasedev成员中被描述。
typedef struct VFIOKernPCIDevice {
    VFIOPCIDevice device;
}VFIOKernPCIDevice;

VFIOUserPCIDevice

  • VFIOUserPCIDevice抽象了vfio-user设备,与Kernel驱动一样,继承自VFIOPCIDevice,但多了几个vfio-user设备相关属性的字段,user驱动的通过socket实现,在父类的vbasedev成员中,增加了proxy字段描述user驱动。
typedef struct VFIOUserPCIDevice {
    VFIOPCIDevice device;
    char *sock_name;
    bool secure_dma;    /* disable shared mem for DMA */
    bool send_queued;   /* all sends are queued */
    bool no_post;       /* all regions write are sync */
    uint32_t wait_time; /* timeout for message replies */
} VFIOUserPCIDevice;

proxy

  • vfio-user设备引入proxy概念,用于封装基于socket通信的vfio-user协议的实现细节,具体实现包括:
  1. 基于socket的信息传输
  2. 基于IOThread实现的poll逻辑
  3. 基于链表的队列缓存
  • qemu将通过vfio-user协议传输的消息抽象为三类,分别如下:
  1. async message: 异步消息,这一类消息发送完调用者无需等待回复,可以直接返回,承载消息的消息体会被标记为不再使用。
  2. nowait message: 异步消息,这一类消息发送完后调用者可以返回,不用等待,行为和async message相同,但nowait mesage需要获取消息的回复。一旦proxy接收到消息的回复,承载消息的消息体会被标记为不再使用。
  3. wait message: 同步消息,这一类消息发送完后调用者会一直等待直到回复的消息到达,proxy接收到该消息后会将request和reply一起返回给调用者,由调用者标记为不再使用。
  • 三类消息Qemu的comment如下:
/*
 * Messages are queued onto the proxy's outgoing list.
 *
 * It handles 3 types of messages:
 *
 * async messages - replies and posted writes
 *
 * There will be no reply from the server, so message
 * buffers are freed after they're sent.
 *
 * nowait messages - map/unmap during address space transactions
 *
 * These are also sent async, but a reply is expected so that
 * vfio_wait_reqs() can wait for the youngest nowait request.
 * They transition from the outgoing list to the pending list
 * when sent, and are freed when the reply is received.
 *
 * wait messages - all other requests
 *
 * The reply to these messages is waited for by their caller.
 * They also transition from outgoing to pending when sent, but
 * the message buffer is returned to the caller with the reply
 * contents.  The caller is responsible for freeing these messages.
 *
 * As an optimization, if the outgoing list and the socket send
 * buffer are empty, the message is sent inline instead of being
 * added to the outgoing list.  The rest of the transitions are
 * unchanged.
 *
 * returns 0 if the message was sent or queued
 * returns -1 on send error
 */
  • proxy在实现协议时定义了4个链表,每个链表的元素是VFIOUserMsg ,代表一个封装vfio-user协议消息体,4个链表分别是:
  1. free: 空闲链表,存放提前申请好内存的消息体,当proxy要发送消息时,首先从free链表中查询是否有空闲消息体,有则直接取出VFIOUserMsg 用于发送消息,没有则从内存分配。
  2. outgoing: 待发送链表,存放将要通过proxy发送的消息体。
  3. pending: 等待链表,存放等待server回复的已发送的消息体。当消息体为同步等待-wait消息时,需要等待server回复。这一类消息在发送完成后,消息体中的信息不会被释放,而是从outgoing链表中移出,被添加到pending链表中,等到proxy接收到对端reply并通过id成功匹配pending链表中的request消息时,将request和reply一起交由调用者来释放。
  4. incoming: 接收链表,顾名思义,proxy将从socket收到的来自server侧的请求(request)放到incoming链表中。

VFIOUserMsg

  • VFIOUserFDs描述Qemu客户端收发信息
typedef struct {
    int send_fds;
    int recv_fds;
    int *fds;
} VFIOUserFDs;
  • 消息类型的定义,参考前面的解释
enum msg_type {
    VFIO_MSG_NONE,
    VFIO_MSG_ASYNC,	/* 异步请求,消息发送完成后立即返回,消息体回收到free链表中 */
    VFIO_MSG_WAIT,	/* 同步请求,消息发送完成后线程睡眠在信号量上,等待server处理完成 */ 
                    /* reply到达后被唤醒。消息体的在等待发送过程中首先放入outgoing队列,发送完成后放入pending队列 */
    VFIO_MSG_NOWAIT,
    VFIO_MSG_REQ,
};
  • 消息体,消息链表上元素的结构体
typedef struct VFIOUserMsg {
    QTAILQ_ENTRY(VFIOUserMsg) next;
    VFIOUserHdr *hdr;	/* vfio-user消息头 */
    VFIOUserFDs *fds;	/* IO向量包含的文件描述符组 */
    uint32_t rsize;
    /* 当同步等待的消息请求发送后,如果回复的消息到达,通过此id匹配reply和request */
    uint32_t id;
    /* 发送消息后,需要同步等待回复消息的线程,在此信号量上等待回复消息 */
    QemuCond cv;
    bool complete;
    enum msg_type type;	/* 标识消息类型 */
} VFIOUserMsg;

VFIOProxy

typedef struct VFIOProxy {
    QLIST_ENTRY(VFIOProxy) next;
    char *sockname;
    struct QIOChannel *ioc;	/* unix socket对应的IO通道,vfio-user协议基于此收发数据 */
    void (*request)(void *opaque, VFIOUserMsg *msg);
    void *req_arg;
    int flags;
    uint32_t wait_time;
    QemuCond close_cv;
    AioContext *ctx;	/* IOThread上下文 */
    QEMUBH *req_bh;

    /*
     * above only changed when BQL is held
     * below are protected by per-proxy lock
     */
    QemuMutex lock;	/* 消息发送和信号量更新的同步锁 */
    VFIOUserMsgQ free;	/* 空闲可用的消息链表 */
    VFIOUserMsgQ pending;	/* nowait message在发送完成后链入的链表*/
    VFIOUserMsgQ incoming;	/* client侧接收的来自server侧的requst */
    VFIOUserMsgQ outgoing;	/* 即将发送出去的message链表 */
    VFIOUserMsg *last_nowait;
    VFIOUserMsg *part_recv;
    size_t recv_left;
    enum proxy_state state;
} VFIOProxy;

流程详解

消息发松流程

请添加图片描述

  • TODO

DMA映射流程

  • TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/669066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「解析」YOLOv4模型小结

Paper Yolo v4: https://arxiv.org/abs/2004.10934 Scaled-YOLOv4: Scaling Cross Stage Partial Network Source code:https://github.com/AlexeyAB/darknet Bag of Freebies(BoF) 只增加训练成本,但是能显著提高精度,并不影响推理速度;数据…

Kubernetes使用Istio

Kubernetes使用Istio 1、基本概念 1.1、流量方向 南北流量(NORTH-SOURTH-TRAFFIC):客户端到服务器之间通信的流量 东西流量(EAST-WEST-TRAFFIC):指的是服务器和服务器之间的流量 1.2、Service Mesh 2、安装Istio 2.1、下载 …

【编译、链接、装载九】静态链接

【编译和链接九】静态链接 一、demo二、空间与地址分配1、相似段合并 三、即虚拟地址VMA(Virtual Memory Address)四、重定位1、add调用2、printf调用——同add2、shared 五、重定位表六、符号解析七、c相关问题1、重复代码消除2、全局构造与析构3、C与A…

从创建到维护:掌握package.json的最佳实践

文章目录 I. 介绍什么是package.jsonpackage.json的作用npm与package.json的关系 II. 创建package.jsonnpm init自动生成package.jsonpackage.json各个字段的含义 III. dependencies和devDependenciesdependencies和devDependencies的区别安装依赖包安装依赖包的版本更新依赖包…

Flink 学习十 FlinkSQL

Flink 学习十 Flink SQL 1. FlinkSQL 基础概念 flink sql 基于flink core ,使用sql 语义方便快捷的进行结构化数据处理的上层库; 类似理解sparksql 和sparkcore , hive和mapreduce 1.1 工作流程 整体架构和工作流程 数据流,绑定元数据 schema ,注册成catalog 中的表 table …

【C语言复习】第七篇、关于C语言关键字的知识

目录 第一部分、常见关键字 1、数据类型关键字 2、流程控制类关键字 第二部分、常用的关键字 1、typedef(类型重定义/类型重命名) 2、static(易混淆const) 2.1、static修饰局部变量 2.2、static修饰全局变量 2.3、static修饰…

9.创建provider实例

创建provider网络 controller节点 创建一个provider 网络,网络类型为 external 对于 provider 网络来说,实例通过 2 层(桥接网络)连接到提供商网 络。 参数说明: --share: 允许所有项目都可以使用该网络…

深度学习-【图像分类】学习笔记8 ShuffleNet

文章目录 8.1 ShuffleNet v1 v2理论讲解ShuffleNet v1ShuffleNet v2 8.2 使用Pytorch搭建ShuffleNet 8.1 ShuffleNet v1 v2理论讲解 ShuffleNet v1 论文链接:https://readpaper.com/paper/2963125010 Channel shuffle 相关链接:深度学习-【图像分类】…

车载以太网 - 传输层 - TCP/IP

目录 一、传输层基础介绍 传输层主要包括两种协议 传输层端口号 二、UDP通信 UDP协议介绍 UDP 通信特点: UDP Segment结构 UDP通信过程 三、TCP通信 TCP通信特点: TCP Segment结构 一、传输层基础介绍 传输层的寻址方式:端口号 包括传输层的寻址方式&…

几个SQL的高级写法

一、ORDER BY FLELD() 自定义排序逻辑 MySql 中的排序 ORDER BY 除了可以用 ASC 和 DESC,还可以通过 ORDER BY FIELD(str,str1,...) 自定义字符串/数字来实现排序。这里用 order_diy 表举例,结构以及表数据展示: ORDER BY FIELD(str,str1,..…

chatgpt赋能python:Python支持跨平台软件开发

Python支持跨平台软件开发 作为一种高级编程语言,Python 以其丰富的库和跨平台支持而备受开发人员欢迎。Python 通过将应用程序的可移植性最大化,使得开发人员可以轻松地在不同的操作系统平台上构建和部署软件。 跨平台支持 Python 支持各种不同的操作…

三子棋都玩过吧,那C语言现造一个呢???

目录 前言 三子棋简介 棋盘介绍 规则介绍 程序设计 基本流程 游戏逻辑 菜单界面打印 创建棋盘并初始化 打印棋盘 玩家落子 电脑落子 判断胜负 1.判定是否和棋 2.判定胜负 代码总汇 ✅Game.h 头文件 ✅Game.c ✅Test.c 前言 🥰想必各位大佬们上学的…

编译安装以及升级Nginx

目录 一、前言 1、简介 2、 Nginx模块 3、与Apache的差异 4、优点 二、编译安装 1、关闭防火墙 2、安装依赖包 3、创建运行用户与组 4、编译安装 5、检测配置文件是否正确 6、添加系统服务 三、版本升级 四、总结 一、前言 1、简介 Nginx是一个高性能的HTTP和反…

chatgpt赋能python:Python散点图连线——一种美妙的可视化方法

Python散点图连线——一种美妙的可视化方法 散点图连线是一种常用的可视化方法,可以展示不同维度之间的关系和趋势。在Python中,我们可以使用多种库来绘制散点图连线,例如matplotlib、seaborn和plotly等。本文将介绍如何使用matplotlib和plo…

数学公式库mathjs 安装使用教程

Math.js 是个JavaScript 和 Node.js 的扩展数学库。它包括了灵活的表达式解析器,提供数字,大数值,复杂数值,单位,矩阵等等集成的解决方案。Math.js 很强大又易于使用。 特性 支持数值,大数值&#xff0c…

医疗IT绝缘监测应用

近年来,随着医疗技术的迅猛发展,电子电气相关医疗设备在医院中的应用越来越广泛,各类疾病治疗也越来越依赖医疗电气设备 。人们对医院医疗环境提出更高要求,传统的建筑、电气等设计已难以充分保障其人身安全。患者及医务人员已深陷…

每日学术速递6.10

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Unifying (Machine) Vision via Counterfactual World Modeling 标题:通过反事实世界建模统一(机器)视觉 作者:Daniel M. Bear, K…

LFS11.3在VMware安装后需要做的准备

参考lfs 11.3和Blfs 11.3 先简单罗列一下要做的步骤,后续有机会再补充一下细节,遇到问题欢迎读者留言。 1、配置vmware中的网络连接 使用vmware net8 net模式,选用VMnet 配置网络连接/etc/sysconfig/ 目录下ifconfig.*** (***为…

RK3588平台开发系列讲解(AI 篇)什么是RKNPU

文章目录 一、RKNPU的发展历史二、RKNPU单核架构三、RKNPU性能计算四、RKNPU应用场景沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解什么是RKNPU。 一、RKNPU的发展历史 二、RKNPU单核架构 三、RKNPU性能计算 NPU算力是指每秒可以处理的运算次数,通常…

基于Hata模型的BPSK调制信号小区覆盖模拟matlab完整程序分享

基于Hata信道模型的BPSK调制信号小区覆盖模拟matlab仿真,对比VoIP, Live Video,FTP/Email 完整程序: clc; clear; close all; warning off; addpath(genpath(pwd)); % Random bits are generated here. bits = randi([0, 1], [50,1]); M = 2; t = 1:1:50; trans = pskmod(bi…