From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-01-24 16:55:42
This series implements a driver part of the virtio sound device
specification v8 [1].
The driver supports PCM playback and capture substreams, jack and
channel map controls. A message-based transport is used to write/read
PCM frames to/from a device.
The series is based (and was actually tested) on Linus's master
branch [2], on top of
commit 1e2a199f6ccd ("Merge tag 'spi-fix-v5.11-rc4' of ...")
As a device part was used OpenSynergy proprietary implementation.
Any comments are very welcome.
v1->v2 changes:
1. For some reason, in the previous patch series, several patches were
squashed. Fixed this issue to make the review easier.
2. Added mst@redhat.com to the MAINTAINERS.
3. When creating virtqueues, now only the event virtqueue is disabled.
It's enabled only after successful initialization of the device.
4. Added additional comments to the reset worker function:
[2/9] virtio_card.c:virtsnd_reset_fn()
5. Added check that VIRTIO_F_VERSION_1 feature bit is set.
6. Added additional comments to the device removing function:
[2/9] virtio_card.c:virtsnd_remove()
7. Added additional comments to the tx/rx interrupt handler:
[5/9] virtio_pcm_msg.c:virtsnd_pcm_msg_complete()
8. Added additional comments to substream release wait function.
[6/9] virtio_pcm_ops.c:virtsnd_pcm_released()
[1] https://lists.oasis-open.org/archives/virtio-dev/202003/msg00185.html
[2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Anton Yakovlev (9):
uapi: virtio_ids: add a sound device type ID from OASIS spec
ALSA: virtio: add virtio sound driver
ALSA: virtio: handling control messages
ALSA: virtio: build PCM devices and substream hardware descriptors
ALSA: virtio: handling control and I/O messages for the PCM device
ALSA: virtio: PCM substream operators
ALSA: virtio: introduce jack support
ALSA: virtio: introduce PCM channel map support
ALSA: virtio: introduce device suspend/resume support
MAINTAINERS | 9 +
include/uapi/linux/virtio_ids.h | 1 +
include/uapi/linux/virtio_snd.h | 361 ++++++++++++++++++++
sound/Kconfig | 2 +
sound/Makefile | 3 +-
sound/virtio/Kconfig | 10 +
sound/virtio/Makefile | 13 +
sound/virtio/virtio_card.c | 577 +++++++++++++++++++++++++++++++
sound/virtio/virtio_card.h | 121 +++++++
sound/virtio/virtio_chmap.c | 237 +++++++++++++
sound/virtio/virtio_ctl_msg.c | 293 ++++++++++++++++
sound/virtio/virtio_ctl_msg.h | 122 +++++++
sound/virtio/virtio_jack.c | 255 ++++++++++++++
sound/virtio/virtio_pcm.c | 582 ++++++++++++++++++++++++++++++++
sound/virtio/virtio_pcm.h | 132 ++++++++
sound/virtio/virtio_pcm_msg.c | 325 ++++++++++++++++++
sound/virtio/virtio_pcm_ops.c | 528 +++++++++++++++++++++++++++++
17 files changed, 3570 insertions(+), 1 deletion(-)
create mode 100644 include/uapi/linux/virtio_snd.h
create mode 100644 sound/virtio/Kconfig
create mode 100644 sound/virtio/Makefile
create mode 100644 sound/virtio/virtio_card.c
create mode 100644 sound/virtio/virtio_card.h
create mode 100644 sound/virtio/virtio_chmap.c
create mode 100644 sound/virtio/virtio_ctl_msg.c
create mode 100644 sound/virtio/virtio_ctl_msg.h
create mode 100644 sound/virtio/virtio_jack.c
create mode 100644 sound/virtio/virtio_pcm.c
create mode 100644 sound/virtio/virtio_pcm.h
create mode 100644 sound/virtio/virtio_pcm_msg.c
create mode 100644 sound/virtio/virtio_pcm_ops.c
--
2.30.0
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-01-24 16:55:25
The OASIS virtio spec defines a sound device type ID that is not
present in the header yet.
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
include/uapi/linux/virtio_ids.h | 1 +
1 file changed, 1 insertion(+)
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-01-24 16:55:56
The control queue can be used by different parts of the driver to send
commands to the device. Control messages can be either synchronous or
asynchronous. The lifetime of a message is controlled by a reference
count.
Introduce a module parameter to set the message completion timeout:
msg_timeout_ms [=1000]
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 3 +-
sound/virtio/virtio_card.c | 20 +++
sound/virtio/virtio_card.h | 7 +
sound/virtio/virtio_ctl_msg.c | 293 ++++++++++++++++++++++++++++++++++
sound/virtio/virtio_ctl_msg.h | 122 ++++++++++++++
5 files changed, 444 insertions(+), 1 deletion(-)
create mode 100644 sound/virtio/virtio_ctl_msg.c
create mode 100644 sound/virtio/virtio_ctl_msg.h
@@ -24,6 +24,10 @@#include"virtio_card.h"+intmsg_timeout_ms=MSEC_PER_SEC;+module_param(msg_timeout_ms,int,0644);+MODULE_PARM_DESC(msg_timeout_ms,"Message completion timeout in milliseconds");+staticvoidvirtsnd_remove(structvirtio_device*vdev);/**
@@ -125,6 +129,7 @@ static int virtsnd_find_vqs(struct virtio_snd *snd)unsignedintn=0;intrc;+callbacks[VIRTIO_SND_VQ_CONTROL]=virtsnd_ctl_notify_cb;callbacks[VIRTIO_SND_VQ_EVENT]=virtsnd_event_notify_cb;rc=virtio_find_vqs(vdev,VIRTIO_SND_VQ_MAX,vqs,callbacks,names,
@@ -193,6 +198,15 @@ static void virtsnd_disable_vqs(struct virtio_snd *snd)if(queue->vqueue)virtqueue_disable_cb(queue->vqueue);queue->vqueue=NULL;+/* Cancel all pending requests for the control queue */+if(i==VIRTIO_SND_VQ_CONTROL){+structvirtio_snd_msg*msg;+structvirtio_snd_msg*next;++list_for_each_entry_safe(msg,next,&snd->ctl_msgs,+list)+virtsnd_ctl_msg_complete(snd,msg);+}spin_unlock_irqrestore(&queue->lock,flags);}
@@ -283,6 +297,11 @@ static int virtsnd_validate(struct virtio_device *vdev)return-EINVAL;}+if(!msg_timeout_ms){+dev_err(&vdev->dev,"msg_timeout_ms value cannot be zero\n");+return-EINVAL;+}+return0;}
@@ -305,6 +324,7 @@ static int virtsnd_probe(struct virtio_device *vdev)snd->vdev=vdev;INIT_WORK(&snd->reset_work,virtsnd_reset_fn);+INIT_LIST_HEAD(&snd->ctl_msgs);vdev->priv=snd;
@@ -0,0 +1,293 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Soundcarddriverforvirtio+*Copyright(C)2020OpenSynergyGmbH+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,see<http://www.gnu.org/licenses/>.+*/+#include<linux/moduleparam.h>+#include<linux/virtio_config.h>++#include"virtio_card.h"+#include"virtio_ctl_msg.h"++/**+*virtsnd_ctl_msg_alloc_ext()-Allocateandinitializeacontrolmessage.+*@vdev:VirtIOparentdevice.+*@request_size:Sizeofrequestheader(pointedtobysg_requestfield).+*@response_size:Sizeofresponseheader(pointedtobysg_responsefield).+*@sgs:Additionaldatatoattachtothemessage(maybeNULL).+*@out_sgs:Numberofscattergatherelementstoattachtotherequestheader.+*@in_sgs:Numberofscattergatherelementstoattachtotheresponseheader.+*@gfp:Kernelflagsformemoryallocation.+*+*Themessagewillbeautomaticallyfreedwhentheref_countvalueis0.+*+*Context:Anycontext.Maysleepif@gfpflagspermit.+*Return:Allocatedmessageonsuccess,ERR_PTR(-errno)onfailure.+*/+structvirtio_snd_msg*virtsnd_ctl_msg_alloc_ext(structvirtio_device*vdev,+size_trequest_size,+size_tresponse_size,+structscatterlist*sgs,+unsignedintout_sgs,+unsignedintin_sgs,gfp_tgfp)+{+structvirtio_snd_msg*msg;+size_tmsg_size=+sizeof(*msg)+(1+out_sgs+1+in_sgs)*sizeof(*msg->sgs);+unsignedinti;++msg=devm_kzalloc(&vdev->dev,msg_size+request_size+response_size,+gfp);+if(!msg)+returnERR_PTR(-ENOMEM);++sg_init_one(&msg->sg_request,(u8*)msg+msg_size,request_size);+sg_init_one(&msg->sg_response,(u8*)msg+msg_size+request_size,+response_size);++INIT_LIST_HEAD(&msg->list);+init_completion(&msg->notify);+atomic_set(&msg->ref_count,1);++msg->sgs[msg->out_sgs++]=&msg->sg_request;+if(sgs)+for(i=0;i<out_sgs;++i)+msg->sgs[msg->out_sgs++]=&sgs[i];++msg->sgs[msg->out_sgs+msg->in_sgs++]=&msg->sg_response;+if(sgs)+for(i=out_sgs;i<out_sgs+in_sgs;++i)+msg->sgs[msg->out_sgs+msg->in_sgs++]=&sgs[i];++returnmsg;+}++/**+*virtsnd_ctl_msg_send()-Sendan(asynchronous)controlmessage.+*@snd:VirtIOsounddevice.+*@msg:Controlmessage.+*+*Ifamessageisfailedtobeenqueued,itwillbedeleted.Ifmessagecontent+*isstillneeded,thecallermustadditionallytovirtsnd_ctl_msg_ref/unref()+*it.+*+*Context:Anycontext.Takesandreleasesthecontrolqueuespinlock.+*Return:0onsuccess,-errnoonfailure.+*/+intvirtsnd_ctl_msg_send(structvirtio_snd*snd,structvirtio_snd_msg*msg)+{+structvirtio_device*vdev=snd->vdev;+structvirtio_snd_queue*queue=virtsnd_control_queue(snd);+structvirtio_snd_hdr*response=sg_virt(&msg->sg_response);+boolnotify=false;+unsignedlongflags;+intrc=-EIO;++/* Set the default status in case the message was not sent or was+*canceled.+*/+response->code=cpu_to_virtio32(vdev,VIRTIO_SND_S_IO_ERR);++spin_lock_irqsave(&queue->lock,flags);+if(queue->vqueue){+rc=virtqueue_add_sgs(queue->vqueue,msg->sgs,msg->out_sgs,+msg->in_sgs,msg,GFP_ATOMIC);+if(!rc){+notify=virtqueue_kick_prepare(queue->vqueue);+list_add_tail(&msg->list,&snd->ctl_msgs);+}+}+spin_unlock_irqrestore(&queue->lock,flags);++if(!rc){+if(!notify||virtqueue_notify(queue->vqueue))+return0;++spin_lock_irqsave(&queue->lock,flags);+list_del(&msg->list);+spin_unlock_irqrestore(&queue->lock,flags);+}++virtsnd_ctl_msg_unref(snd->vdev,msg);++return-EIO;+}++/**+*virtsnd_ctl_msg_send_sync()-Senda(synchronous)controlmessage.+*@snd:VirtIOsounddevice.+*@msg:Controlmessage.+*+*Afterreturningfromthisfunction,themessagewillbedeleted.Ifmessage+*contentisstillneeded,thecallermustadditionallyto+*virtsnd_ctl_msg_ref/unref()it.+*+*Themsg_timeout_msmoduleparameterdefinesthemessagecompletiontimeout.+*Ifthemessageisnotcompletedwithinthistime,thefunctionwillreturnan+*error.+*+*Context:Anycontext.Takesandreleasesthecontrolqueuespinlock.+*Return:0onsuccess,-errnoonfailure.+*+*Thereturnvalueisamessagestatuscode(VIRTIO_SND_S_XXX)convertedtoan+*appropriate-errnovalue.+*/+intvirtsnd_ctl_msg_send_sync(structvirtio_snd*snd,+structvirtio_snd_msg*msg)+{+structvirtio_device*vdev=snd->vdev;+unsignedintjs=msecs_to_jiffies(msg_timeout_ms);+structvirtio_snd_hdr*response;+intrc;++virtsnd_ctl_msg_ref(vdev,msg);++rc=virtsnd_ctl_msg_send(snd,msg);+if(rc)+gotoon_failure;++rc=wait_for_completion_interruptible_timeout(&msg->notify,js);+if(rc<=0){+if(!rc){+structvirtio_snd_hdr*request=+sg_virt(&msg->sg_request);++dev_err(&vdev->dev,+"control message (0x%08x) timeout\n",+le32_to_cpu(request->code));+rc=-EIO;+}++gotoon_failure;+}++response=sg_virt(&msg->sg_response);++switch(le32_to_cpu(response->code)){+caseVIRTIO_SND_S_OK:+rc=0;+break;+caseVIRTIO_SND_S_BAD_MSG:+rc=-EINVAL;+break;+caseVIRTIO_SND_S_NOT_SUPP:+rc=-EOPNOTSUPP;+break;+caseVIRTIO_SND_S_IO_ERR:+rc=-EIO;+break;+default:+rc=-EPERM;+break;+}++on_failure:+virtsnd_ctl_msg_unref(vdev,msg);++returnrc;+}++/**+*virtsnd_ctl_msg_complete()-Completeacontrolmessage.+*@snd:VirtIOsounddevice.+*@msg:Controlmessage.+*+*Context:Anycontext.+*/+voidvirtsnd_ctl_msg_complete(structvirtio_snd*snd,+structvirtio_snd_msg*msg)+{+list_del(&msg->list);+complete(&msg->notify);++virtsnd_ctl_msg_unref(snd->vdev,msg);+}++/**+*virtsnd_ctl_query_info()-Querytheitemconfigurationfromthedevice.+*@snd:VirtIOsounddevice.+*@command:Controlrequestcode(VIRTIO_SND_R_XXX_INFO).+*@start_id:Itemstartidentifier.+*@count:Itemcounttoquery.+*@size:Iteminformationsizeinbytes.+*@info:Bufferforstoringiteminformation.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+intvirtsnd_ctl_query_info(structvirtio_snd*snd,intcommand,intstart_id,+intcount,size_tsize,void*info)+{+structvirtio_device*vdev=snd->vdev;+structvirtio_snd_msg*msg;+structvirtio_snd_query_info*query;+structscatterlistsg;++sg_init_one(&sg,info,count*size);++msg=virtsnd_ctl_msg_alloc_ext(vdev,sizeof(*query),+sizeof(structvirtio_snd_hdr),&sg,0,+1,GFP_KERNEL);+if(IS_ERR(msg))+returnPTR_ERR(msg);++query=sg_virt(&msg->sg_request);+query->hdr.code=cpu_to_virtio32(vdev,command);+query->start_id=cpu_to_virtio32(vdev,start_id);+query->count=cpu_to_virtio32(vdev,count);+query->size=cpu_to_virtio32(vdev,size);++returnvirtsnd_ctl_msg_send_sync(snd,msg);+}++/**+*virtsnd_ctl_notify_cb()-Processallcompletedcontrolmessages.+*@vqueue:Underlyingcontrolvirtqueue.+*+*Thiscallbackfunctioniscalleduponavringinterruptrequestfromthe+*device.+*+*Context:Interruptcontext.Takesandreleasesthecontrolqueuespinlock.+*/+voidvirtsnd_ctl_notify_cb(structvirtqueue*vqueue)+{+structvirtio_snd*snd=vqueue->vdev->priv;+structvirtio_snd_queue*queue=virtsnd_control_queue(snd);+unsignedlongflags;++spin_lock_irqsave(&queue->lock,flags);+while(queue->vqueue){+virtqueue_disable_cb(queue->vqueue);++for(;;){+structvirtio_snd_msg*msg;+u32length;++msg=virtqueue_get_buf(queue->vqueue,&length);+if(!msg)+break;++virtsnd_ctl_msg_complete(snd,msg);+}++if(unlikely(virtqueue_is_broken(queue->vqueue)))+break;++if(virtqueue_enable_cb(queue->vqueue))+break;+}+spin_unlock_irqrestore(&queue->lock,flags);+}
@@ -0,0 +1,361 @@+/* SPDX-License-Identifier: BSD-3-Clause */+/*+*Copyright(C)2020OpenSynergyGmbH+*+*ThisheaderisBSDlicensedsoanyonecanusethedefinitionsto+*implementcompatibledrivers/servers.+*+*Redistributionanduseinsourceandbinaryforms,withorwithout+*modification,arepermittedprovidedthatthefollowingconditions+*aremet:+*1.Redistributionsofsourcecodemustretaintheabovecopyright+*notice,thislistofconditionsandthefollowingdisclaimer.+*2.Redistributionsinbinaryformmustreproducetheabovecopyright+*notice,thislistofconditionsandthefollowingdisclaimerinthe+*documentationand/orothermaterialsprovidedwiththedistribution.+*3.NeitherthenameofOpenSynergyGmbHnorthenamesofitscontributors+*maybeusedtoendorseorpromoteproductsderivedfromthissoftware+*withoutspecificpriorwrittenpermission.+*THISSOFTWAREISPROVIDEDBYTHECOPYRIGHTHOLDERSANDCONTRIBUTORS+*``ASIS''ANDANYEXPRESSORIMPLIEDWARRANTIES,INCLUDING,BUTNOT+*LIMITEDTO,THEIMPLIEDWARRANTIESOFMERCHANTABILITYANDFITNESS+*FORAPARTICULARPURPOSEAREDISCLAIMED.INNOEVENTSHALLIBMOR+*CONTRIBUTORSBELIABLEFORANYDIRECT,INDIRECT,INCIDENTAL,+*SPECIAL,EXEMPLARY,ORCONSEQUENTIALDAMAGES(INCLUDING,BUTNOT+*LIMITEDTO,PROCUREMENTOFSUBSTITUTEGOODSORSERVICES;LOSSOF+*USE,DATA,ORPROFITS;ORBUSINESSINTERRUPTION)HOWEVERCAUSEDAND+*ONANYTHEORYOFLIABILITY,WHETHERINCONTRACT,STRICTLIABILITY,+*ORTORT(INCLUDINGNEGLIGENCEOROTHERWISE)ARISINGINANYWAYOUT+*OFTHEUSEOFTHISSOFTWARE,EVENIFADVISEDOFTHEPOSSIBILITYOF+*SUCHDAMAGE.+*/+#ifndef VIRTIO_SND_IF_H+#define VIRTIO_SND_IF_H++#include<linux/virtio_types.h>++/*******************************************************************************+*CONFIGURATIONSPACE+*/+structvirtio_snd_config{+/* # of available physical jacks */+__le32jacks;+/* # of available PCM streams */+__le32streams;+/* # of available channel maps */+__le32chmaps;+};++enum{+/* device virtqueue indexes */+VIRTIO_SND_VQ_CONTROL=0,+VIRTIO_SND_VQ_EVENT,+VIRTIO_SND_VQ_TX,+VIRTIO_SND_VQ_RX,+/* # of device virtqueues */+VIRTIO_SND_VQ_MAX+};++/*******************************************************************************+*COMMONDEFINITIONS+*/++/* supported dataflow directions */+enum{+VIRTIO_SND_D_OUTPUT=0,+VIRTIO_SND_D_INPUT+};++enum{+/* jack control request types */+VIRTIO_SND_R_JACK_INFO=1,+VIRTIO_SND_R_JACK_REMAP,++/* PCM control request types */+VIRTIO_SND_R_PCM_INFO=0x0100,+VIRTIO_SND_R_PCM_SET_PARAMS,+VIRTIO_SND_R_PCM_PREPARE,+VIRTIO_SND_R_PCM_RELEASE,+VIRTIO_SND_R_PCM_START,+VIRTIO_SND_R_PCM_STOP,++/* channel map control request types */+VIRTIO_SND_R_CHMAP_INFO=0x0200,++/* jack event types */+VIRTIO_SND_EVT_JACK_CONNECTED=0x1000,+VIRTIO_SND_EVT_JACK_DISCONNECTED,++/* PCM event types */+VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED=0x1100,+VIRTIO_SND_EVT_PCM_XRUN,++/* common status codes */+VIRTIO_SND_S_OK=0x8000,+VIRTIO_SND_S_BAD_MSG,+VIRTIO_SND_S_NOT_SUPP,+VIRTIO_SND_S_IO_ERR+};++/* common header */+structvirtio_snd_hdr{+__le32code;+};++/* event notification */+structvirtio_snd_event{+/* VIRTIO_SND_EVT_XXX */+structvirtio_snd_hdrhdr;+/* optional event data */+__le32data;+};++/* common control request to query an item information */+structvirtio_snd_query_info{+/* VIRTIO_SND_R_XXX_INFO */+structvirtio_snd_hdrhdr;+/* item start identifier */+__le32start_id;+/* item count to query */+__le32count;+/* item information size in bytes */+__le32size;+};++/* common item information header */+structvirtio_snd_info{+/* function group node id (High Definition Audio Specification 7.1.2) */+__le32hda_fn_nid;+};++/*******************************************************************************+*JACKCONTROLMESSAGES+*/+structvirtio_snd_jack_hdr{+/* VIRTIO_SND_R_JACK_XXX */+structvirtio_snd_hdrhdr;+/* 0 ... virtio_snd_config::jacks - 1 */+__le32jack_id;+};++/* supported jack features */+enum{+VIRTIO_SND_JACK_F_REMAP=0+};++structvirtio_snd_jack_info{+/* common header */+structvirtio_snd_infohdr;+/* supported feature bit map (1 << VIRTIO_SND_JACK_F_XXX) */+__le32features;+/* pin configuration (High Definition Audio Specification 7.3.3.31) */+__le32hda_reg_defconf;+/* pin capabilities (High Definition Audio Specification 7.3.4.9) */+__le32hda_reg_caps;+/* current jack connection status (0: disconnected, 1: connected) */+__u8connected;++__u8padding[7];+};++/* jack remapping control request */+structvirtio_snd_jack_remap{+/* .code = VIRTIO_SND_R_JACK_REMAP */+structvirtio_snd_jack_hdrhdr;+/* selected association number */+__le32association;+/* selected sequence number */+__le32sequence;+};++/*******************************************************************************+*PCMCONTROLMESSAGES+*/+structvirtio_snd_pcm_hdr{+/* VIRTIO_SND_R_PCM_XXX */+structvirtio_snd_hdrhdr;+/* 0 ... virtio_snd_config::streams - 1 */+__le32stream_id;+};++/* supported PCM stream features */+enum{+VIRTIO_SND_PCM_F_SHMEM_HOST=0,+VIRTIO_SND_PCM_F_SHMEM_GUEST,+VIRTIO_SND_PCM_F_MSG_POLLING,+VIRTIO_SND_PCM_F_EVT_SHMEM_PERIODS,+VIRTIO_SND_PCM_F_EVT_XRUNS+};++/* supported PCM sample formats */+enum{+/* analog formats (width / physical width) */+VIRTIO_SND_PCM_FMT_IMA_ADPCM=0,/* 4 / 4 bits */+VIRTIO_SND_PCM_FMT_MU_LAW,/* 8 / 8 bits */+VIRTIO_SND_PCM_FMT_A_LAW,/* 8 / 8 bits */+VIRTIO_SND_PCM_FMT_S8,/* 8 / 8 bits */+VIRTIO_SND_PCM_FMT_U8,/* 8 / 8 bits */+VIRTIO_SND_PCM_FMT_S16,/* 16 / 16 bits */+VIRTIO_SND_PCM_FMT_U16,/* 16 / 16 bits */+VIRTIO_SND_PCM_FMT_S18_3,/* 18 / 24 bits */+VIRTIO_SND_PCM_FMT_U18_3,/* 18 / 24 bits */+VIRTIO_SND_PCM_FMT_S20_3,/* 20 / 24 bits */+VIRTIO_SND_PCM_FMT_U20_3,/* 20 / 24 bits */+VIRTIO_SND_PCM_FMT_S24_3,/* 24 / 24 bits */+VIRTIO_SND_PCM_FMT_U24_3,/* 24 / 24 bits */+VIRTIO_SND_PCM_FMT_S20,/* 20 / 32 bits */+VIRTIO_SND_PCM_FMT_U20,/* 20 / 32 bits */+VIRTIO_SND_PCM_FMT_S24,/* 24 / 32 bits */+VIRTIO_SND_PCM_FMT_U24,/* 24 / 32 bits */+VIRTIO_SND_PCM_FMT_S32,/* 32 / 32 bits */+VIRTIO_SND_PCM_FMT_U32,/* 32 / 32 bits */+VIRTIO_SND_PCM_FMT_FLOAT,/* 32 / 32 bits */+VIRTIO_SND_PCM_FMT_FLOAT64,/* 64 / 64 bits */+/* digital formats (width / physical width) */+VIRTIO_SND_PCM_FMT_DSD_U8,/* 8 / 8 bits */+VIRTIO_SND_PCM_FMT_DSD_U16,/* 16 / 16 bits */+VIRTIO_SND_PCM_FMT_DSD_U32,/* 32 / 32 bits */+VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME/* 32 / 32 bits */+};++/* supported PCM frame rates */+enum{+VIRTIO_SND_PCM_RATE_5512=0,+VIRTIO_SND_PCM_RATE_8000,+VIRTIO_SND_PCM_RATE_11025,+VIRTIO_SND_PCM_RATE_16000,+VIRTIO_SND_PCM_RATE_22050,+VIRTIO_SND_PCM_RATE_32000,+VIRTIO_SND_PCM_RATE_44100,+VIRTIO_SND_PCM_RATE_48000,+VIRTIO_SND_PCM_RATE_64000,+VIRTIO_SND_PCM_RATE_88200,+VIRTIO_SND_PCM_RATE_96000,+VIRTIO_SND_PCM_RATE_176400,+VIRTIO_SND_PCM_RATE_192000,+VIRTIO_SND_PCM_RATE_384000+};++structvirtio_snd_pcm_info{+/* common header */+structvirtio_snd_infohdr;+/* supported feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */+__le32features;+/* supported sample format bit map (1 << VIRTIO_SND_PCM_FMT_XXX) */+__le64formats;+/* supported frame rate bit map (1 << VIRTIO_SND_PCM_RATE_XXX) */+__le64rates;+/* dataflow direction (VIRTIO_SND_D_XXX) */+__u8direction;+/* minimum # of supported channels */+__u8channels_min;+/* maximum # of supported channels */+__u8channels_max;++__u8padding[5];+};++/* set PCM stream format */+structvirtio_snd_pcm_set_params{+/* .code = VIRTIO_SND_R_PCM_SET_PARAMS */+structvirtio_snd_pcm_hdrhdr;+/* size of the hardware buffer */+__le32buffer_bytes;+/* size of the hardware period */+__le32period_bytes;+/* selected feature bit map (1 << VIRTIO_SND_PCM_F_XXX) */+__le32features;+/* selected # of channels */+__u8channels;+/* selected sample format (VIRTIO_SND_PCM_FMT_XXX) */+__u8format;+/* selected frame rate (VIRTIO_SND_PCM_RATE_XXX) */+__u8rate;++__u8padding;+};++/*******************************************************************************+*PCMI/OMESSAGES+*/++/* I/O request header */+structvirtio_snd_pcm_xfer{+/* 0 ... virtio_snd_config::streams - 1 */+__le32stream_id;+};++/* I/O request status */+structvirtio_snd_pcm_status{+/* VIRTIO_SND_S_XXX */+__le32status;+/* current device latency */+__le32latency_bytes;+};++/*******************************************************************************+*CHANNELMAPCONTROLMESSAGES+*/+structvirtio_snd_chmap_hdr{+/* VIRTIO_SND_R_CHMAP_XXX */+structvirtio_snd_hdrhdr;+/* 0 ... virtio_snd_config::chmaps - 1 */+__le32chmap_id;+};++/* standard channel position definition */+enum{+VIRTIO_SND_CHMAP_NONE=0,/* undefined */+VIRTIO_SND_CHMAP_NA,/* silent */+VIRTIO_SND_CHMAP_MONO,/* mono stream */+VIRTIO_SND_CHMAP_FL,/* front left */+VIRTIO_SND_CHMAP_FR,/* front right */+VIRTIO_SND_CHMAP_RL,/* rear left */+VIRTIO_SND_CHMAP_RR,/* rear right */+VIRTIO_SND_CHMAP_FC,/* front center */+VIRTIO_SND_CHMAP_LFE,/* low frequency (LFE) */+VIRTIO_SND_CHMAP_SL,/* side left */+VIRTIO_SND_CHMAP_SR,/* side right */+VIRTIO_SND_CHMAP_RC,/* rear center */+VIRTIO_SND_CHMAP_FLC,/* front left center */+VIRTIO_SND_CHMAP_FRC,/* front right center */+VIRTIO_SND_CHMAP_RLC,/* rear left center */+VIRTIO_SND_CHMAP_RRC,/* rear right center */+VIRTIO_SND_CHMAP_FLW,/* front left wide */+VIRTIO_SND_CHMAP_FRW,/* front right wide */+VIRTIO_SND_CHMAP_FLH,/* front left high */+VIRTIO_SND_CHMAP_FCH,/* front center high */+VIRTIO_SND_CHMAP_FRH,/* front right high */+VIRTIO_SND_CHMAP_TC,/* top center */+VIRTIO_SND_CHMAP_TFL,/* top front left */+VIRTIO_SND_CHMAP_TFR,/* top front right */+VIRTIO_SND_CHMAP_TFC,/* top front center */+VIRTIO_SND_CHMAP_TRL,/* top rear left */+VIRTIO_SND_CHMAP_TRR,/* top rear right */+VIRTIO_SND_CHMAP_TRC,/* top rear center */+VIRTIO_SND_CHMAP_TFLC,/* top front left center */+VIRTIO_SND_CHMAP_TFRC,/* top front right center */+VIRTIO_SND_CHMAP_TSL,/* top side left */+VIRTIO_SND_CHMAP_TSR,/* top side right */+VIRTIO_SND_CHMAP_LLFE,/* left LFE */+VIRTIO_SND_CHMAP_RLFE,/* right LFE */+VIRTIO_SND_CHMAP_BC,/* bottom center */+VIRTIO_SND_CHMAP_BLC,/* bottom left center */+VIRTIO_SND_CHMAP_BRC/* bottom right center */+};++/* maximum possible number of channels */+#define VIRTIO_SND_CHMAP_MAX_SIZE 18++structvirtio_snd_chmap_info{+/* common header */+structvirtio_snd_infohdr;+/* dataflow direction (VIRTIO_SND_D_XXX) */+__u8direction;+/* # of valid channel position values */+__u8channels;+/* channel position values (VIRTIO_SND_CHMAP_XXX) */+__u8positions[VIRTIO_SND_CHMAP_MAX_SIZE];+};++#endif /* VIRTIO_SND_IF_H */
@@ -5,7 +5,8 @@obj-$(CONFIG_SOUND)+=soundcore.oobj-$(CONFIG_DMASOUND)+=oss/dmasound/obj-$(CONFIG_SND)+=core/i2c/drivers/isa/pci/ppc/arm/sh/synth/usb/\-firewire/sparc/spi/parisc/pcmcia/mips/soc/atmel/hda/x86/xen/+firewire/sparc/spi/parisc/pcmcia/mips/soc/atmel/hda/x86/xen/\+virtio/obj-$(CONFIG_SND_AOA)+=aoa/# This one must be compilable even if sound is configured out
@@ -0,0 +1,415 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Soundcarddriverforvirtio+*Copyright(C)2020OpenSynergyGmbH+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,see<http://www.gnu.org/licenses/>.+*/+#include<linux/module.h>+#include<linux/moduleparam.h>+#include<linux/virtio_config.h>+#include<sound/initval.h>+#include<uapi/linux/virtio_ids.h>++#include"virtio_card.h"++staticvoidvirtsnd_remove(structvirtio_device*vdev);++/**+*virtsnd_event_send()-Addaneventtotheeventqueue.+*@vqueue:Underlyingeventvirtqueue.+*@event:Event.+*@notify:Indicateswhetherornottosendanotificationtothedevice.+*@gfp:Kernelflagsformemoryallocation.+*+*Context:Anycontext.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_event_send(structvirtqueue*vqueue,+structvirtio_snd_event*event,boolnotify,+gfp_tgfp)+{+structscatterlistsg;+structscatterlist*psgs[1]={&sg};+intrc;++/* reset event content */+memset(event,0,sizeof(*event));++sg_init_one(&sg,event,sizeof(*event));++rc=virtqueue_add_sgs(vqueue,psgs,0,1,event,gfp);+if(rc)+returnrc;++if(notify)+if(virtqueue_kick_prepare(vqueue))+if(!virtqueue_notify(vqueue))+return-EIO;++return0;+}++/**+*virtsnd_event_notify_cb()-Dispatchallreportedeventsfromtheeventqueue.+*@vqueue:Underlyingeventvirtqueue.+*+*Thiscallbackfunctioniscalleduponavringinterruptrequestfromthe+*device.+*+*Context:Interruptcontext.+*/+staticvoidvirtsnd_event_notify_cb(structvirtqueue*vqueue)+{+structvirtio_snd*snd=vqueue->vdev->priv;+structvirtio_snd_queue*queue=virtsnd_event_queue(snd);+unsignedlongflags;++spin_lock_irqsave(&queue->lock,flags);+while(queue->vqueue){+virtqueue_disable_cb(queue->vqueue);++for(;;){+structvirtio_snd_event*event;+u32length;++event=virtqueue_get_buf(queue->vqueue,&length);+if(!event)+break;++virtsnd_event_send(queue->vqueue,event,true,+GFP_ATOMIC);+}++if(unlikely(virtqueue_is_broken(queue->vqueue)))+break;++if(virtqueue_enable_cb(queue->vqueue))+break;+}+spin_unlock_irqrestore(&queue->lock,flags);+}++/**+*virtsnd_find_vqs()-Enumerateandinitializeallvirtqueues.+*@snd:VirtIOsounddevice.+*+*Aftercallingthisfunction,theeventqueueisdisabled.+*+*Context:Anycontext.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_find_vqs(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+vq_callback_t*callbacks[VIRTIO_SND_VQ_MAX]={0};+constchar*names[VIRTIO_SND_VQ_MAX]={+[VIRTIO_SND_VQ_CONTROL]="virtsnd-ctl",+[VIRTIO_SND_VQ_EVENT]="virtsnd-event",+[VIRTIO_SND_VQ_TX]="virtsnd-tx",+[VIRTIO_SND_VQ_RX]="virtsnd-rx"+};+structvirtqueue*vqs[VIRTIO_SND_VQ_MAX]={0};+unsignedinti;+unsignedintn=0;+intrc;++callbacks[VIRTIO_SND_VQ_EVENT]=virtsnd_event_notify_cb;++rc=virtio_find_vqs(vdev,VIRTIO_SND_VQ_MAX,vqs,callbacks,names,+NULL);+if(rc){+dev_err(&vdev->dev,"failed to initialize virtqueues\n");+returnrc;+}++for(i=0;i<VIRTIO_SND_VQ_MAX;++i)+snd->queues[i].vqueue=vqs[i];++/* Allocate events and populate the event queue */+virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]);++n=virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]);++snd->event_msgs=devm_kcalloc(&vdev->dev,n,sizeof(*snd->event_msgs),+GFP_KERNEL);+if(!snd->event_msgs)+return-ENOMEM;++for(i=0;i<n;++i){+rc=virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT],+&snd->event_msgs[i],false,GFP_KERNEL);+if(rc)+returnrc;+}++return0;+}++/**+*virtsnd_enable_event_vq()-Enabletheeventvirtqueue.+*@snd:VirtIOsounddevice.+*+*Context:Anycontext.+*/+staticvoidvirtsnd_enable_event_vq(structvirtio_snd*snd)+{+structvirtio_snd_queue*queue=virtsnd_event_queue(snd);++if(!virtqueue_enable_cb(queue->vqueue))+virtsnd_event_notify_cb(queue->vqueue);+}++/**+*virtsnd_disable_vqs()-Disableallvirtqueues.+*@snd:VirtIOsounddevice.+*+*Alsofreeallallocatedeventsandcontrolmessages.+*+*Context:Anycontext.+*/+staticvoidvirtsnd_disable_vqs(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+unsignedinti;+unsignedlongflags;++for(i=0;i<VIRTIO_SND_VQ_MAX;++i){+structvirtio_snd_queue*queue=&snd->queues[i];++spin_lock_irqsave(&queue->lock,flags);+/* Prohibit the use of the queue */+if(queue->vqueue)+virtqueue_disable_cb(queue->vqueue);+queue->vqueue=NULL;+spin_unlock_irqrestore(&queue->lock,flags);+}++if(snd->event_msgs)+devm_kfree(&vdev->dev,snd->event_msgs);++snd->event_msgs=NULL;+}++/**+*virtsnd_reset_fn()-Kernelworker'sfunctiontoresetthedevice.+*@work:Resetdevicework.+*+*Context:Processcontext.+*/+staticvoidvirtsnd_reset_fn(structwork_struct*work)+{+structvirtio_snd*snd=+container_of(work,structvirtio_snd,reset_work);+structvirtio_device*vdev=snd->vdev;+structdevice*dev=&vdev->dev;+intrc;++dev_info(dev,"sound device needs reset\n");++/*+*Itseemsthattheonlywaytoproperlyresetthedeviceistoremove+*andre-createtheALSAsoundcarddevice.+*+*Alsoresettingthedeviceinvolvesanumberofstepswithsettingthe+*statusbitsdescribedinthevirtiospecification.Andtheeasiest+*waytogeteverythingrightistousethevirtiobusinterface.+*/+rc=dev->bus->remove(dev);+if(rc)+dev_warn(dev,"bus->remove() failed: %d",rc);++rc=dev->bus->probe(dev);+if(rc)+dev_err(dev,"bus->probe() failed: %d",rc);+}++/**+*virtsnd_build_devs()-ReadconfigurationandbuildALSAdevices.+*@snd:VirtIOsounddevice.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_build_devs(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+intrc;++rc=snd_card_new(&vdev->dev,SNDRV_DEFAULT_IDX1,SNDRV_DEFAULT_STR1,+THIS_MODULE,0,&snd->card);+if(rc<0)+returnrc;++snd->card->private_data=snd;++strscpy(snd->card->id,"viosnd",sizeof(snd->card->id));+strscpy(snd->card->driver,"virtio_snd",sizeof(snd->card->driver));+strscpy(snd->card->shortname,"VIOSND",sizeof(snd->card->shortname));+strscpy(snd->card->longname,"VirtIO Sound Card",+sizeof(snd->card->longname));++returnsnd_card_register(snd->card);+}++/**+*virtsnd_validate()-Validateifthedevicecanbestarted.+*@vdev:VirtIOparentdevice.+*+*Context:Anycontext.+*Return:0onsuccess,-EINVALonfailure.+*/+staticintvirtsnd_validate(structvirtio_device*vdev)+{+if(!vdev->config->get){+dev_err(&vdev->dev,"configuration access disabled\n");+return-EINVAL;+}++if(!virtio_has_feature(vdev,VIRTIO_F_VERSION_1)){+dev_err(&vdev->dev,+"device does not comply with spec version 1.x\n");+return-EINVAL;+}++return0;+}++/**+*virtsnd_probe()-Createandinitializethedevice.+*@vdev:VirtIOparentdevice.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_probe(structvirtio_device*vdev)+{+structvirtio_snd*snd;+unsignedinti;+intrc;++snd=devm_kzalloc(&vdev->dev,sizeof(*snd),GFP_KERNEL);+if(!snd)+return-ENOMEM;++snd->vdev=vdev;+INIT_WORK(&snd->reset_work,virtsnd_reset_fn);++vdev->priv=snd;++for(i=0;i<VIRTIO_SND_VQ_MAX;++i)+spin_lock_init(&snd->queues[i].lock);++rc=virtsnd_find_vqs(snd);+if(rc)+gotoon_failure;++virtio_device_ready(vdev);++rc=virtsnd_build_devs(snd);+if(rc)+gotoon_failure;++virtsnd_enable_event_vq(snd);++on_failure:+if(rc)+virtsnd_remove(vdev);++returnrc;+}++/**+*virtsnd_remove()-RemoveVirtIOandALSAdevices.+*@vdev:VirtIOparentdevice.+*+*Context:Anycontextthatpermitstosleep.+*/+staticvoidvirtsnd_remove(structvirtio_device*vdev)+{+structvirtio_snd*snd=vdev->priv;++if(!snd)+return;++/*+*Makesurenooneisaccessingthevirtqueuesandsendingsynchronous+*requeststothedevice.Thiscanhappenifwegotherebecausethe+*deviceneedstobereset.+*/+virtsnd_disable_vqs(snd);++if(snd->card)+snd_card_free(snd->card);++vdev->config->reset(vdev);+vdev->config->del_vqs(vdev);++devm_kfree(&vdev->dev,snd);++vdev->priv=NULL;+}++/**+*virtsnd_config_changed()-Handleconfigurationchangenotification.+*@vdev:VirtIOparentdevice.+*+*Thiscallbackfunctioniscalleduponaconfigurationchangeinterrupt+*requestfromthedevice.CurrentlyonlyusedtohandleNEEDS_RESETdevice+*status.+*+*Context:Interruptcontext.+*/+staticvoidvirtsnd_config_changed(structvirtio_device*vdev)+{+structvirtio_snd*snd=vdev->priv;+unsignedintstatus=vdev->config->get_status(vdev);++if(status&VIRTIO_CONFIG_S_NEEDS_RESET)+schedule_work(&snd->reset_work);+else+dev_warn(&vdev->dev,+"sound device configuration was changed\n");+}++staticconststructvirtio_device_idid_table[]={+{VIRTIO_ID_SOUND,VIRTIO_DEV_ANY_ID},+{0},+};++staticstructvirtio_drivervirtsnd_driver={+.driver.name=KBUILD_MODNAME,+.driver.owner=THIS_MODULE,+.id_table=id_table,+.validate=virtsnd_validate,+.probe=virtsnd_probe,+.remove=virtsnd_remove,+.config_changed=virtsnd_config_changed,+};++staticint__initinit(void)+{+returnregister_virtio_driver(&virtsnd_driver);+}+module_init(init);++staticvoid__exitfini(void)+{+unregister_virtio_driver(&virtsnd_driver);+}+module_exit(fini);++MODULE_DEVICE_TABLE(virtio,id_table);+MODULE_DESCRIPTION("Virtio sound card driver");+MODULE_LICENSE("GPL");
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-01-24 16:57:17
Like the HDA specification, the virtio sound device specification links
PCM substreams, jacks and PCM channel maps into functional groups. For
each discovered group, a PCM device is created, the number of which
coincides with the group number.
Introduce the module parameters for setting the hardware buffer
parameters:
pcm_buffer_ms [=160]
pcm_periods_min [=2]
pcm_periods_max [=16]
pcm_period_ms_min [=10]
pcm_period_ms_max [=80]
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 3 +-
sound/virtio/virtio_card.c | 45 ++++
sound/virtio/virtio_card.h | 9 +
sound/virtio/virtio_pcm.c | 536 +++++++++++++++++++++++++++++++++++++
sound/virtio/virtio_pcm.h | 89 ++++++
5 files changed, 681 insertions(+), 1 deletion(-)
create mode 100644 sound/virtio/virtio_pcm.c
create mode 100644 sound/virtio/virtio_pcm.h
@@ -0,0 +1,536 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Soundcarddriverforvirtio+*Copyright(C)2020OpenSynergyGmbH+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,see<http://www.gnu.org/licenses/>.+*/+#include<linux/moduleparam.h>+#include<linux/virtio_config.h>++#include"virtio_card.h"++staticunsignedintpcm_buffer_ms=160;+module_param(pcm_buffer_ms,uint,0644);+MODULE_PARM_DESC(pcm_buffer_ms,"PCM substream buffer time in milliseconds");++staticunsignedintpcm_periods_min=2;+module_param(pcm_periods_min,uint,0644);+MODULE_PARM_DESC(pcm_periods_min,"Minimum number of PCM periods");++staticunsignedintpcm_periods_max=16;+module_param(pcm_periods_max,uint,0644);+MODULE_PARM_DESC(pcm_periods_max,"Maximum number of PCM periods");++staticunsignedintpcm_period_ms_min=10;+module_param(pcm_period_ms_min,uint,0644);+MODULE_PARM_DESC(pcm_period_ms_min,"Minimum PCM period time in milliseconds");++staticunsignedintpcm_period_ms_max=80;+module_param(pcm_period_ms_max,uint,0644);+MODULE_PARM_DESC(pcm_period_ms_max,"Maximum PCM period time in milliseconds");++/* Map for converting VirtIO format to ALSA format. */+staticconstunsignedintg_v2a_format_map[]={+[VIRTIO_SND_PCM_FMT_IMA_ADPCM]=SNDRV_PCM_FORMAT_IMA_ADPCM,+[VIRTIO_SND_PCM_FMT_MU_LAW]=SNDRV_PCM_FORMAT_MU_LAW,+[VIRTIO_SND_PCM_FMT_A_LAW]=SNDRV_PCM_FORMAT_A_LAW,+[VIRTIO_SND_PCM_FMT_S8]=SNDRV_PCM_FORMAT_S8,+[VIRTIO_SND_PCM_FMT_U8]=SNDRV_PCM_FORMAT_U8,+[VIRTIO_SND_PCM_FMT_S16]=SNDRV_PCM_FORMAT_S16_LE,+[VIRTIO_SND_PCM_FMT_U16]=SNDRV_PCM_FORMAT_U16_LE,+[VIRTIO_SND_PCM_FMT_S18_3]=SNDRV_PCM_FORMAT_S18_3LE,+[VIRTIO_SND_PCM_FMT_U18_3]=SNDRV_PCM_FORMAT_U18_3LE,+[VIRTIO_SND_PCM_FMT_S20_3]=SNDRV_PCM_FORMAT_S20_3LE,+[VIRTIO_SND_PCM_FMT_U20_3]=SNDRV_PCM_FORMAT_U20_3LE,+[VIRTIO_SND_PCM_FMT_S24_3]=SNDRV_PCM_FORMAT_S24_3LE,+[VIRTIO_SND_PCM_FMT_U24_3]=SNDRV_PCM_FORMAT_U24_3LE,+[VIRTIO_SND_PCM_FMT_S20]=SNDRV_PCM_FORMAT_S20_LE,+[VIRTIO_SND_PCM_FMT_U20]=SNDRV_PCM_FORMAT_U20_LE,+[VIRTIO_SND_PCM_FMT_S24]=SNDRV_PCM_FORMAT_S24_LE,+[VIRTIO_SND_PCM_FMT_U24]=SNDRV_PCM_FORMAT_U24_LE,+[VIRTIO_SND_PCM_FMT_S32]=SNDRV_PCM_FORMAT_S32_LE,+[VIRTIO_SND_PCM_FMT_U32]=SNDRV_PCM_FORMAT_U32_LE,+[VIRTIO_SND_PCM_FMT_FLOAT]=SNDRV_PCM_FORMAT_FLOAT_LE,+[VIRTIO_SND_PCM_FMT_FLOAT64]=SNDRV_PCM_FORMAT_FLOAT64_LE,+[VIRTIO_SND_PCM_FMT_DSD_U8]=SNDRV_PCM_FORMAT_DSD_U8,+[VIRTIO_SND_PCM_FMT_DSD_U16]=SNDRV_PCM_FORMAT_DSD_U16_LE,+[VIRTIO_SND_PCM_FMT_DSD_U32]=SNDRV_PCM_FORMAT_DSD_U32_LE,+[VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME]=+SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE+};++/* Map for converting VirtIO frame rate to ALSA frame rate. */+structvirtsnd_v2a_rate{+unsignedintalsa_bit;+unsignedintrate;+};++staticconststructvirtsnd_v2a_rateg_v2a_rate_map[]={+[VIRTIO_SND_PCM_RATE_5512]={SNDRV_PCM_RATE_5512,5512},+[VIRTIO_SND_PCM_RATE_8000]={SNDRV_PCM_RATE_8000,8000},+[VIRTIO_SND_PCM_RATE_11025]={SNDRV_PCM_RATE_11025,11025},+[VIRTIO_SND_PCM_RATE_16000]={SNDRV_PCM_RATE_16000,16000},+[VIRTIO_SND_PCM_RATE_22050]={SNDRV_PCM_RATE_22050,22050},+[VIRTIO_SND_PCM_RATE_32000]={SNDRV_PCM_RATE_32000,32000},+[VIRTIO_SND_PCM_RATE_44100]={SNDRV_PCM_RATE_44100,44100},+[VIRTIO_SND_PCM_RATE_48000]={SNDRV_PCM_RATE_48000,48000},+[VIRTIO_SND_PCM_RATE_64000]={SNDRV_PCM_RATE_64000,64000},+[VIRTIO_SND_PCM_RATE_88200]={SNDRV_PCM_RATE_88200,88200},+[VIRTIO_SND_PCM_RATE_96000]={SNDRV_PCM_RATE_96000,96000},+[VIRTIO_SND_PCM_RATE_176400]={SNDRV_PCM_RATE_176400,176400},+[VIRTIO_SND_PCM_RATE_192000]={SNDRV_PCM_RATE_192000,192000}+};++/**+*virtsnd_pcm_build_hw()-ParsesubstreamconfigandbuildHWdescriptor.+*@substream:VirtIOsubstream.+*@info:VirtIOsubstreaminformationentry.+*+*Context:Anycontext.+*Return:0onsuccess,-EINVALifconfigurationisinvalid.+*/+staticintvirtsnd_pcm_build_hw(structvirtio_pcm_substream*substream,+structvirtio_snd_pcm_info*info)+{+structvirtio_device*vdev=substream->snd->vdev;+unsignedinti;+u64values;+size_tsample_max=0;+size_tsample_min=0;++substream->features=le32_to_cpu(info->features);++/*+*TODO:setSNDRV_PCM_INFO_{BATCH,BLOCK_TRANSFER}ifdevicesupports+*onlymessage-basedtransport.+*/+substream->hw.info=+SNDRV_PCM_INFO_MMAP|+SNDRV_PCM_INFO_MMAP_VALID|+SNDRV_PCM_INFO_BATCH|+SNDRV_PCM_INFO_BLOCK_TRANSFER|+SNDRV_PCM_INFO_INTERLEAVED;++if(!info->channels_min||info->channels_min>info->channels_max){+dev_err(&vdev->dev,+"SID %u: invalid channel range [%u %u]\n",+substream->sid,info->channels_min,info->channels_max);+return-EINVAL;+}++substream->hw.channels_min=info->channels_min;+substream->hw.channels_max=info->channels_max;++values=le64_to_cpu(info->formats);++substream->hw.formats=0;++for(i=0;i<ARRAY_SIZE(g_v2a_format_map);++i)+if(values&(1ULL<<i)){+unsignedintalsa_fmt=g_v2a_format_map[i];+intbytes=snd_pcm_format_physical_width(alsa_fmt)/8;++if(!sample_min||sample_min>bytes)+sample_min=bytes;++if(sample_max<bytes)+sample_max=bytes;++substream->hw.formats|=(1ULL<<alsa_fmt);+}++if(!substream->hw.formats){+dev_err(&vdev->dev,+"SID %u: no supported PCM sample formats found\n",+substream->sid);+return-EINVAL;+}++values=le64_to_cpu(info->rates);++substream->hw.rates=0;++for(i=0;i<ARRAY_SIZE(g_v2a_rate_map);++i)+if(values&(1ULL<<i)){+if(!substream->hw.rate_min||+substream->hw.rate_min>g_v2a_rate_map[i].rate)+substream->hw.rate_min=g_v2a_rate_map[i].rate;++if(substream->hw.rate_max<g_v2a_rate_map[i].rate)+substream->hw.rate_max=g_v2a_rate_map[i].rate;++substream->hw.rates|=g_v2a_rate_map[i].alsa_bit;+}++if(!substream->hw.rates){+dev_err(&vdev->dev,+"SID %u: no supported PCM frame rates found\n",+substream->sid);+return-EINVAL;+}++substream->hw.periods_min=pcm_periods_min;+substream->hw.periods_max=pcm_periods_max;++/*+*Wemustensurethatthereisenoughspaceinthebuffertostore+*pcm_buffer_msmsforthecombination(Cmax,Smax,Rmax),where:+*Cmax=maximumsupportednumberofchannels,+*Smax=maximumsupportedsamplesizeinbytes,+*Rmax=maximumsupportedframerate.+*/+substream->hw.buffer_bytes_max=+sample_max*substream->hw.channels_max*pcm_buffer_ms*+(substream->hw.rate_max/MSEC_PER_SEC);++/* Align the buffer size to the page size */+substream->hw.buffer_bytes_max=+(substream->hw.buffer_bytes_max+PAGE_SIZE-1)&-PAGE_SIZE;++/*+*Wemustensurethattheminimumperiodsizeisenoughtostore+*pcm_period_ms_minmsforthecombination(Cmin,Smin,Rmin),where:+*Cmin=minimumsupportednumberofchannels,+*Smin=minimumsupportedsamplesizeinbytes,+*Rmin=minimumsupportedframerate.+*/+substream->hw.period_bytes_min=+sample_min*substream->hw.channels_min*pcm_period_ms_min*+(substream->hw.rate_min/MSEC_PER_SEC);++/*+*Wemustensurethatthemaximumperiodsizeisenoughtostore+*pcm_period_ms_maxmsforthecombination(Cmax,Smax,Rmax).+*/+substream->hw.period_bytes_max=+sample_max*substream->hw.channels_max*pcm_period_ms_max*+(substream->hw.rate_max/MSEC_PER_SEC);++return0;+}++/**+*virtsnd_pcm_prealloc_pages()-Preallocatesubstreamhardwarebuffer.+*@substream:VirtIOsubstream.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_pcm_prealloc_pages(structvirtio_pcm_substream*substream)+{+structsnd_pcm_substream*ksubstream=substream->substream;+size_tsize=substream->hw.buffer_bytes_max;+structdevice*data=snd_dma_continuous_data(GFP_KERNEL);++/*+*WejustallocateaCONTINUOUSbufferasitshouldworkinanysetup.+*+*IfthereisaneedtouseDEV(_XXX),thenaddthiscasehereand+*(probably)updatetherelatedsourcecodeinotherplaces.+*/+snd_pcm_lib_preallocate_pages(ksubstream,SNDRV_DMA_TYPE_CONTINUOUS,+data,size,size);++return0;+}++/**+*virtsnd_pcm_find()-FindthePCMdeviceforthespecifiednodeID.+*@snd:VirtIOsounddevice.+*@nid:FunctionnodeID.+*+*Context:Anycontext.+*Return:apointertothePCMdeviceorERR_PTR(-ENOENT).+*/+structvirtio_pcm*virtsnd_pcm_find(structvirtio_snd*snd,unsignedintnid)+{+structvirtio_pcm*pcm;++list_for_each_entry(pcm,&snd->pcm_list,list)+if(pcm->nid==nid)+returnpcm;++returnERR_PTR(-ENOENT);+}++/**+*virtsnd_pcm_find_or_create()-FindorcreatethePCMdeviceforthe+*specifiednodeID.+*@snd:VirtIOsounddevice.+*@nid:FunctionnodeID.+*+*Context:Anycontextthatpermitstosleep.+*Return:apointertothePCMdeviceorERR_PTR(-errno).+*/+structvirtio_pcm*virtsnd_pcm_find_or_create(structvirtio_snd*snd,+unsignedintnid)+{+structvirtio_device*vdev=snd->vdev;+structvirtio_pcm*pcm;++pcm=virtsnd_pcm_find(snd,nid);+if(!IS_ERR(pcm))+returnpcm;++pcm=devm_kzalloc(&vdev->dev,sizeof(*pcm),GFP_KERNEL);+if(!pcm)+returnERR_PTR(-ENOMEM);++pcm->nid=nid;+list_add_tail(&pcm->list,&snd->pcm_list);++returnpcm;+}++/**+*virtsnd_pcm_validate()-Validateifthedevicecanbestarted.+*@vdev:VirtIOparentdevice.+*+*Context:Anycontext.+*Return:0onsuccess,-EINVALonfailure.+*/+intvirtsnd_pcm_validate(structvirtio_device*vdev)+{+if(pcm_periods_min<2||pcm_periods_min>pcm_periods_max){+dev_err(&vdev->dev,+"invalid range [%u %u] of the number of PCM periods\n",+pcm_periods_min,pcm_periods_max);+return-EINVAL;+}++if(!pcm_period_ms_min||pcm_period_ms_min>pcm_period_ms_max){+dev_err(&vdev->dev,+"invalid range [%u %u] of the size of the PCM period\n",+pcm_period_ms_min,pcm_period_ms_max);+return-EINVAL;+}++if(pcm_buffer_ms<pcm_periods_min*pcm_period_ms_min){+dev_err(&vdev->dev,+"pcm_buffer_ms(=%u) value cannot be < %u ms\n",+pcm_buffer_ms,pcm_periods_min*pcm_period_ms_min);+return-EINVAL;+}++if(pcm_period_ms_max>pcm_buffer_ms/2){+dev_err(&vdev->dev,+"pcm_period_ms_max(=%u) value cannot be > %u ms\n",+pcm_period_ms_max,pcm_buffer_ms/2);+return-EINVAL;+}++return0;+}++/**+*virtsnd_pcm_parse_cfg()-Parsethestreamconfiguration.+*@snd:VirtIOsounddevice.+*+*Thisfunctioniscalledduringinitialdeviceinitialization.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+intvirtsnd_pcm_parse_cfg(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+structvirtio_snd_pcm_info*info;+unsignedinti;+intrc;++virtio_cread(vdev,structvirtio_snd_config,streams,+&snd->nsubstreams);+if(!snd->nsubstreams)+return0;++snd->substreams=devm_kcalloc(&vdev->dev,snd->nsubstreams,+sizeof(*snd->substreams),GFP_KERNEL);+if(!snd->substreams)+return-ENOMEM;++info=devm_kcalloc(&vdev->dev,snd->nsubstreams,sizeof(*info),+GFP_KERNEL);+if(!info)+return-ENOMEM;++rc=virtsnd_ctl_query_info(snd,VIRTIO_SND_R_PCM_INFO,0,+snd->nsubstreams,sizeof(*info),info);+if(rc)+returnrc;++for(i=0;i<snd->nsubstreams;++i){+structvirtio_pcm_substream*substream=&snd->substreams[i];+structvirtio_pcm*pcm;++substream->snd=snd;+substream->sid=i;++rc=virtsnd_pcm_build_hw(substream,&info[i]);+if(rc)+returnrc;++substream->nid=le32_to_cpu(info[i].hdr.hda_fn_nid);++pcm=virtsnd_pcm_find_or_create(snd,substream->nid);+if(IS_ERR(pcm))+returnPTR_ERR(pcm);++switch(info[i].direction){+caseVIRTIO_SND_D_OUTPUT:{+substream->direction=SNDRV_PCM_STREAM_PLAYBACK;+break;+}+caseVIRTIO_SND_D_INPUT:{+substream->direction=SNDRV_PCM_STREAM_CAPTURE;+break;+}+default:{+dev_err(&vdev->dev,"SID %u: unknown direction (%u)\n",+substream->sid,info[i].direction);+return-EINVAL;+}+}++pcm->streams[substream->direction].nsubstreams++;+}++devm_kfree(&vdev->dev,info);++return0;+}++/**+*virtsnd_pcm_build_devs()-BuildALSAPCMdevices.+*@snd:VirtIOsounddevice.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+intvirtsnd_pcm_build_devs(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+structvirtio_pcm*pcm;+unsignedinti;+intrc;++list_for_each_entry(pcm,&snd->pcm_list,list){+unsignedintnpbs=+pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams;+unsignedintncps=+pcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams;++if(!npbs&&!ncps)+continue;++rc=snd_pcm_new(snd->card,"virtio_snd",pcm->nid,npbs,ncps,+&pcm->pcm);+if(rc){+dev_err(&vdev->dev,"snd_pcm_new[%u] failed: %d\n",+pcm->nid,rc);+returnrc;+}++pcm->pcm->info_flags=0;+pcm->pcm->dev_class=SNDRV_PCM_CLASS_GENERIC;+pcm->pcm->dev_subclass=SNDRV_PCM_SUBCLASS_GENERIC_MIX;+strscpy(pcm->pcm->name,"VirtIO PCM",sizeof(pcm->pcm->name));++pcm->pcm->private_data=pcm;++for(i=0;i<ARRAY_SIZE(pcm->streams);++i){+structvirtio_pcm_stream*stream=&pcm->streams[i];++if(!stream->nsubstreams)+continue;++stream->substreams=+devm_kcalloc(&vdev->dev,+stream->nsubstreams,+sizeof(*stream->substreams),+GFP_KERNEL);+if(!stream->substreams)+return-ENOMEM;++stream->nsubstreams=0;+}+}++for(i=0;i<snd->nsubstreams;++i){+structvirtio_pcm_substream*substream=&snd->substreams[i];+structvirtio_pcm_stream*stream;++pcm=virtsnd_pcm_find(snd,substream->nid);+if(IS_ERR(pcm))+returnPTR_ERR(pcm);++stream=&pcm->streams[substream->direction];+stream->substreams[stream->nsubstreams++]=substream;+}++list_for_each_entry(pcm,&snd->pcm_list,list)+for(i=0;i<ARRAY_SIZE(pcm->streams);++i){+structvirtio_pcm_stream*stream=&pcm->streams[i];+structsnd_pcm_str*kstream;+structsnd_pcm_substream*ksubstream;++if(!stream->nsubstreams)+continue;++kstream=&pcm->pcm->streams[i];+ksubstream=kstream->substream;++while(ksubstream){+structvirtio_pcm_substream*substream=+stream->substreams[ksubstream->number];++substream->substream=ksubstream;+ksubstream=ksubstream->next;++rc=virtsnd_pcm_prealloc_pages(substream);+if(rc)+returnrc;+}+}++return0;+}++/**+*virtsnd_pcm_event()-HandlethePCMdeviceeventnotification.+*@snd:VirtIOsounddevice.+*@event:VirtIOsoundevent.+*+*Context:Interruptcontext.+*/+voidvirtsnd_pcm_event(structvirtio_snd*snd,structvirtio_snd_event*event)+{+structvirtio_pcm_substream*substream;+unsignedintsid=le32_to_cpu(event->data);++if(sid>=snd->nsubstreams)+return;++substream=&snd->substreams[sid];++switch(le32_to_cpu(event->hdr.code)){+caseVIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:{+/* TODO: deal with shmem elapsed period */+break;+}+caseVIRTIO_SND_EVT_PCM_XRUN:{+break;+}+}+}
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-01-24 16:57:18
The driver implements a message-based transport for I/O substream
operations. Before the start of the substream, the hardware buffer is
sliced into I/O messages, the number of which is equal to the current
number of periods. The size of each message is equal to the current
size of one period.
I/O messages are organized in an ordered queue. The completion of the
I/O message indicates an elapsed period (the only exception is the end
of the stream for the capture substream). Upon completion, the message
is automatically re-added to the end of the queue.
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 3 +-
sound/virtio/virtio_card.c | 10 ++
sound/virtio/virtio_card.h | 9 +
sound/virtio/virtio_pcm.c | 3 +
sound/virtio/virtio_pcm.h | 31 ++++
sound/virtio/virtio_pcm_msg.c | 325 ++++++++++++++++++++++++++++++++++
6 files changed, 380 insertions(+), 1 deletion(-)
create mode 100644 sound/virtio/virtio_pcm_msg.c
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-01-24 16:58:36
Enumerate all available jacks and create ALSA controls.
At the moment jacks have a simple implementation and can only be used
to receive notifications about a plugged in/out device.
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 1 +
sound/virtio/virtio_card.c | 18 +++
sound/virtio/virtio_card.h | 12 ++
sound/virtio/virtio_jack.c | 255 +++++++++++++++++++++++++++++++++++++
4 files changed, 286 insertions(+)
create mode 100644 sound/virtio/virtio_jack.c
@@ -0,0 +1,237 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Soundcarddriverforvirtio+*Copyright(C)2020OpenSynergyGmbH+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,see<http://www.gnu.org/licenses/>.+*/+#include<linux/virtio_config.h>++#include"virtio_card.h"++/* VirtIO->ALSA channel position map */+staticconstu8g_v2a_position_map[]={+[VIRTIO_SND_CHMAP_NONE]=SNDRV_CHMAP_UNKNOWN,+[VIRTIO_SND_CHMAP_NA]=SNDRV_CHMAP_NA,+[VIRTIO_SND_CHMAP_MONO]=SNDRV_CHMAP_MONO,+[VIRTIO_SND_CHMAP_FL]=SNDRV_CHMAP_FL,+[VIRTIO_SND_CHMAP_FR]=SNDRV_CHMAP_FR,+[VIRTIO_SND_CHMAP_RL]=SNDRV_CHMAP_RL,+[VIRTIO_SND_CHMAP_RR]=SNDRV_CHMAP_RR,+[VIRTIO_SND_CHMAP_FC]=SNDRV_CHMAP_FC,+[VIRTIO_SND_CHMAP_LFE]=SNDRV_CHMAP_LFE,+[VIRTIO_SND_CHMAP_SL]=SNDRV_CHMAP_SL,+[VIRTIO_SND_CHMAP_SR]=SNDRV_CHMAP_SR,+[VIRTIO_SND_CHMAP_RC]=SNDRV_CHMAP_RC,+[VIRTIO_SND_CHMAP_FLC]=SNDRV_CHMAP_FLC,+[VIRTIO_SND_CHMAP_FRC]=SNDRV_CHMAP_FRC,+[VIRTIO_SND_CHMAP_RLC]=SNDRV_CHMAP_RLC,+[VIRTIO_SND_CHMAP_RRC]=SNDRV_CHMAP_RRC,+[VIRTIO_SND_CHMAP_FLW]=SNDRV_CHMAP_FLW,+[VIRTIO_SND_CHMAP_FRW]=SNDRV_CHMAP_FRW,+[VIRTIO_SND_CHMAP_FLH]=SNDRV_CHMAP_FLH,+[VIRTIO_SND_CHMAP_FCH]=SNDRV_CHMAP_FCH,+[VIRTIO_SND_CHMAP_FRH]=SNDRV_CHMAP_FRH,+[VIRTIO_SND_CHMAP_TC]=SNDRV_CHMAP_TC,+[VIRTIO_SND_CHMAP_TFL]=SNDRV_CHMAP_TFL,+[VIRTIO_SND_CHMAP_TFR]=SNDRV_CHMAP_TFR,+[VIRTIO_SND_CHMAP_TFC]=SNDRV_CHMAP_TFC,+[VIRTIO_SND_CHMAP_TRL]=SNDRV_CHMAP_TRL,+[VIRTIO_SND_CHMAP_TRR]=SNDRV_CHMAP_TRR,+[VIRTIO_SND_CHMAP_TRC]=SNDRV_CHMAP_TRC,+[VIRTIO_SND_CHMAP_TFLC]=SNDRV_CHMAP_TFLC,+[VIRTIO_SND_CHMAP_TFRC]=SNDRV_CHMAP_TFRC,+[VIRTIO_SND_CHMAP_TSL]=SNDRV_CHMAP_TSL,+[VIRTIO_SND_CHMAP_TSR]=SNDRV_CHMAP_TSR,+[VIRTIO_SND_CHMAP_LLFE]=SNDRV_CHMAP_LLFE,+[VIRTIO_SND_CHMAP_RLFE]=SNDRV_CHMAP_RLFE,+[VIRTIO_SND_CHMAP_BC]=SNDRV_CHMAP_BC,+[VIRTIO_SND_CHMAP_BLC]=SNDRV_CHMAP_BLC,+[VIRTIO_SND_CHMAP_BRC]=SNDRV_CHMAP_BRC+};++/**+*virtsnd_chmap_parse_cfg()-Parsethechannelmapconfiguration.+*@snd:VirtIOsounddevice.+*+*Thisfunctioniscalledduringinitialdeviceinitialization.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+intvirtsnd_chmap_parse_cfg(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+unsignedinti;+intrc;++virtio_cread(vdev,structvirtio_snd_config,chmaps,&snd->nchmaps);+if(!snd->nchmaps)+return0;++snd->chmaps=devm_kcalloc(&vdev->dev,snd->nchmaps,+sizeof(*snd->chmaps),GFP_KERNEL);+if(!snd->chmaps)+return-ENOMEM;++rc=virtsnd_ctl_query_info(snd,VIRTIO_SND_R_CHMAP_INFO,0,+snd->nchmaps,sizeof(*snd->chmaps),+snd->chmaps);+if(rc)+returnrc;++/* Count the number of channel maps per each PCM device/stream. */+for(i=0;i<snd->nchmaps;++i){+structvirtio_snd_chmap_info*info=&snd->chmaps[i];+unsignedintnid=le32_to_cpu(info->hdr.hda_fn_nid);+structvirtio_pcm*pcm;+structvirtio_pcm_stream*stream;++pcm=virtsnd_pcm_find_or_create(snd,nid);+if(IS_ERR(pcm))+returnPTR_ERR(pcm);++switch(info->direction){+caseVIRTIO_SND_D_OUTPUT:{+stream=&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];+break;+}+caseVIRTIO_SND_D_INPUT:{+stream=&pcm->streams[SNDRV_PCM_STREAM_CAPTURE];+break;+}+default:{+dev_err(&vdev->dev,+"chmap #%u: unknown direction (%u)\n",i,+info->direction);+return-EINVAL;+}+}++stream->nchmaps++;+}++return0;+}++/**+*virtsnd_chmap_add_ctls()-CreateanALSAcontrolforchannelmaps.+*@pcm:ALSAPCMdevice.+*@direction:PCMstreamdirection(SNDRV_PCM_STREAM_XXX).+*@stream:VirtIOPCMstream.+*+*Context:Anycontext.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_chmap_add_ctls(structsnd_pcm*pcm,intdirection,+structvirtio_pcm_stream*stream)+{+unsignedinti;+intmax_channels=0;++for(i=0;i<stream->nchmaps;i++)+if(max_channels<stream->chmaps[i].channels)+max_channels=stream->chmaps[i].channels;++returnsnd_pcm_add_chmap_ctls(pcm,direction,stream->chmaps,+max_channels,0,NULL);+}++/**+*virtsnd_chmap_build_devs()-BuildALSAcontrolsforchannelmaps.+*@snd:VirtIOsounddevice.+*+*Context:Anycontext.+*Return:0onsuccess,-errnoonfailure.+*/+intvirtsnd_chmap_build_devs(structvirtio_snd*snd)+{+structvirtio_device*vdev=snd->vdev;+structvirtio_pcm*pcm;+structvirtio_pcm_stream*stream;+unsignedinti;+intrc;++/* Allocate channel map elements per each PCM device/stream. */+list_for_each_entry(pcm,&snd->pcm_list,list){+for(i=0;i<ARRAY_SIZE(pcm->streams);++i){+stream=&pcm->streams[i];++if(!stream->nchmaps)+continue;++stream->chmaps=devm_kcalloc(&vdev->dev,+stream->nchmaps+1,+sizeof(*stream->chmaps),+GFP_KERNEL);+if(!stream->chmaps)+return-ENOMEM;++stream->nchmaps=0;+}+}++/* Initialize channel maps per each PCM device/stream. */+for(i=0;i<snd->nchmaps;++i){+structvirtio_snd_chmap_info*info=&snd->chmaps[i];+unsignedintnid=le32_to_cpu(info->hdr.hda_fn_nid);+unsignedintchannels=info->channels;+unsignedintch;+structsnd_pcm_chmap_elem*chmap;++pcm=virtsnd_pcm_find(snd,nid);+if(IS_ERR(pcm))+returnPTR_ERR(pcm);++if(info->direction==VIRTIO_SND_D_OUTPUT)+stream=&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];+else+stream=&pcm->streams[SNDRV_PCM_STREAM_CAPTURE];++chmap=&stream->chmaps[stream->nchmaps++];++if(channels>ARRAY_SIZE(chmap->map))+channels=ARRAY_SIZE(chmap->map);++chmap->channels=channels;++for(ch=0;ch<channels;++ch){+u8position=info->positions[ch];++if(position>=ARRAY_SIZE(g_v2a_position_map))+return-EINVAL;++chmap->map[ch]=g_v2a_position_map[position];+}+}++/* Create an ALSA control per each PCM device/stream. */+list_for_each_entry(pcm,&snd->pcm_list,list){+if(!pcm->pcm)+continue;++for(i=0;i<ARRAY_SIZE(pcm->streams);++i){+stream=&pcm->streams[i];++if(!stream->nchmaps)+continue;++rc=virtsnd_chmap_add_ctls(pcm->pcm,i,stream);+if(rc)+returnrc;+}+}++return0;+}
+ * 1. Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in the+ * documentation and/or other materials provided with the distribution.+ * 3. Neither the name of OpenSynergy GmbH nor the names of its contributors+ * may be used to endorse or promote products derived from this software+ * without specific prior written permission.+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR
IBM? Also no idea whether this warranty disclaimer is appropriate here. I thought we were transitioning to those SPDX identifiers to eliminate all these headers.
quoted hunk
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+ * SUCH DAMAGE.+ */
Same here, I think SPDX means you don't need all this here any more.
quoted hunk
+ *+ * This program is distributed in the hope that it will be useful,+ * but WITHOUT ANY WARRANTY; without even the implied warranty of+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ * GNU General Public License for more details.+ *+ * You should have received a copy of the GNU General Public License+ * along with this program; if not, see <http://www.gnu.org/licenses/>.+ */+#include <linux/module.h>+#include <linux/moduleparam.h>+#include <linux/virtio_config.h>+#include <sound/initval.h>+#include <uapi/linux/virtio_ids.h>++#include "virtio_card.h"++static void virtsnd_remove(struct virtio_device *vdev);++/**+ * virtsnd_event_send() - Add an event to the event queue.+ * @vqueue: Underlying event virtqueue.+ * @event: Event.+ * @notify: Indicates whether or not to send a notification to the device.+ * @gfp: Kernel flags for memory allocation.+ *+ * Context: Any context.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_event_send(struct virtqueue *vqueue,+ struct virtio_snd_event *event, bool notify,+ gfp_t gfp)+{+ struct scatterlist sg;+ struct scatterlist *psgs[1] = { &sg };+ int rc;++ /* reset event content */+ memset(event, 0, sizeof(*event));++ sg_init_one(&sg, event, sizeof(*event));++ rc = virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp);+ if (rc)+ return rc;++ if (notify)+ if (virtqueue_kick_prepare(vqueue))+ if (!virtqueue_notify(vqueue))+ return -EIO;++ return 0;+}++/**+ * virtsnd_event_notify_cb() - Dispatch all reported events from the event queue.+ * @vqueue: Underlying event virtqueue.+ *+ * This callback function is called upon a vring interrupt request from the+ * device.+ *+ * Context: Interrupt context.+ */+static void virtsnd_event_notify_cb(struct virtqueue *vqueue)+{+ struct virtio_snd *snd = vqueue->vdev->priv;+ struct virtio_snd_queue *queue = virtsnd_event_queue(snd);+ unsigned long flags;++ spin_lock_irqsave(&queue->lock, flags);+ while (queue->vqueue) {+ virtqueue_disable_cb(queue->vqueue);++ for (;;) {+ struct virtio_snd_event *event;+ u32 length;++ event = virtqueue_get_buf(queue->vqueue, &length);+ if (!event)+ break;++ virtsnd_event_send(queue->vqueue, event, true,+ GFP_ATOMIC);+ }++ if (unlikely(virtqueue_is_broken(queue->vqueue)))+ break;++ if (virtqueue_enable_cb(queue->vqueue))+ break;+ }+ spin_unlock_irqrestore(&queue->lock, flags);+}++/**+ * virtsnd_find_vqs() - Enumerate and initialize all virtqueues.+ * @snd: VirtIO sound device.+ *+ * After calling this function, the event queue is disabled.+ *+ * Context: Any context.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_find_vqs(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { 0 };+ const char *names[VIRTIO_SND_VQ_MAX] = {+ [VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl",+ [VIRTIO_SND_VQ_EVENT] = "virtsnd-event",+ [VIRTIO_SND_VQ_TX] = "virtsnd-tx",+ [VIRTIO_SND_VQ_RX] = "virtsnd-rx"+ };+ struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 };+ unsigned int i;+ unsigned int n = 0;+ int rc;++ callbacks[VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb;++ rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names,+ NULL);+ if (rc) {+ dev_err(&vdev->dev, "failed to initialize virtqueues\n");+ return rc;+ }++ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)+ snd->queues[i].vqueue = vqs[i];++ /* Allocate events and populate the event queue */+ virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]);++ n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]);++ snd->event_msgs = devm_kcalloc(&vdev->dev, n, sizeof(*snd->event_msgs),+ GFP_KERNEL);+ if (!snd->event_msgs)+ return -ENOMEM;++ for (i = 0; i < n; ++i) {+ rc = virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT],+ &snd->event_msgs[i], false, GFP_KERNEL);+ if (rc)+ return rc;+ }++ return 0;+}++/**+ * virtsnd_enable_event_vq() - Enable the event virtqueue.+ * @snd: VirtIO sound device.+ *+ * Context: Any context.+ */+static void virtsnd_enable_event_vq(struct virtio_snd *snd)+{+ struct virtio_snd_queue *queue = virtsnd_event_queue(snd);++ if (!virtqueue_enable_cb(queue->vqueue))+ virtsnd_event_notify_cb(queue->vqueue);+}++/**+ * virtsnd_disable_vqs() - Disable all virtqueues.+ * @snd: VirtIO sound device.+ *+ * Also free all allocated events and control messages.+ *+ * Context: Any context.+ */+static void virtsnd_disable_vqs(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ unsigned int i;+ unsigned long flags;++ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) {+ struct virtio_snd_queue *queue = &snd->queues[i];++ spin_lock_irqsave(&queue->lock, flags);+ /* Prohibit the use of the queue */+ if (queue->vqueue)+ virtqueue_disable_cb(queue->vqueue);+ queue->vqueue = NULL;+ spin_unlock_irqrestore(&queue->lock, flags);+ }++ if (snd->event_msgs)
Check not needed, kfree(NULL) is ok.
+ devm_kfree(&vdev->dev, snd->event_msgs);
I think there are very few cases when managed resources have to be explicitly freed. If explicit freeing is always required, then there's no need to have them managed. If there's a clear case for managed resources, usually you don't need to free them explicitly. Here.event_msgs are allocated in virtsnd_find_vqs() above, which is only called during probing. And this function is only called during release. So, I'd assume, that you don't need to free memory explicitly here.
+
+ snd->event_msgs = NULL;
snd is about to be freed, so do you really need this?
quoted hunk
+}++/**+ * virtsnd_reset_fn() - Kernel worker's function to reset the device.+ * @work: Reset device work.+ *+ * Context: Process context.+ */+static void virtsnd_reset_fn(struct work_struct *work)+{+ struct virtio_snd *snd =+ container_of(work, struct virtio_snd, reset_work);+ struct virtio_device *vdev = snd->vdev;+ struct device *dev = &vdev->dev;+ int rc;++ dev_info(dev, "sound device needs reset\n");++ /*+ * It seems that the only way to properly reset the device is to remove+ * and re-create the ALSA sound card device.+ *+ * Also resetting the device involves a number of steps with setting the+ * status bits described in the virtio specification. And the easiest+ * way to get everything right is to use the virtio bus interface.+ */+ rc = dev->bus->remove(dev);+ if (rc)+ dev_warn(dev, "bus->remove() failed: %d", rc);++ rc = dev->bus->probe(dev);+ if (rc)+ dev_err(dev, "bus->probe() failed: %d", rc);
This looks very suspicious to me. Wondering what ALSA maintainers will say to this.
quoted hunk
+}++/**+ * virtsnd_build_devs() - Read configuration and build ALSA devices.+ * @snd: VirtIO sound device.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_build_devs(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ int rc;++ rc = snd_card_new(&vdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,+ THIS_MODULE, 0, &snd->card);+ if (rc < 0)+ return rc;++ snd->card->private_data = snd;++ strscpy(snd->card->id, "viosnd", sizeof(snd->card->id));+ strscpy(snd->card->driver, "virtio_snd", sizeof(snd->card->driver));+ strscpy(snd->card->shortname, "VIOSND", sizeof(snd->card->shortname));+ strscpy(snd->card->longname, "VirtIO Sound Card",+ sizeof(snd->card->longname));++ return snd_card_register(snd->card);+}++/**+ * virtsnd_validate() - Validate if the device can be started.+ * @vdev: VirtIO parent device.+ *+ * Context: Any context.+ * Return: 0 on success, -EINVAL on failure.+ */+static int virtsnd_validate(struct virtio_device *vdev)+{+ if (!vdev->config->get) {+ dev_err(&vdev->dev, "configuration access disabled\n");+ return -EINVAL;+ }++ if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {+ dev_err(&vdev->dev,+ "device does not comply with spec version 1.x\n");+ return -EINVAL;+ }++ return 0;+}++/**+ * virtsnd_probe() - Create and initialize the device.+ * @vdev: VirtIO parent device.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_probe(struct virtio_device *vdev)+{+ struct virtio_snd *snd;+ unsigned int i;+ int rc;++ snd = devm_kzalloc(&vdev->dev, sizeof(*snd), GFP_KERNEL);+ if (!snd)+ return -ENOMEM;++ snd->vdev = vdev;+ INIT_WORK(&snd->reset_work, virtsnd_reset_fn);++ vdev->priv = snd;++ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i)+ spin_lock_init(&snd->queues[i].lock);++ rc = virtsnd_find_vqs(snd);+ if (rc)+ goto on_failure;++ virtio_device_ready(vdev);++ rc = virtsnd_build_devs(snd);+ if (rc)+ goto on_failure;++ virtsnd_enable_event_vq(snd);++on_failure:+ if (rc)+ virtsnd_remove(vdev);++ return rc;+}++/**+ * virtsnd_remove() - Remove VirtIO and ALSA devices.+ * @vdev: VirtIO parent device.+ *+ * Context: Any context that permits to sleep.+ */+static void virtsnd_remove(struct virtio_device *vdev)+{+ struct virtio_snd *snd = vdev->priv;++ if (!snd)+ return;++ /*+ * Make sure no one is accessing the virtqueues and sending synchronous+ * requests to the device. This can happen if we got here because the+ * device needs to be reset.+ */+ virtsnd_disable_vqs(snd);++ if (snd->card)+ snd_card_free(snd->card);++ vdev->config->reset(vdev);+ vdev->config->del_vqs(vdev);++ devm_kfree(&vdev->dev, snd);
No need for this.
+
+ vdev->priv = NULL;
and for this either.
quoted hunk
+}++/**+ * virtsnd_config_changed() - Handle configuration change notification.+ * @vdev: VirtIO parent device.+ *+ * This callback function is called upon a configuration change interrupt+ * request from the device. Currently only used to handle NEEDS_RESET device+ * status.+ *+ * Context: Interrupt context.+ */+static void virtsnd_config_changed(struct virtio_device *vdev)+{+ struct virtio_snd *snd = vdev->priv;+ unsigned int status = vdev->config->get_status(vdev);++ if (status & VIRTIO_CONFIG_S_NEEDS_RESET)+ schedule_work(&snd->reset_work);+ else+ dev_warn(&vdev->dev,+ "sound device configuration was changed\n");+}++static const struct virtio_device_id id_table[] = {+ { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID },+ { 0 },+};++static struct virtio_driver virtsnd_driver = {+ .driver.name = KBUILD_MODNAME,+ .driver.owner = THIS_MODULE,+ .id_table = id_table,+ .validate = virtsnd_validate,+ .probe = virtsnd_probe,+ .remove = virtsnd_remove,+ .config_changed = virtsnd_config_changed,+};++static int __init init(void)+{+ return register_virtio_driver(&virtsnd_driver);+}+module_init(init);++static void __exit fini(void)+{+ unregister_virtio_driver(&virtsnd_driver);+}+module_exit(fini);++MODULE_DEVICE_TABLE(virtio, id_table);+MODULE_DESCRIPTION("Virtio sound card driver");+MODULE_LICENSE("GPL");
I think the use of (devm_)kmalloc() and friends needs some refinement in several patches in the series.
On Sun, 24 Jan 2021, Anton Yakovlev wrote:
The control queue can be used by different parts of the driver to send
commands to the device. Control messages can be either synchronous or
asynchronous. The lifetime of a message is controlled by a reference
count.
Introduce a module parameter to set the message completion timeout:
msg_timeout_ms [=1000]
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 3 +-
sound/virtio/virtio_card.c | 20 +++
sound/virtio/virtio_card.h | 7 +
sound/virtio/virtio_ctl_msg.c | 293 ++++++++++++++++++++++++++++++++++
sound/virtio/virtio_ctl_msg.h | 122 ++++++++++++++
5 files changed, 444 insertions(+), 1 deletion(-)
create mode 100644 sound/virtio/virtio_ctl_msg.c
create mode 100644 sound/virtio/virtio_ctl_msg.h
Same comment about licence, and in other patches as well.
quoted hunk
+ * it under the terms of the GNU General Public License as published by+ * the Free Software Foundation; either version 2 of the License, or+ * (at your option) any later version.+ *+ * This program is distributed in the hope that it will be useful,+ * but WITHOUT ANY WARRANTY; without even the implied warranty of+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ * GNU General Public License for more details.+ *+ * You should have received a copy of the GNU General Public License+ * along with this program; if not, see <http://www.gnu.org/licenses/>.+ */+#include <linux/moduleparam.h>+#include <linux/virtio_config.h>++#include "virtio_card.h"+#include "virtio_ctl_msg.h"++/**+ * virtsnd_ctl_msg_alloc_ext() - Allocate and initialize a control message.+ * @vdev: VirtIO parent device.+ * @request_size: Size of request header (pointed to by sg_request field).+ * @response_size: Size of response header (pointed to by sg_response field).+ * @sgs: Additional data to attach to the message (may be NULL).+ * @out_sgs: Number of scattergather elements to attach to the request header.+ * @in_sgs: Number of scattergather elements to attach to the response header.+ * @gfp: Kernel flags for memory allocation.+ *+ * The message will be automatically freed when the ref_count value is 0.+ *+ * Context: Any context. May sleep if @gfp flags permit.+ * Return: Allocated message on success, ERR_PTR(-errno) on failure.+ */+struct virtio_snd_msg *virtsnd_ctl_msg_alloc_ext(struct virtio_device *vdev,+ size_t request_size,+ size_t response_size,+ struct scatterlist *sgs,+ unsigned int out_sgs,+ unsigned int in_sgs, gfp_t gfp)+{+ struct virtio_snd_msg *msg;+ size_t msg_size =+ sizeof(*msg) + (1 + out_sgs + 1 + in_sgs) * sizeof(*msg->sgs);+ unsigned int i;++ msg = devm_kzalloc(&vdev->dev, msg_size + request_size + response_size,+ gfp);
Messages are short-lived, right? So, I think their allocation and freeing has to be explicit, no need for devm_.
quoted hunk
+ if (!msg)+ return ERR_PTR(-ENOMEM);++ sg_init_one(&msg->sg_request, (u8 *)msg + msg_size, request_size);+ sg_init_one(&msg->sg_response, (u8 *)msg + msg_size + request_size,+ response_size);++ INIT_LIST_HEAD(&msg->list);+ init_completion(&msg->notify);+ atomic_set(&msg->ref_count, 1);++ msg->sgs[msg->out_sgs++] = &msg->sg_request;+ if (sgs)+ for (i = 0; i < out_sgs; ++i)+ msg->sgs[msg->out_sgs++] = &sgs[i];++ msg->sgs[msg->out_sgs + msg->in_sgs++] = &msg->sg_response;+ if (sgs)+ for (i = out_sgs; i < out_sgs + in_sgs; ++i)+ msg->sgs[msg->out_sgs + msg->in_sgs++] = &sgs[i];++ return msg;+}++/**+ * virtsnd_ctl_msg_send() - Send an (asynchronous) control message.+ * @snd: VirtIO sound device.+ * @msg: Control message.+ *+ * If a message is failed to be enqueued, it will be deleted. If message content+ * is still needed, the caller must additionally to virtsnd_ctl_msg_ref/unref()+ * it.+ *+ * Context: Any context. Takes and releases the control queue spinlock.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_snd_queue *queue = virtsnd_control_queue(snd);+ struct virtio_snd_hdr *response = sg_virt(&msg->sg_response);+ bool notify = false;+ unsigned long flags;+ int rc = -EIO;++ /* Set the default status in case the message was not sent or was+ * canceled.+ */+ response->code = cpu_to_virtio32(vdev, VIRTIO_SND_S_IO_ERR);++ spin_lock_irqsave(&queue->lock, flags);+ if (queue->vqueue) {
+}++/**+ * virtsnd_ctl_msg_send_sync() - Send a (synchronous) control message.+ * @snd: VirtIO sound device.+ * @msg: Control message.+ *+ * After returning from this function, the message will be deleted. If message+ * content is still needed, the caller must additionally to+ * virtsnd_ctl_msg_ref/unref() it.+ *+ * The msg_timeout_ms module parameter defines the message completion timeout.+ * If the message is not completed within this time, the function will return an+ * error.+ *+ * Context: Any context. Takes and releases the control queue spinlock.+ * Return: 0 on success, -errno on failure.+ *+ * The return value is a message status code (VIRTIO_SND_S_XXX) converted to an+ * appropriate -errno value.+ */+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,+ struct virtio_snd_msg *msg)+{+ struct virtio_device *vdev = snd->vdev;+ unsigned int js = msecs_to_jiffies(msg_timeout_ms);+ struct virtio_snd_hdr *response;+ int rc;++ virtsnd_ctl_msg_ref(vdev, msg);++ rc = virtsnd_ctl_msg_send(snd, msg);+ if (rc)+ goto on_failure;++ rc = wait_for_completion_interruptible_timeout(&msg->notify, js);+ if (rc <= 0) {+ if (!rc) {+ struct virtio_snd_hdr *request =+ sg_virt(&msg->sg_request);++ dev_err(&vdev->dev,+ "control message (0x%08x) timeout\n",+ le32_to_cpu(request->code));+ rc = -EIO;
Wouldn't -ETIMEDOUT be better here?
quoted hunk
+ }++ goto on_failure;+ }++ response = sg_virt(&msg->sg_response);++ switch (le32_to_cpu(response->code)) {+ case VIRTIO_SND_S_OK:+ rc = 0;+ break;+ case VIRTIO_SND_S_BAD_MSG:+ rc = -EINVAL;+ break;+ case VIRTIO_SND_S_NOT_SUPP:+ rc = -EOPNOTSUPP;+ break;+ case VIRTIO_SND_S_IO_ERR:+ rc = -EIO;+ break;+ default:+ rc = -EPERM;
any special reason for EPERM as a default error code? I think often EINVAL is used in similar cases.
quoted hunk
+ break;+ }++on_failure:
cosmetic: this path is also taken on success, so maybe better just call the lable "exit" or similar.
quoted hunk
+ virtsnd_ctl_msg_unref(vdev, msg);++ return rc;+}++/**+ * virtsnd_ctl_msg_complete() - Complete a control message.+ * @snd: VirtIO sound device.+ * @msg: Control message.+ *+ * Context: Any context.+ */+void virtsnd_ctl_msg_complete(struct virtio_snd *snd,+ struct virtio_snd_msg *msg)+{+ list_del(&msg->list);+ complete(&msg->notify);++ virtsnd_ctl_msg_unref(snd->vdev, msg);+}++/**+ * virtsnd_ctl_query_info() - Query the item configuration from the device.+ * @snd: VirtIO sound device.+ * @command: Control request code (VIRTIO_SND_R_XXX_INFO).+ * @start_id: Item start identifier.+ * @count: Item count to query.+ * @size: Item information size in bytes.+ * @info: Buffer for storing item information.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_id,+ int count, size_t size, void *info)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_snd_msg *msg;+ struct virtio_snd_query_info *query;+ struct scatterlist sg;++ sg_init_one(&sg, info, count * size);++ msg = virtsnd_ctl_msg_alloc_ext(vdev, sizeof(*query),+ sizeof(struct virtio_snd_hdr), &sg, 0,+ 1, GFP_KERNEL);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ query = sg_virt(&msg->sg_request);+ query->hdr.code = cpu_to_virtio32(vdev, command);+ query->start_id = cpu_to_virtio32(vdev, start_id);+ query->count = cpu_to_virtio32(vdev, count);+ query->size = cpu_to_virtio32(vdev, size);++ return virtsnd_ctl_msg_send_sync(snd, msg);+}++/**+ * virtsnd_ctl_notify_cb() - Process all completed control messages.+ * @vqueue: Underlying control virtqueue.+ *+ * This callback function is called upon a vring interrupt request from the+ * device.+ *+ * Context: Interrupt context. Takes and releases the control queue spinlock.+ */+void virtsnd_ctl_notify_cb(struct virtqueue *vqueue)+{+ struct virtio_snd *snd = vqueue->vdev->priv;+ struct virtio_snd_queue *queue = virtsnd_control_queue(snd);+ unsigned long flags;++ spin_lock_irqsave(&queue->lock, flags);+ while (queue->vqueue) {+ virtqueue_disable_cb(queue->vqueue);++ for (;;) {+ struct virtio_snd_msg *msg;+ u32 length;++ msg = virtqueue_get_buf(queue->vqueue, &length);+ if (!msg)+ break;++ virtsnd_ctl_msg_complete(snd, msg);+ }++ if (unlikely(virtqueue_is_broken(queue->vqueue)))+ break;++ if (virtqueue_enable_cb(queue->vqueue))+ break;+ }+ spin_unlock_irqrestore(&queue->lock, flags);+}
@@ -0,0 +1,122 @@+/* SPDX-License-Identifier: GPL-2.0+ */+/*+*Soundcarddriverforvirtio+*Copyright(C)2020OpenSynergyGmbH+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,see<http://www.gnu.org/licenses/>.+*/+#ifndef VIRTIO_SND_MSG_H+#define VIRTIO_SND_MSG_H++#include<linux/atomic.h>+#include<linux/virtio.h>++structvirtio_snd;++/**+*structvirtio_snd_msg-Controlmessage.+*@sg_request:Scattergatherelementcontainingadevicerequest(header).+*@sg_response:Scattergatherelementcontainingadeviceresponse(status).+*@list:Pendingmessagelistentry.+*@notify:Requestcompletednotification.+*@ref_count:Referencecountusedtomanageamessagelifetime.+*@out_sgs:Numberofread-onlysgelementsinthesgsarray.+*@in_sgs:Numberofwrite-onlysgelementsinthesgsarray.+*@sgs:Arrayofsgelementstoaddtothecontrolvirtqueue.+*/+structvirtio_snd_msg{+/* public: */+structscatterlistsg_request;+structscatterlistsg_response;+/* private: internal use only */+structlist_headlist;+structcompletionnotify;+atomic_tref_count;+unsignedintout_sgs;+unsignedintin_sgs;+structscatterlist*sgs[0];+};++/**+*virtsnd_ctl_msg_ref()-Incrementreferencecounterforthemessage.+*@vdev:VirtIOparentdevice.+*@msg:Controlmessage.+*+*Context:Anycontext.+*/+staticinlinevoidvirtsnd_ctl_msg_ref(structvirtio_device*vdev,+structvirtio_snd_msg*msg)+{+atomic_inc(&msg->ref_count);+}++/**+*virtsnd_ctl_msg_unref()-Decrementreferencecounterforthemessage.+*@vdev:VirtIOparentdevice.+*@msg:Controlmessage.+*+*Themessagewillbefreedwhentheref_countvalueis0.+*+*Context:Anycontext.+*/+staticinlinevoidvirtsnd_ctl_msg_unref(structvirtio_device*vdev,+structvirtio_snd_msg*msg)+{+if(!atomic_dec_return(&msg->ref_count))
Since you use atomic operations, this function can probably be called with no additional locking right? But if so, couldn't it be preempted here between the check and the call to kfree()? As was mentioned in a previous review, the use of atomic operations in this series has to be very carefully examined...
Thanks
Guennadi
+ devm_kfree(&vdev->dev, msg);
+}
+
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc_ext(struct virtio_device *vdev,
+ size_t request_size,
+ size_t response_size,
+ struct scatterlist *sgs,
+ unsigned int out_sgs,
+ unsigned int in_sgs,
+ gfp_t gfp);
+
+/**
+ * virtsnd_ctl_msg_alloc() - Simplified control message allocation.
+ * @vdev: VirtIO parent device.
+ * @request_size: Size of request header (pointed to by sg_request field).
+ * @response_size: Size of response header (pointed to by sg_response field).
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated message on success, ERR_PTR(-errno) on failure.
+ */
+static inline
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc(struct virtio_device *vdev,
+ size_t request_size,
+ size_t response_size, gfp_t gfp)
+{
+ return virtsnd_ctl_msg_alloc_ext(vdev, request_size, response_size,
+ NULL, 0, 0, gfp);
+}
+
+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct virtio_snd_msg *msg);
+
+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,
+ struct virtio_snd_msg *msg);
+
+void virtsnd_ctl_msg_complete(struct virtio_snd *snd,
+ struct virtio_snd_msg *msg);
+
+int virtsnd_ctl_query_info(struct virtio_snd *snd, int command, int start_id,
+ int count, size_t size, void *info);
+
+void virtsnd_ctl_notify_cb(struct virtqueue *vqueue);
+
+#endif /* VIRTIO_SND_MSG_H */
--
2.30.0
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
The driver implements a message-based transport for I/O substream
operations. Before the start of the substream, the hardware buffer is
sliced into I/O messages, the number of which is equal to the current
number of periods. The size of each message is equal to the current
size of one period.
I/O messages are organized in an ordered queue. The completion of the
I/O message indicates an elapsed period (the only exception is the end
of the stream for the capture substream). Upon completion, the message
is automatically re-added to the end of the queue.
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 3 +-
sound/virtio/virtio_card.c | 10 ++
sound/virtio/virtio_card.h | 9 +
sound/virtio/virtio_pcm.c | 3 +
sound/virtio/virtio_pcm.h | 31 ++++
sound/virtio/virtio_pcm_msg.c | 325 ++++++++++++++++++++++++++++++++++
6 files changed, 380 insertions(+), 1 deletion(-)
create mode 100644 sound/virtio/virtio_pcm_msg.c
@@ -177,6 +183,10 @@ static int virtsnd_find_vqs(struct virtio_snd *snd) * virtsnd_enable_event_vq() - Enable the event virtqueue. * @snd: VirtIO sound device. *+ * The tx queue is enabled only if the device supports playback stream(s).+ *+ * The rx queue is enabled only if the device supports capture stream(s).+ * * Context: Any context. */
break;
}
case VIRTIO_SND_EVT_PCM_XRUN: {
+ if (atomic_read(&substream->xfer_enabled))
Why does .xfer_enabled have to be atomic? It only takes two values - 0 and 1, I don't see any incrementing, or test-and-set type operations or anything similar. Also I don't see .xfer_enabled being set to 1 anywhere in this patch, presumably that happens in one of later patches.
@@ -34,6 +35,16 @@ struct virtio_pcm; * @features: Stream VirtIO feature bit map (1 << VIRTIO_SND_PCM_F_XXX). * @substream: Kernel ALSA substream. * @hw: Kernel ALSA substream hardware descriptor.+ * @frame_bytes: Current frame size in bytes.+ * @period_size: Current period size in frames.+ * @buffer_size: Current buffer size in frames.+ * @hw_ptr: Substream hardware pointer value in frames [0 ... buffer_size).+ * @xfer_enabled: Data transfer state (0 - off, 1 - on).+ * @xfer_xrun: Data underflow/overflow state (0 - no xrun, 1 - xrun).+ * @msgs: I/O messages.+ * @msg_last_enqueued: Index of the last I/O message added to the virtqueue.+ * @msg_count: Number of pending I/O messages in the virtqueue.+ * @msg_empty: Notify when msg_count is zero. */
If I understand correctly, messages are sent to the back-end driver in this specific order, so this is a part of the ABI, isn't it? Is it also a part of the spec? If so this should be defined in your ABI header?
quoted hunk
++/**+ * struct virtio_pcm_msg - VirtIO I/O message.+ * @substream: VirtIO PCM substream.+ * @xfer: Request header payload.+ * @status: Response header payload.+ * @sgs: Payload scatter-gather table.+ */+struct virtio_pcm_msg {+ struct virtio_pcm_substream *substream;+ struct virtio_snd_pcm_xfer xfer;+ struct virtio_snd_pcm_status status;+ struct scatterlist sgs[PCM_MSG_SG_MAX];+};++/**+ * virtsnd_pcm_msg_alloc() - Allocate I/O messages.+ * @substream: VirtIO PCM substream.+ * @nmsg: Number of messages (equal to the number of periods).+ * @dma_area: Pointer to used audio buffer.+ * @period_bytes: Period (message payload) size.+ *+ * The function slices the buffer into nmsg parts (each with the size of+ * period_bytes), and creates nmsg corresponding I/O messages.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -ENOMEM on failure.+ */+int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *substream,+ unsigned int nmsg, u8 *dma_area,+ unsigned int period_bytes)+{+ struct virtio_device *vdev = substream->snd->vdev;+ unsigned int i;++ if (substream->msgs)+ devm_kfree(&vdev->dev, substream->msgs);++ substream->msgs = devm_kcalloc(&vdev->dev, nmsg,+ sizeof(*substream->msgs), GFP_KERNEL);+ if (!substream->msgs)+ return -ENOMEM;++ for (i = 0; i < nmsg; ++i) {+ struct virtio_pcm_msg *msg = &substream->msgs[i];++ msg->substream = substream;++ sg_init_table(msg->sgs, PCM_MSG_SG_MAX);
Why do you need to initialise a table of 3 meddages if you then initialise each of them separately immediately below?
quoted hunk
+ sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], &msg->xfer,+ sizeof(msg->xfer));+ sg_init_one(&msg->sgs[PCM_MSG_SG_DATA],+ dma_area + period_bytes * i, period_bytes);+ sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status,+ sizeof(msg->status));+ }++ return 0;+}++/**+ * virtsnd_pcm_msg_send() - Send asynchronous I/O messages.+ * @substream: VirtIO PCM substream.+ *+ * All messages are organized in an ordered circular list. Each time the+ * function is called, all currently non-enqueued messages are added to the+ * virtqueue. For this, the function keeps track of two values:+ *+ * msg_last_enqueued = index of the last enqueued message,+ * msg_count = # of pending messages in the virtqueue.+ *+ * Context: Any context.+ * Return: 0 on success, -EIO on failure.+ */+int virtsnd_pcm_msg_send(struct virtio_pcm_substream *substream)+{+ struct snd_pcm_runtime *runtime = substream->substream->runtime;+ struct virtio_snd *snd = substream->snd;+ struct virtio_device *vdev = snd->vdev;+ struct virtqueue *vqueue = virtsnd_pcm_queue(substream)->vqueue;+ int i;+ int n;+ bool notify = false;++ if (!vqueue)+ return -EIO;
Is this actually possible? That would mean a data corruption or a bug in the driver, right? In either case it can be NULL or 1 or any other invalid value, so checking for NULL doesn't seem to help a lot?
quoted hunk
++ i = (substream->msg_last_enqueued + 1) % runtime->periods;+ n = runtime->periods - atomic_read(&substream->msg_count);++ for (; n; --n, i = (i + 1) % runtime->periods) {+ struct virtio_pcm_msg *msg = &substream->msgs[i];+ struct scatterlist *psgs[PCM_MSG_SG_MAX] = {+ [PCM_MSG_SG_XFER] = &msg->sgs[PCM_MSG_SG_XFER],+ [PCM_MSG_SG_DATA] = &msg->sgs[PCM_MSG_SG_DATA],+ [PCM_MSG_SG_STATUS] = &msg->sgs[PCM_MSG_SG_STATUS]+ };+ int rc;++ msg->xfer.stream_id = cpu_to_virtio32(vdev, substream->sid);+ memset(&msg->status, 0, sizeof(msg->status));++ atomic_inc(&substream->msg_count);
.msg_count is also accessed in virtsnd_pcm_msg_complete() which is why presumably you use atomic access. But here you already increment the count before you even begin adding the message to the virtqueue. So if virtsnd_pcm_msg_complete() preempts you here the .msg_count will be inconsistent? Possibly you need to protect both operations together: incrementing the counter and adding messages to queues.
quoted hunk
++ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK)+ rc = virtqueue_add_sgs(vqueue, psgs, 2, 1, msg,+ GFP_ATOMIC);+ else+ rc = virtqueue_add_sgs(vqueue, psgs, 1, 2, msg,+ GFP_ATOMIC);++ if (rc) {+ atomic_dec(&substream->msg_count);+ return -EIO;+ }++ substream->msg_last_enqueued = i;+ }++ if (!(substream->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING)))+ notify = virtqueue_kick_prepare(vqueue);++ if (notify)+ if (!virtqueue_notify(vqueue))+ return -EIO;++ return 0;+}++/**+ * virtsnd_pcm_msg_complete() - Complete an I/O message.+ * @msg: I/O message.+ * @size: Number of bytes written.+ *+ * Completion of the message means the elapsed period.+ *+ * The interrupt handler modifies three fields of the substream structure+ * (hw_ptr, xfer_xrun, msg_count) that are used in operator functions. These+ * values are atomic to avoid frequent interlocks with the interrupt handler.+ * This becomes especially important in the case of multiple running substreams+ * that share both the virtqueue and interrupt handler.+ *+ * Context: Interrupt context.+ */+static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg, size_t size)+{+ struct virtio_pcm_substream *substream = msg->substream;+ snd_pcm_uframes_t hw_ptr;+ unsigned int msg_count;++ /*+ * hw_ptr always indicates the buffer position of the first I/O message+ * in the virtqueue. Therefore, on each completion of an I/O message,+ * the hw_ptr value is unconditionally advanced.+ */+ hw_ptr = (snd_pcm_uframes_t)atomic_read(&substream->hw_ptr);
Also unclear why this has to be atomic, especially taking into account that it's only accessed in "interrupt context."
quoted hunk
++ /*+ * If the capture substream returned an incorrect status, then just+ * increase the hw_ptr by the period size.+ */+ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK ||+ size <= sizeof(msg->status)) {+ hw_ptr += substream->period_size;+ } else {+ size -= sizeof(msg->status);+ hw_ptr += size / substream->frame_bytes;+ }++ atomic_set(&substream->hw_ptr, (u32)(hw_ptr % substream->buffer_size));+ atomic_set(&substream->xfer_xrun, 0);++ msg_count = atomic_dec_return(&substream->msg_count);++ if (atomic_read(&substream->xfer_enabled)) {+ struct snd_pcm_runtime *runtime = substream->substream->runtime;++ runtime->delay =+ bytes_to_frames(runtime,+ le32_to_cpu(msg->status.latency_bytes));++ snd_pcm_period_elapsed(substream->substream);++ virtsnd_pcm_msg_send(substream);+ } else if (!msg_count) {+ wake_up_all(&substream->msg_empty);+ }+}
+/**+ * virtsnd_pcm_release() - Release the PCM substream on the device side.+ * @substream: VirtIO substream.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+static inline bool virtsnd_pcm_released(struct virtio_pcm_substream *substream)+{+ /*+ * The spec states that upon receipt of the RELEASE command "the device+ * MUST complete all pending I/O messages for the specified stream ID".+ * Thus, we consider the absence of I/O messages in the queue as an+ * indication that the substream has been released.+ */+ return atomic_read(&substream->msg_count) == 0;
Also here having it atomic doesn't really seem to help. This just means, that at some point of time it was == 0.
quoted hunk
+}++static int virtsnd_pcm_release(struct virtio_pcm_substream *substream)
kernel-doc missing
quoted hunk
+{+ struct virtio_snd *snd = substream->snd;+ struct virtio_snd_msg *msg;+ unsigned int js = msecs_to_jiffies(msg_timeout_ms);+ int rc;++ msg = virtsnd_pcm_ctl_msg_alloc(substream, VIRTIO_SND_R_PCM_RELEASE,+ GFP_KERNEL);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ rc = virtsnd_ctl_msg_send_sync(snd, msg);+ if (rc)+ return rc;++ return wait_event_interruptible_timeout(substream->msg_empty,+ virtsnd_pcm_released(substream),+ js);+}++/**+ * virtsnd_pcm_open() - Open the PCM substream.+ * @substream: Kernel ALSA substream.+ *+ * Context: Any context.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_pcm_open(struct snd_pcm_substream *substream)+{+ struct virtio_pcm *pcm = snd_pcm_substream_chip(substream);+ struct virtio_pcm_substream *ss = NULL;++ if (pcm) {+ switch (substream->stream) {+ case SNDRV_PCM_STREAM_PLAYBACK:+ case SNDRV_PCM_STREAM_CAPTURE: {+ struct virtio_pcm_stream *stream =+ &pcm->streams[substream->stream];++ if (substream->number < stream->nsubstreams)
Can this condition ever be false?
quoted hunk
+ ss = stream->substreams[substream->number];+ break;+ }+ }+ }++ if (!ss)+ return -EBADFD;++ substream->runtime->hw = ss->hw;+ substream->private_data = ss;++ return 0;+}++/**+ * virtsnd_pcm_close() - Close the PCM substream.+ * @substream: Kernel ALSA substream.+ *+ * Context: Any context.+ * Return: 0.+ */+static int virtsnd_pcm_close(struct snd_pcm_substream *substream)+{+ return 0;+}++/**+ * virtsnd_pcm_hw_params() - Set the parameters of the PCM substream.+ * @substream: Kernel ALSA substream.+ * @hw_params: Hardware parameters (can be NULL).+ *+ * The function can be called both from the upper level (in this case,+ * @hw_params is not NULL) or from the driver itself (in this case, @hw_params+ * is NULL, and the parameter values are taken from the runtime structure).+ *+ * In all cases, the function:+ * 1. checks the state of the virtqueue and, if necessary, tries to fix it,+ * 2. sets the parameters on the device side,+ * 3. allocates a hardware buffer and I/O messages.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,+ struct snd_pcm_hw_params *hw_params)+{+ struct snd_pcm_runtime *runtime = substream->runtime;+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);+ struct virtio_device *vdev = ss->snd->vdev;+ struct virtio_snd_msg *msg;+ struct virtio_snd_pcm_set_params *request;+ snd_pcm_format_t format;+ unsigned int channels;+ unsigned int rate;+ unsigned int buffer_bytes;+ unsigned int period_bytes;+ unsigned int periods;+ unsigned int i;+ int vformat = -1;+ int vrate = -1;+ int rc;++ /*+ * If we got here after ops->trigger() was called, the queue may+ * still contain messages. In this case, we need to release the+ * substream first.+ */+ if (atomic_read(&ss->msg_count)) {+ rc = virtsnd_pcm_release(ss);+ if (rc) {+ dev_err(&vdev->dev,+ "SID %u: invalid I/O queue state\n",+ ss->sid);+ return rc;+ }+ }++ /* Set hardware parameters in device */+ if (hw_params) {+ format = params_format(hw_params);+ channels = params_channels(hw_params);+ rate = params_rate(hw_params);+ buffer_bytes = params_buffer_bytes(hw_params);+ period_bytes = params_period_bytes(hw_params);+ periods = params_periods(hw_params);+ } else {+ format = runtime->format;+ channels = runtime->channels;+ rate = runtime->rate;+ buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size);+ period_bytes = frames_to_bytes(runtime, runtime->period_size);+ periods = runtime->periods;+ }++ for (i = 0; i < ARRAY_SIZE(g_a2v_format_map); ++i)+ if (g_a2v_format_map[i].alsa_bit == format) {+ vformat = g_a2v_format_map[i].vio_bit;++ break;+ }++ for (i = 0; i < ARRAY_SIZE(g_a2v_rate_map); ++i)+ if (g_a2v_rate_map[i].rate == rate) {+ vrate = g_a2v_rate_map[i].vio_bit;++ break;+ }++ if (vformat == -1 || vrate == -1)+ return -EINVAL;++ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_SET_PARAMS,+ GFP_KERNEL);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ request = sg_virt(&msg->sg_request);++ request->buffer_bytes = cpu_to_virtio32(vdev, buffer_bytes);+ request->period_bytes = cpu_to_virtio32(vdev, period_bytes);+ request->channels = channels;+ request->format = vformat;+ request->rate = vrate;
I presume the latter three fields don't have to be endienness-converted, perhaps they're 8-bit wide only.
Wouldn't it be better to only try to send the message after below allocations completed successfully?
quoted hunk
+ if (rc)+ return rc;++ /* If the buffer was already allocated earlier, do nothing. */+ if (runtime->dma_area)+ return 0;++ /* Allocate hardware buffer */+ rc = snd_pcm_lib_malloc_pages(substream, buffer_bytes);+ if (rc < 0)+ return rc;++ /* Allocate and initialize I/O messages */+ rc = virtsnd_pcm_msg_alloc(ss, periods, runtime->dma_area,+ period_bytes);+ if (rc)+ snd_pcm_lib_free_pages(substream);++ return rc;+}++/**+ * virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream.+ * @substream: Kernel ALSA substream.+ *+ * The function does the following:+ * 1. tries to release the PCM substream on the device side,+ * 2. frees the hardware buffer.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream)+{+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);+ int rc;++ rc = virtsnd_pcm_release(ss);++ /*+ * Even if we failed to send the RELEASE message or wait for the queue+ * flush to complete, we can safely delete the buffer. Because after+ * receiving the STOP command, the device must stop all I/O message+ * processing. If there are still pending messages in the queue, the+ * next ops->hw_params() call should deal with this.+ */+ snd_pcm_lib_free_pages(substream);++ return rc;+}++/**+ * virtsnd_pcm_hw_params() - Prepare the PCM substream.
copy-paste: this is virtsnd_pcm_prepare()
quoted hunk
+ * @substream: Kernel ALSA substream.+ *+ * The function can be called both from the upper level or from the driver+ * itself.+ *+ * In all cases, the function:+ * 1. checks the state of the virtqueue and, if necessary, tries to fix it,+ * 2. prepares the substream on the device side.+ *+ * Context: Any context that permits to sleep. May take and release the tx/rx+ * queue spinlock.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)+{+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);+ struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);+ struct virtio_snd_msg *msg;+ unsigned long flags;+ int rc;++ /*+ * If we got here after ops->trigger() was called, the queue may+ * still contain messages. In this case, we need to reset the+ * substream first.+ */+ if (atomic_read(&ss->msg_count)) {+ rc = virtsnd_pcm_hw_params(substream, NULL);+ if (rc)+ return rc;+ }++ spin_lock_irqsave(&queue->lock, flags);+ ss->msg_last_enqueued = -1;+ spin_unlock_irqrestore(&queue->lock, flags);++ /*+ * Since I/O messages are asynchronous, they can be completed+ * when the runtime structure no longer exists. Since each+ * completion implies incrementing the hw_ptr, we cache all the+ * current values needed to compute the new hw_ptr value.+ */+ ss->frame_bytes = substream->runtime->frame_bits >> 3;+ ss->period_size = substream->runtime->period_size;+ ss->buffer_size = substream->runtime->buffer_size;++ atomic_set(&ss->hw_ptr, 0);+ atomic_set(&ss->xfer_xrun, 0);+ atomic_set(&ss->msg_count, 0);++ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_PREPARE,+ GFP_KERNEL);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ return virtsnd_ctl_msg_send_sync(ss->snd, msg);+}++/**+ * virtsnd_pcm_trigger() - Process command for the PCM substream.+ * @substream: Kernel ALSA substream.+ * @command: Substream command (SNDRV_PCM_TRIGGER_XXX).+ *+ * Depending on the command, the function does the following:+ * 1. enables/disables data transmission,+ * 2. starts/stops the substream on the device side.+ *+ * Context: Atomic context. May take and release the tx/rx queue spinlock.
Really? Cannot .trigger() sleep? E.g. I see mdelay(25) in snd_es18xx_playback1_trigger()
quoted hunk
+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)+{+ struct virtio_pcm_substream *ss = snd_pcm_substream_chip(substream);+ struct virtio_snd *snd = ss->snd;+ struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);+ struct virtio_snd_msg *msg;++ switch (command) {+ case SNDRV_PCM_TRIGGER_START:+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: {+ int rc;++ spin_lock(&queue->lock);+ rc = virtsnd_pcm_msg_send(ss);+ spin_unlock(&queue->lock);
Maybe it would be good to explain why locking is required here and isn't required in most other locations, where messages are sent?
Thanks
Guennadi
Like the HDA specification, the virtio sound device specification links
PCM substreams, jacks and PCM channel maps into functional groups. For
each discovered group, a PCM device is created, the number of which
coincides with the group number.
Introduce the module parameters for setting the hardware buffer
parameters:
pcm_buffer_ms [=160]
pcm_periods_min [=2]
pcm_periods_max [=16]
pcm_period_ms_min [=10]
pcm_period_ms_max [=80]
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 3 +-
sound/virtio/virtio_card.c | 45 ++++
sound/virtio/virtio_card.h | 9 +
sound/virtio/virtio_pcm.c | 536 +++++++++++++++++++++++++++++++++++++
sound/virtio/virtio_pcm.h | 89 ++++++
5 files changed, 681 insertions(+), 1 deletion(-)
create mode 100644 sound/virtio/virtio_pcm.c
create mode 100644 sound/virtio/virtio_pcm.h
if (!event)
break;
+ switch (le32_to_cpu(event->hdr.code)) {
+ case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
+ case VIRTIO_SND_EVT_PCM_XRUN: {
In the previous patch you had a switch-case statement complying to the common kernel coding style. It isn't specified in coding-style.rst, but these superfluous braces really don't seem to be good for anything - in this and multiple other switch-case statements in the series.
An empty default doesn't seem very useful either. So the above could've just been
+ switch (le32_to_cpu(event->hdr.code)) {
+ case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
+ case VIRTIO_SND_EVT_PCM_XRUN:
+ virtsnd_pcm_event(snd, event);
+ }
@@ -0,0 +1,536 @@+// SPDX-License-Identifier: GPL-2.0++/*+*Soundcarddriverforvirtio+*Copyright(C)2020OpenSynergyGmbH+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,see<http://www.gnu.org/licenses/>.+*/+#include<linux/moduleparam.h>+#include<linux/virtio_config.h>++#include"virtio_card.h"++staticunsignedintpcm_buffer_ms=160;+module_param(pcm_buffer_ms,uint,0644);+MODULE_PARM_DESC(pcm_buffer_ms,"PCM substream buffer time in milliseconds");++staticunsignedintpcm_periods_min=2;+module_param(pcm_periods_min,uint,0644);+MODULE_PARM_DESC(pcm_periods_min,"Minimum number of PCM periods");++staticunsignedintpcm_periods_max=16;+module_param(pcm_periods_max,uint,0644);+MODULE_PARM_DESC(pcm_periods_max,"Maximum number of PCM periods");++staticunsignedintpcm_period_ms_min=10;+module_param(pcm_period_ms_min,uint,0644);+MODULE_PARM_DESC(pcm_period_ms_min,"Minimum PCM period time in milliseconds");++staticunsignedintpcm_period_ms_max=80;+module_param(pcm_period_ms_max,uint,0644);+MODULE_PARM_DESC(pcm_period_ms_max,"Maximum PCM period time in milliseconds");++/* Map for converting VirtIO format to ALSA format. */+staticconstunsignedintg_v2a_format_map[]={+[VIRTIO_SND_PCM_FMT_IMA_ADPCM]=SNDRV_PCM_FORMAT_IMA_ADPCM,+[VIRTIO_SND_PCM_FMT_MU_LAW]=SNDRV_PCM_FORMAT_MU_LAW,+[VIRTIO_SND_PCM_FMT_A_LAW]=SNDRV_PCM_FORMAT_A_LAW,+[VIRTIO_SND_PCM_FMT_S8]=SNDRV_PCM_FORMAT_S8,+[VIRTIO_SND_PCM_FMT_U8]=SNDRV_PCM_FORMAT_U8,+[VIRTIO_SND_PCM_FMT_S16]=SNDRV_PCM_FORMAT_S16_LE,+[VIRTIO_SND_PCM_FMT_U16]=SNDRV_PCM_FORMAT_U16_LE,+[VIRTIO_SND_PCM_FMT_S18_3]=SNDRV_PCM_FORMAT_S18_3LE,+[VIRTIO_SND_PCM_FMT_U18_3]=SNDRV_PCM_FORMAT_U18_3LE,+[VIRTIO_SND_PCM_FMT_S20_3]=SNDRV_PCM_FORMAT_S20_3LE,+[VIRTIO_SND_PCM_FMT_U20_3]=SNDRV_PCM_FORMAT_U20_3LE,+[VIRTIO_SND_PCM_FMT_S24_3]=SNDRV_PCM_FORMAT_S24_3LE,+[VIRTIO_SND_PCM_FMT_U24_3]=SNDRV_PCM_FORMAT_U24_3LE,+[VIRTIO_SND_PCM_FMT_S20]=SNDRV_PCM_FORMAT_S20_LE,+[VIRTIO_SND_PCM_FMT_U20]=SNDRV_PCM_FORMAT_U20_LE,+[VIRTIO_SND_PCM_FMT_S24]=SNDRV_PCM_FORMAT_S24_LE,+[VIRTIO_SND_PCM_FMT_U24]=SNDRV_PCM_FORMAT_U24_LE,+[VIRTIO_SND_PCM_FMT_S32]=SNDRV_PCM_FORMAT_S32_LE,+[VIRTIO_SND_PCM_FMT_U32]=SNDRV_PCM_FORMAT_U32_LE,+[VIRTIO_SND_PCM_FMT_FLOAT]=SNDRV_PCM_FORMAT_FLOAT_LE,+[VIRTIO_SND_PCM_FMT_FLOAT64]=SNDRV_PCM_FORMAT_FLOAT64_LE,+[VIRTIO_SND_PCM_FMT_DSD_U8]=SNDRV_PCM_FORMAT_DSD_U8,+[VIRTIO_SND_PCM_FMT_DSD_U16]=SNDRV_PCM_FORMAT_DSD_U16_LE,+[VIRTIO_SND_PCM_FMT_DSD_U32]=SNDRV_PCM_FORMAT_DSD_U32_LE,+[VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME]=+SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE+};++/* Map for converting VirtIO frame rate to ALSA frame rate. */+structvirtsnd_v2a_rate{+unsignedintalsa_bit;+unsignedintrate;+};++staticconststructvirtsnd_v2a_rateg_v2a_rate_map[]={+[VIRTIO_SND_PCM_RATE_5512]={SNDRV_PCM_RATE_5512,5512},+[VIRTIO_SND_PCM_RATE_8000]={SNDRV_PCM_RATE_8000,8000},+[VIRTIO_SND_PCM_RATE_11025]={SNDRV_PCM_RATE_11025,11025},+[VIRTIO_SND_PCM_RATE_16000]={SNDRV_PCM_RATE_16000,16000},+[VIRTIO_SND_PCM_RATE_22050]={SNDRV_PCM_RATE_22050,22050},+[VIRTIO_SND_PCM_RATE_32000]={SNDRV_PCM_RATE_32000,32000},+[VIRTIO_SND_PCM_RATE_44100]={SNDRV_PCM_RATE_44100,44100},+[VIRTIO_SND_PCM_RATE_48000]={SNDRV_PCM_RATE_48000,48000},+[VIRTIO_SND_PCM_RATE_64000]={SNDRV_PCM_RATE_64000,64000},+[VIRTIO_SND_PCM_RATE_88200]={SNDRV_PCM_RATE_88200,88200},+[VIRTIO_SND_PCM_RATE_96000]={SNDRV_PCM_RATE_96000,96000},+[VIRTIO_SND_PCM_RATE_176400]={SNDRV_PCM_RATE_176400,176400},+[VIRTIO_SND_PCM_RATE_192000]={SNDRV_PCM_RATE_192000,192000}+};++/**+*virtsnd_pcm_build_hw()-ParsesubstreamconfigandbuildHWdescriptor.+*@substream:VirtIOsubstream.+*@info:VirtIOsubstreaminformationentry.+*+*Context:Anycontext.+*Return:0onsuccess,-EINVALifconfigurationisinvalid.+*/+staticintvirtsnd_pcm_build_hw(structvirtio_pcm_substream*substream,+structvirtio_snd_pcm_info*info)+{+structvirtio_device*vdev=substream->snd->vdev;+unsignedinti;+u64values;+size_tsample_max=0;+size_tsample_min=0;++substream->features=le32_to_cpu(info->features);++/*+*TODO:setSNDRV_PCM_INFO_{BATCH,BLOCK_TRANSFER}ifdevicesupports+*onlymessage-basedtransport.+*/+substream->hw.info=+SNDRV_PCM_INFO_MMAP|+SNDRV_PCM_INFO_MMAP_VALID|+SNDRV_PCM_INFO_BATCH|+SNDRV_PCM_INFO_BLOCK_TRANSFER|+SNDRV_PCM_INFO_INTERLEAVED;++if(!info->channels_min||info->channels_min>info->channels_max){+dev_err(&vdev->dev,+"SID %u: invalid channel range [%u %u]\n",+substream->sid,info->channels_min,info->channels_max);+return-EINVAL;+}++substream->hw.channels_min=info->channels_min;+substream->hw.channels_max=info->channels_max;++values=le64_to_cpu(info->formats);++substream->hw.formats=0;++for(i=0;i<ARRAY_SIZE(g_v2a_format_map);++i)+if(values&(1ULL<<i)){+unsignedintalsa_fmt=g_v2a_format_map[i];+intbytes=snd_pcm_format_physical_width(alsa_fmt)/8;++if(!sample_min||sample_min>bytes)+sample_min=bytes;++if(sample_max<bytes)+sample_max=bytes;++substream->hw.formats|=(1ULL<<alsa_fmt);+}++if(!substream->hw.formats){+dev_err(&vdev->dev,+"SID %u: no supported PCM sample formats found\n",+substream->sid);+return-EINVAL;+}++values=le64_to_cpu(info->rates);++substream->hw.rates=0;++for(i=0;i<ARRAY_SIZE(g_v2a_rate_map);++i)+if(values&(1ULL<<i)){+if(!substream->hw.rate_min||+substream->hw.rate_min>g_v2a_rate_map[i].rate)+substream->hw.rate_min=g_v2a_rate_map[i].rate;++if(substream->hw.rate_max<g_v2a_rate_map[i].rate)+substream->hw.rate_max=g_v2a_rate_map[i].rate;++substream->hw.rates|=g_v2a_rate_map[i].alsa_bit;+}++if(!substream->hw.rates){+dev_err(&vdev->dev,+"SID %u: no supported PCM frame rates found\n",+substream->sid);+return-EINVAL;+}++substream->hw.periods_min=pcm_periods_min;+substream->hw.periods_max=pcm_periods_max;++/*+*Wemustensurethatthereisenoughspaceinthebuffertostore+*pcm_buffer_msmsforthecombination(Cmax,Smax,Rmax),where:+*Cmax=maximumsupportednumberofchannels,+*Smax=maximumsupportedsamplesizeinbytes,+*Rmax=maximumsupportedframerate.+*/+substream->hw.buffer_bytes_max=+sample_max*substream->hw.channels_max*pcm_buffer_ms*+(substream->hw.rate_max/MSEC_PER_SEC);++/* Align the buffer size to the page size */+substream->hw.buffer_bytes_max=+(substream->hw.buffer_bytes_max+PAGE_SIZE-1)&-PAGE_SIZE;++/*+*Wemustensurethattheminimumperiodsizeisenoughtostore+*pcm_period_ms_minmsforthecombination(Cmin,Smin,Rmin),where:+*Cmin=minimumsupportednumberofchannels,+*Smin=minimumsupportedsamplesizeinbytes,+*Rmin=minimumsupportedframerate.+*/+substream->hw.period_bytes_min=+sample_min*substream->hw.channels_min*pcm_period_ms_min*+(substream->hw.rate_min/MSEC_PER_SEC);++/*+*Wemustensurethatthemaximumperiodsizeisenoughtostore+*pcm_period_ms_maxmsforthecombination(Cmax,Smax,Rmax).+*/+substream->hw.period_bytes_max=+sample_max*substream->hw.channels_max*pcm_period_ms_max*+(substream->hw.rate_max/MSEC_PER_SEC);++return0;+}++/**+*virtsnd_pcm_prealloc_pages()-Preallocatesubstreamhardwarebuffer.+*@substream:VirtIOsubstream.+*+*Context:Anycontextthatpermitstosleep.+*Return:0onsuccess,-errnoonfailure.+*/+staticintvirtsnd_pcm_prealloc_pages(structvirtio_pcm_substream*substream)+{+structsnd_pcm_substream*ksubstream=substream->substream;+size_tsize=substream->hw.buffer_bytes_max;+structdevice*data=snd_dma_continuous_data(GFP_KERNEL);++/*+*WejustallocateaCONTINUOUSbufferasitshouldworkinanysetup.+*+*IfthereisaneedtouseDEV(_XXX),thenaddthiscasehereand+*(probably)updatetherelatedsourcecodeinotherplaces.+*/+snd_pcm_lib_preallocate_pages(ksubstream,SNDRV_DMA_TYPE_CONTINUOUS,+data,size,size);++return0;
looks like it can be void.
quoted hunk
+}++/**+ * virtsnd_pcm_find() - Find the PCM device for the specified node ID.+ * @snd: VirtIO sound device.+ * @nid: Function node ID.+ *+ * Context: Any context.+ * Return: a pointer to the PCM device or ERR_PTR(-ENOENT).+ */+struct virtio_pcm *virtsnd_pcm_find(struct virtio_snd *snd, unsigned int nid)+{+ struct virtio_pcm *pcm;++ list_for_each_entry(pcm, &snd->pcm_list, list)+ if (pcm->nid == nid)+ return pcm;++ return ERR_PTR(-ENOENT);+}++/**+ * virtsnd_pcm_find_or_create() - Find or create the PCM device for the+ * specified node ID.+ * @snd: VirtIO sound device.+ * @nid: Function node ID.+ *+ * Context: Any context that permits to sleep.+ * Return: a pointer to the PCM device or ERR_PTR(-errno).+ */+struct virtio_pcm *virtsnd_pcm_find_or_create(struct virtio_snd *snd,+ unsigned int nid)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_pcm *pcm;++ pcm = virtsnd_pcm_find(snd, nid);+ if (!IS_ERR(pcm))+ return pcm;++ pcm = devm_kzalloc(&vdev->dev, sizeof(*pcm), GFP_KERNEL);+ if (!pcm)+ return ERR_PTR(-ENOMEM);++ pcm->nid = nid;+ list_add_tail(&pcm->list, &snd->pcm_list);++ return pcm;+}++/**+ * virtsnd_pcm_validate() - Validate if the device can be started.+ * @vdev: VirtIO parent device.+ *+ * Context: Any context.+ * Return: 0 on success, -EINVAL on failure.+ */+int virtsnd_pcm_validate(struct virtio_device *vdev)+{+ if (pcm_periods_min < 2 || pcm_periods_min > pcm_periods_max) {+ dev_err(&vdev->dev,+ "invalid range [%u %u] of the number of PCM periods\n",+ pcm_periods_min, pcm_periods_max);+ return -EINVAL;+ }++ if (!pcm_period_ms_min || pcm_period_ms_min > pcm_period_ms_max) {+ dev_err(&vdev->dev,+ "invalid range [%u %u] of the size of the PCM period\n",+ pcm_period_ms_min, pcm_period_ms_max);+ return -EINVAL;+ }++ if (pcm_buffer_ms < pcm_periods_min * pcm_period_ms_min) {+ dev_err(&vdev->dev,+ "pcm_buffer_ms(=%u) value cannot be < %u ms\n",+ pcm_buffer_ms, pcm_periods_min * pcm_period_ms_min);+ return -EINVAL;+ }++ if (pcm_period_ms_max > pcm_buffer_ms / 2) {+ dev_err(&vdev->dev,+ "pcm_period_ms_max(=%u) value cannot be > %u ms\n",+ pcm_period_ms_max, pcm_buffer_ms / 2);+ return -EINVAL;+ }++ return 0;+}++/**+ * virtsnd_pcm_parse_cfg() - Parse the stream configuration.+ * @snd: VirtIO sound device.+ *+ * This function is called during initial device initialization.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_pcm_parse_cfg(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_snd_pcm_info *info;+ unsigned int i;+ int rc;++ virtio_cread(vdev, struct virtio_snd_config, streams,+ &snd->nsubstreams);+ if (!snd->nsubstreams)+ return 0;++ snd->substreams = devm_kcalloc(&vdev->dev, snd->nsubstreams,+ sizeof(*snd->substreams), GFP_KERNEL);+ if (!snd->substreams)+ return -ENOMEM;++ info = devm_kcalloc(&vdev->dev, snd->nsubstreams, sizeof(*info),+ GFP_KERNEL);
Just kmalloc() but make sure to free it in error cases below.
quoted hunk
+ if (!info)+ return -ENOMEM;++ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_PCM_INFO, 0,+ snd->nsubstreams, sizeof(*info), info);+ if (rc)+ return rc;++ for (i = 0; i < snd->nsubstreams; ++i) {+ struct virtio_pcm_substream *substream = &snd->substreams[i];+ struct virtio_pcm *pcm;++ substream->snd = snd;+ substream->sid = i;++ rc = virtsnd_pcm_build_hw(substream, &info[i]);+ if (rc)+ return rc;++ substream->nid = le32_to_cpu(info[i].hdr.hda_fn_nid);++ pcm = virtsnd_pcm_find_or_create(snd, substream->nid);+ if (IS_ERR(pcm))+ return PTR_ERR(pcm);++ switch (info[i].direction) {+ case VIRTIO_SND_D_OUTPUT: {
Same comment about braces and in other cases in the series.
quoted hunk
+ substream->direction = SNDRV_PCM_STREAM_PLAYBACK;+ break;+ }+ case VIRTIO_SND_D_INPUT: {+ substream->direction = SNDRV_PCM_STREAM_CAPTURE;+ break;+ }+ default: {+ dev_err(&vdev->dev, "SID %u: unknown direction (%u)\n",+ substream->sid, info[i].direction);+ return -EINVAL;+ }+ }++ pcm->streams[substream->direction].nsubstreams++;+ }++ devm_kfree(&vdev->dev, info);++ return 0;+}++/**+ * virtsnd_pcm_build_devs() - Build ALSA PCM devices.+ * @snd: VirtIO sound device.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_pcm_build_devs(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_pcm *pcm;+ unsigned int i;+ int rc;++ list_for_each_entry(pcm, &snd->pcm_list, list) {+ unsigned int npbs =+ pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].nsubstreams;+ unsigned int ncps =+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].nsubstreams;++ if (!npbs && !ncps)+ continue;++ rc = snd_pcm_new(snd->card, "virtio_snd", pcm->nid, npbs, ncps,+ &pcm->pcm);+ if (rc) {+ dev_err(&vdev->dev, "snd_pcm_new[%u] failed: %d\n",+ pcm->nid, rc);+ return rc;+ }++ pcm->pcm->info_flags = 0;+ pcm->pcm->dev_class = SNDRV_PCM_CLASS_GENERIC;+ pcm->pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;+ strscpy(pcm->pcm->name, "VirtIO PCM", sizeof(pcm->pcm->name));++ pcm->pcm->private_data = pcm;++ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {+ struct virtio_pcm_stream *stream = &pcm->streams[i];++ if (!stream->nsubstreams)+ continue;++ stream->substreams =+ devm_kcalloc(&vdev->dev,+ stream->nsubstreams,+ sizeof(*stream->substreams),+ GFP_KERNEL);+ if (!stream->substreams)+ return -ENOMEM;++ stream->nsubstreams = 0;+ }+ }++ for (i = 0; i < snd->nsubstreams; ++i) {+ struct virtio_pcm_substream *substream = &snd->substreams[i];+ struct virtio_pcm_stream *stream;++ pcm = virtsnd_pcm_find(snd, substream->nid);+ if (IS_ERR(pcm))+ return PTR_ERR(pcm);++ stream = &pcm->streams[substream->direction];+ stream->substreams[stream->nsubstreams++] = substream;+ }++ list_for_each_entry(pcm, &snd->pcm_list, list)+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {+ struct virtio_pcm_stream *stream = &pcm->streams[i];+ struct snd_pcm_str *kstream;+ struct snd_pcm_substream *ksubstream;++ if (!stream->nsubstreams)+ continue;++ kstream = &pcm->pcm->streams[i];+ ksubstream = kstream->substream;++ while (ksubstream) {
cosmetic: this could be
for (substream = kstream->substream; ksubstream; ksubstream = ksubstream->next)
+/**+ * virtsnd_pcm_release() - Release the PCM substream on the device side.+ * @substream: VirtIO substream.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+static inline bool virtsnd_pcm_released(struct virtio_pcm_substream *substream)+{+ /*+ * The spec states that upon receipt of the RELEASE command "the device+ * MUST complete all pending I/O messages for the specified stream ID".+ * Thus, we consider the absence of I/O messages in the queue as an+ * indication that the substream has been released.+ */+ return atomic_read(&substream->msg_count) == 0;
Also here having it atomic doesn't really seem to help. This just means, that at some point of time it was == 0.
quoted
+}++static int virtsnd_pcm_release(struct virtio_pcm_substream *substream)
kernel-doc missing
quoted
+{+ struct virtio_snd *snd = substream->snd;+ struct virtio_snd_msg *msg;+ unsigned int js = msecs_to_jiffies(msg_timeout_ms);+ int rc;++ msg = virtsnd_pcm_ctl_msg_alloc(substream, VIRTIO_SND_R_PCM_RELEASE,+ GFP_KERNEL);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ rc = virtsnd_ctl_msg_send_sync(snd, msg);+ if (rc)+ return rc;++ return wait_event_interruptible_timeout(substream->msg_empty,+ virtsnd_pcm_released(substream),+ js);
wait_event_interruptible_timeout() will return a positive number in success cases, 0 means a timeout and condition still false. Whereas when you call this function you interpret 0 as success and you expect any != 0 to be a negative error. Wondering how this worked during your tests?
Thanks
Guennadi
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
Enumerate all available jacks and create ALSA controls.
At the moment jacks have a simple implementation and can only be used
to receive notifications about a plugged in/out device.
Signed-off-by: Anton Yakovlev <anton.yakovlev@opensynergy.com>
---
sound/virtio/Makefile | 1 +
sound/virtio/virtio_card.c | 18 +++
sound/virtio/virtio_card.h | 12 ++
sound/virtio/virtio_jack.c | 255 +++++++++++++++++++++++++++++++++++++
4 files changed, 286 insertions(+)
create mode 100644 sound/virtio/virtio_jack.c
+/**+ * virtsnd_jack_parse_cfg() - Parse the jack configuration.+ * @snd: VirtIO sound device.+ *+ * This function is called during initial device initialization.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_jack_parse_cfg(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_snd_jack_info *info;+ unsigned int i;+ int rc;++ virtio_cread(vdev, struct virtio_snd_config, jacks, &snd->njacks);+ if (!snd->njacks)+ return 0;++ snd->jacks = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*snd->jacks),+ GFP_KERNEL);+ if (!snd->jacks)+ return -ENOMEM;++ info = devm_kcalloc(&vdev->dev, snd->njacks, sizeof(*info), GFP_KERNEL);
+/**+ * virtsnd_chmap_parse_cfg() - Parse the channel map configuration.+ * @snd: VirtIO sound device.+ *+ * This function is called during initial device initialization.+ *+ * Context: Any context that permits to sleep.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_chmap_parse_cfg(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ unsigned int i;+ int rc;++ virtio_cread(vdev, struct virtio_snd_config, chmaps, &snd->nchmaps);+ if (!snd->nchmaps)+ return 0;++ snd->chmaps = devm_kcalloc(&vdev->dev, snd->nchmaps,+ sizeof(*snd->chmaps), GFP_KERNEL);+ if (!snd->chmaps)+ return -ENOMEM;++ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0,+ snd->nchmaps, sizeof(*snd->chmaps),+ snd->chmaps);+ if (rc)+ return rc;++ /* Count the number of channel maps per each PCM device/stream. */+ for (i = 0; i < snd->nchmaps; ++i) {+ struct virtio_snd_chmap_info *info = &snd->chmaps[i];+ unsigned int nid = le32_to_cpu(info->hdr.hda_fn_nid);+ struct virtio_pcm *pcm;+ struct virtio_pcm_stream *stream;++ pcm = virtsnd_pcm_find_or_create(snd, nid);+ if (IS_ERR(pcm))+ return PTR_ERR(pcm);++ switch (info->direction) {+ case VIRTIO_SND_D_OUTPUT: {+ stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];+ break;+ }+ case VIRTIO_SND_D_INPUT: {+ stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];+ break;+ }+ default: {+ dev_err(&vdev->dev,+ "chmap #%u: unknown direction (%u)\n", i,+ info->direction);+ return -EINVAL;+ }+ }++ stream->nchmaps++;+ }++ return 0;+}++/**+ * virtsnd_chmap_add_ctls() - Create an ALSA control for channel maps.+ * @pcm: ALSA PCM device.+ * @direction: PCM stream direction (SNDRV_PCM_STREAM_XXX).+ * @stream: VirtIO PCM stream.+ *+ * Context: Any context.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_chmap_add_ctls(struct snd_pcm *pcm, int direction,+ struct virtio_pcm_stream *stream)+{+ unsigned int i;+ int max_channels = 0;++ for (i = 0; i < stream->nchmaps; i++)+ if (max_channels < stream->chmaps[i].channels)+ max_channels = stream->chmaps[i].channels;++ return snd_pcm_add_chmap_ctls(pcm, direction, stream->chmaps,+ max_channels, 0, NULL);+}++/**+ * virtsnd_chmap_build_devs() - Build ALSA controls for channel maps.+ * @snd: VirtIO sound device.+ *+ * Context: Any context.+ * Return: 0 on success, -errno on failure.+ */+int virtsnd_chmap_build_devs(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ struct virtio_pcm *pcm;+ struct virtio_pcm_stream *stream;+ unsigned int i;+ int rc;++ /* Allocate channel map elements per each PCM device/stream. */+ list_for_each_entry(pcm, &snd->pcm_list, list) {+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {+ stream = &pcm->streams[i];++ if (!stream->nchmaps)+ continue;++ stream->chmaps = devm_kcalloc(&vdev->dev,+ stream->nchmaps + 1,+ sizeof(*stream->chmaps),+ GFP_KERNEL);+ if (!stream->chmaps)+ return -ENOMEM;++ stream->nchmaps = 0;+ }+ }++ /* Initialize channel maps per each PCM device/stream. */+ for (i = 0; i < snd->nchmaps; ++i) {+ struct virtio_snd_chmap_info *info = &snd->chmaps[i];+ unsigned int nid = le32_to_cpu(info->hdr.hda_fn_nid);+ unsigned int channels = info->channels;+ unsigned int ch;+ struct snd_pcm_chmap_elem *chmap;++ pcm = virtsnd_pcm_find(snd, nid);+ if (IS_ERR(pcm))+ return PTR_ERR(pcm);++ if (info->direction == VIRTIO_SND_D_OUTPUT)+ stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];+ else+ stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];++ chmap = &stream->chmaps[stream->nchmaps++];++ if (channels > ARRAY_SIZE(chmap->map))+ channels = ARRAY_SIZE(chmap->map);++ chmap->channels = channels;++ for (ch = 0; ch < channels; ++ch) {+ u8 position = info->positions[ch];++ if (position >= ARRAY_SIZE(g_v2a_position_map))+ return -EINVAL;++ chmap->map[ch] = g_v2a_position_map[position];+ }+ }
You enter this function after virtsnd_chmap_parse_cfg() has run. And virtsnd_chmap_parse_cfg() has already found or created all the PCMs and counted channel maps - the same way as you do in the above loop. Wouldn't it be enough to reuse the result of that counting and avoid re-counting here?
quoted hunk
++ /* Create an ALSA control per each PCM device/stream. */+ list_for_each_entry(pcm, &snd->pcm_list, list) {+ if (!pcm->pcm)+ continue;++ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {+ stream = &pcm->streams[i];++ if (!stream->nchmaps)+ continue;++ rc = virtsnd_chmap_add_ctls(pcm->pcm, i, stream);+ if (rc)+ return rc;+ }+ }++ return 0;+}
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-02-01 23:19:36
Hi Guennadi,
Sorry for the late reply and thanks for your comments, they helped me a
lot! Please see my answers inline.
On 25.01.2021 15:54, Guennadi Liakhovetski wrote:
...[snip]...
quoted
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
the
+ * documentation and/or other materials provided with the
distribution.
+ * 3. Neither the name of OpenSynergy GmbH nor the names of its
contributors
+ * may be used to endorse or promote products derived from this
software
+ * without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR
IBM? Also no idea whether this warranty disclaimer is appropriate here. I
thought we were transitioning to those SPDX identifiers to eliminate all
these headers.
It was a copy-paste mistake, I will edit these lines.
...[snip]...
quoted
++/**+ * virtsnd_disable_vqs() - Disable all virtqueues.+ * @snd: VirtIO sound device.+ *+ * Also free all allocated events and control messages.+ *+ * Context: Any context.+ */+static void virtsnd_disable_vqs(struct virtio_snd *snd)+{+ struct virtio_device *vdev = snd->vdev;+ unsigned int i;+ unsigned long flags;++ for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) {+ struct virtio_snd_queue *queue = &snd->queues[i];++ spin_lock_irqsave(&queue->lock, flags);+ /* Prohibit the use of the queue */+ if (queue->vqueue)+ virtqueue_disable_cb(queue->vqueue);+ queue->vqueue = NULL;+ spin_unlock_irqrestore(&queue->lock, flags);+ }++ if (snd->event_msgs)
Check not needed, kfree(NULL) is ok.
Yes, you are right here. I didn't notice that devm_kfree() now works
fine with NULL argument too.
quoted
+ devm_kfree(&vdev->dev, snd->event_msgs);
I think there are very few cases when managed resources have to be
explicitly freed. If explicit freeing is always required, then there's no
need to have them managed. If there's a clear case for managed resources,
usually you don't need to free them explicitly. Here.event_msgs are
allocated in virtsnd_find_vqs() above, which is only called during
probing. And this function is only called during release. So, I'd assume,
that you don't need to free memory explicitly here.
Here, the reason for explicitly freeing managed resources is in the
current device reset handling logic. At the moment, executing the reset
worker results in a call to virtsnd_disable_vqs. After which the device
is recreated. And since in this case the driver is not detached from the
device, the managed resources are not automatically freed. On the other
hand, managed resources allow not to worry about deallocation if the
probing function returns an error.
quoted
+
+ snd->event_msgs = NULL;
snd is about to be freed, so do you really need this?
No :)
quoted
+}
+
+/**
+ * virtsnd_reset_fn() - Kernel worker's function to reset the device.
+ * @work: Reset device work.
+ *
+ * Context: Process context.
+ */
+static void virtsnd_reset_fn(struct work_struct *work)
+{
+ struct virtio_snd *snd =
+ container_of(work, struct virtio_snd, reset_work);
+ struct virtio_device *vdev = snd->vdev;
+ struct device *dev = &vdev->dev;
+ int rc;
+
+ dev_info(dev, "sound device needs reset\n");
+
+ /*
+ * It seems that the only way to properly reset the device is to
remove
+ * and re-create the ALSA sound card device.
+ *
+ * Also resetting the device involves a number of steps with
setting the
+ * status bits described in the virtio specification. And the
easiest
+ * way to get everything right is to use the virtio bus interface.
+ */
+ rc = dev->bus->remove(dev);
+ if (rc)
+ dev_warn(dev, "bus->remove() failed: %d", rc);
+
+ rc = dev->bus->probe(dev);
+ if (rc)
+ dev_err(dev, "bus->probe() failed: %d", rc);
This looks very suspicious to me. Wondering what ALSA maintainers will say
to this.
I'm also wondering what the virtio people have to say. This part is a
purely virtio specific thing. And since none of the existing virtio
drivers processes the request to reset the device, it is not clear what
is the best way to proceed here. For this reason, the most
straightforward and simple solution was chosen.
...[snip]...
Thanks
Guennadi
---------------------------------------------------------------------
To unsubscribe, e-mail: virtio-dev-unsubscribe@lists.oasis-open.org
For additional commands, e-mail: virtio-dev-help@lists.oasis-open.org
--
Anton Yakovlev
Senior Software Engineer
OpenSynergy GmbH
Rotherstr. 20, 10245 Berlin
www.opensynergy.com
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-02-01 23:20:20
On 25.01.2021 16:22, Guennadi Liakhovetski wrote:
I think the use of (devm_)kmalloc() and friends needs some refinement in
several patches in the series.
Maybe yes, but using non-managed resources will slightly complicate the
device removing path.
...[snip]...
quoted
+/**
+ * virtsnd_ctl_msg_alloc_ext() - Allocate and initialize a control
message.
+ * @vdev: VirtIO parent device.
+ * @request_size: Size of request header (pointed to by sg_request
field).
+ * @response_size: Size of response header (pointed to by sg_response
field).
+ * @sgs: Additional data to attach to the message (may be NULL).
+ * @out_sgs: Number of scattergather elements to attach to the
request header.
+ * @in_sgs: Number of scattergather elements to attach to the
response header.
+ * @gfp: Kernel flags for memory allocation.
+ *
+ * The message will be automatically freed when the ref_count value
is 0.
+ *
+ * Context: Any context. May sleep if @gfp flags permit.
+ * Return: Allocated message on success, ERR_PTR(-errno) on failure.
+ */
+struct virtio_snd_msg *virtsnd_ctl_msg_alloc_ext(struct virtio_device
*vdev,
+ size_t request_size,
+ size_t response_size,
+ struct scatterlist *sgs,
+ unsigned int out_sgs,
+ unsigned int in_sgs,
gfp_t gfp)
+{
+ struct virtio_snd_msg *msg;
+ size_t msg_size =
+ sizeof(*msg) + (1 + out_sgs + 1 + in_sgs) *
sizeof(*msg->sgs);
+ unsigned int i;
+
+ msg = devm_kzalloc(&vdev->dev, msg_size + request_size +
response_size,
+ gfp);
Messages are short-lived, right? So, I think their allocation and freeing
has to be explicit, no need for devm_.
If explicit allocating and freeing is more appropriate here, then let it
be. Moreover, when deleting the control virtqueue, all pending messages
must be explicitly canceled. It should not be that hard to add explicit
freeing there.
...[snip]...
quoted
+
+/**
+ * virtsnd_ctl_msg_send() - Send an (asynchronous) control message.
+ * @snd: VirtIO sound device.
+ * @msg: Control message.
+ *
+ * If a message is failed to be enqueued, it will be deleted. If
message content
+ * is still needed, the caller must additionally to
virtsnd_ctl_msg_ref/unref()
+ * it.
+ *
+ * Context: Any context. Takes and releases the control queue spinlock.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtsnd_ctl_msg_send(struct virtio_snd *snd, struct
virtio_snd_msg *msg)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_snd_queue *queue = virtsnd_control_queue(snd);
+ struct virtio_snd_hdr *response = sg_virt(&msg->sg_response);
+ bool notify = false;
+ unsigned long flags;
+ int rc = -EIO;
+
+ /* Set the default status in case the message was not sent or was
+ * canceled.
+ */
+ response->code = cpu_to_virtio32(vdev, VIRTIO_SND_S_IO_ERR);
+
+ spin_lock_irqsave(&queue->lock, flags);
+ if (queue->vqueue) {
Is it allowed for queue->vqueue to be NULL?
In general it is possible. The device may request a reset when actively
used. In this case, we don't want to allow further use of the virtqueues.
Yes, that would probably be better as there is no harm in propagating
the error returned by virtqueue_add_sgs.
quoted
+}
+
+/**
+ * virtsnd_ctl_msg_send_sync() - Send a (synchronous) control message.
+ * @snd: VirtIO sound device.
+ * @msg: Control message.
+ *
+ * After returning from this function, the message will be deleted.
If message
+ * content is still needed, the caller must additionally to
+ * virtsnd_ctl_msg_ref/unref() it.
+ *
+ * The msg_timeout_ms module parameter defines the message completion
timeout.
+ * If the message is not completed within this time, the function
will return an
+ * error.
+ *
+ * Context: Any context. Takes and releases the control queue spinlock.
+ * Return: 0 on success, -errno on failure.
+ *
+ * The return value is a message status code (VIRTIO_SND_S_XXX)
converted to an
+ * appropriate -errno value.
+ */
+int virtsnd_ctl_msg_send_sync(struct virtio_snd *snd,
+ struct virtio_snd_msg *msg)
+{
+ struct virtio_device *vdev = snd->vdev;
+ unsigned int js = msecs_to_jiffies(msg_timeout_ms);
+ struct virtio_snd_hdr *response;
+ int rc;
+
+ virtsnd_ctl_msg_ref(vdev, msg);
+
+ rc = virtsnd_ctl_msg_send(snd, msg);
+ if (rc)
+ goto on_failure;
+
+ rc = wait_for_completion_interruptible_timeout(&msg->notify, js);
+ if (rc <= 0) {
+ if (!rc) {
+ struct virtio_snd_hdr *request =
+ sg_virt(&msg->sg_request);
+
+ dev_err(&vdev->dev,
+ "control message (0x%08x) timeout\n",
+ le32_to_cpu(request->code));
+ rc = -EIO;
Wouldn't -ETIMEDOUT be better here?
Yes, it would be.
quoted
+ }++ goto on_failure;+ }++ response = sg_virt(&msg->sg_response);++ switch (le32_to_cpu(response->code)) {+ case VIRTIO_SND_S_OK:+ rc = 0;+ break;+ case VIRTIO_SND_S_BAD_MSG:+ rc = -EINVAL;+ break;+ case VIRTIO_SND_S_NOT_SUPP:+ rc = -EOPNOTSUPP;+ break;+ case VIRTIO_SND_S_IO_ERR:+ rc = -EIO;+ break;+ default:+ rc = -EPERM;
any special reason for EPERM as a default error code? I think often EINVAL
is used in similar cases.
No, there is no particular reason, I just wasn't sure what to choose for
the default value.
quoted
+ break;+ }++on_failure:
cosmetic: this path is also taken on success, so maybe better just call
the lable "exit" or similar.
Ok! Then I probably need to check for other goto cases as well.
...[snip]...
quoted
+
+/**
+ * virtsnd_ctl_msg_unref() - Decrement reference counter for the
message.
+ * @vdev: VirtIO parent device.
+ * @msg: Control message.
+ *
+ * The message will be freed when the ref_count value is 0.
+ *
+ * Context: Any context.
+ */
+static inline void virtsnd_ctl_msg_unref(struct virtio_device *vdev,
+ struct virtio_snd_msg *msg)
+{
+ if (!atomic_dec_return(&msg->ref_count))
Since you use atomic operations, this function can probably be called with
no additional locking right? But if so, couldn't it be preempted here
between the check and the call to kfree()? As was mentioned in a previous
review, the use of atomic operations in this series has to be very
carefully examined...
The control message workflow is implemented in such a way that all
necessary increments occur before the first possible call to this
function. So even if preemption does occur, it shouldn't be a problem.
quoted
+ devm_kfree(&vdev->dev, msg);+}+
...[snip]...
---------------------------------------------------------------------
To unsubscribe, e-mail: virtio-dev-unsubscribe@lists.oasis-open.org
For additional commands, e-mail: virtio-dev-help@lists.oasis-open.org
--
Anton Yakovlev
Senior Software Engineer
OpenSynergy GmbH
Rotherstr. 20, 10245 Berlin
www.opensynergy.com
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
virtqueue *vqueue)
if (!event)
break;
+ switch (le32_to_cpu(event->hdr.code)) {
+ case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED:
+ case VIRTIO_SND_EVT_PCM_XRUN: {
In the previous patch you had a switch-case statement complying to the
common kernel coding style. It isn't specified in coding-style.rst, but
these superfluous braces really don't seem to be good for anything - in
this and multiple other switch-case statements in the series.
I will fix this. Thanks!
...[snip]...
quoted
@@ -359,6 +384,8 @@ static int virtsnd_probe(struct virtio_device *vdev)
struct virtio_snd_event *event)
break;
}
case VIRTIO_SND_EVT_PCM_XRUN: {
+ if (atomic_read(&substream->xfer_enabled))
Why does .xfer_enabled have to be atomic? It only takes two values - 0 and
1, I don't see any incrementing, or test-and-set type operations or
anything similar. Also I don't see .xfer_enabled being set to 1 anywhere
in this patch, presumably that happens in one of later patches.
quoted
+ atomic_set(&substream->xfer_xrun, 1);
Ditto.
Yes, maybe I was not very good at breaking the code into patches.
.xfer_enabled and .xfer_xrun are used in callback functions for
operators (next patch). Basically, these two contain boolean values.
...[snip]...
quoted
+
+/**
+ * enum pcm_msg_sg_index - Scatter-gather element indexes for an I/O
message.
+ * @PCM_MSG_SG_XFER: Element containing a virtio_snd_pcm_xfer structure.
+ * @PCM_MSG_SG_DATA: Element containing a data buffer.
+ * @PCM_MSG_SG_STATUS: Element containing a virtio_snd_pcm_status
structure.
+ * @PCM_MSG_SG_MAX: The maximum number of elements in the
scatter-gather table.
+ *
+ * These values are used as the index of the payload scatter-gather
table.
+ */
+enum pcm_msg_sg_index {
+ PCM_MSG_SG_XFER = 0,
+ PCM_MSG_SG_DATA,
+ PCM_MSG_SG_STATUS,
+ PCM_MSG_SG_MAX
+};
If I understand correctly, messages are sent to the back-end driver in
this specific order, so this is a part of the ABI, isn't it? Is it also a
part of the spec? If so this should be defined in your ABI header?
Yes, this is a part of the spec. But the spec only defines a "layout" of
the message, and does not limit or in any way define the number of
descriptors to transmit each of the parts of the message. Hence, this
enum cannot be defined as part of the ABI. However, since this driver
uses only one descriptor for each part, it is more convenient to use an
enum to make the code more readable.
quoted
+
+/**
+ * struct virtio_pcm_msg - VirtIO I/O message.
+ * @substream: VirtIO PCM substream.
+ * @xfer: Request header payload.
+ * @status: Response header payload.
+ * @sgs: Payload scatter-gather table.
+ */
+struct virtio_pcm_msg {
+ struct virtio_pcm_substream *substream;
+ struct virtio_snd_pcm_xfer xfer;
+ struct virtio_snd_pcm_status status;
+ struct scatterlist sgs[PCM_MSG_SG_MAX];
+};
+
+/**
+ * virtsnd_pcm_msg_alloc() - Allocate I/O messages.
+ * @substream: VirtIO PCM substream.
+ * @nmsg: Number of messages (equal to the number of periods).
+ * @dma_area: Pointer to used audio buffer.
+ * @period_bytes: Period (message payload) size.
+ *
+ * The function slices the buffer into nmsg parts (each with the size of
+ * period_bytes), and creates nmsg corresponding I/O messages.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -ENOMEM on failure.
+ */
+int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *substream,
+ unsigned int nmsg, u8 *dma_area,
+ unsigned int period_bytes)
+{
+ struct virtio_device *vdev = substream->snd->vdev;
+ unsigned int i;
+
+ if (substream->msgs)
+ devm_kfree(&vdev->dev, substream->msgs);
+
+ substream->msgs = devm_kcalloc(&vdev->dev, nmsg,
+ sizeof(*substream->msgs),
GFP_KERNEL);
+ if (!substream->msgs)
+ return -ENOMEM;
+
+ for (i = 0; i < nmsg; ++i) {
+ struct virtio_pcm_msg *msg = &substream->msgs[i];
+
+ msg->substream = substream;
+
+ sg_init_table(msg->sgs, PCM_MSG_SG_MAX);
Why do you need to initialise a table of 3 meddages if you then initialise
each of them separately immediately below?
Hm, good point! I forgot to delete this line, thanks.
quoted
+ sg_init_one(&msg->sgs[PCM_MSG_SG_XFER], &msg->xfer,
+ sizeof(msg->xfer));
+ sg_init_one(&msg->sgs[PCM_MSG_SG_DATA],
+ dma_area + period_bytes * i, period_bytes);
+ sg_init_one(&msg->sgs[PCM_MSG_SG_STATUS], &msg->status,
+ sizeof(msg->status));
+ }
+
+ return 0;
+}
+
+/**
+ * virtsnd_pcm_msg_send() - Send asynchronous I/O messages.
+ * @substream: VirtIO PCM substream.
+ *
+ * All messages are organized in an ordered circular list. Each time the
+ * function is called, all currently non-enqueued messages are added
to the
+ * virtqueue. For this, the function keeps track of two values:
+ *
+ * msg_last_enqueued = index of the last enqueued message,
+ * msg_count = # of pending messages in the virtqueue.
+ *
+ * Context: Any context.
+ * Return: 0 on success, -EIO on failure.
+ */
+int virtsnd_pcm_msg_send(struct virtio_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->substream->runtime;
+ struct virtio_snd *snd = substream->snd;
+ struct virtio_device *vdev = snd->vdev;
+ struct virtqueue *vqueue = virtsnd_pcm_queue(substream)->vqueue;
+ int i;
+ int n;
+ bool notify = false;
+
+ if (!vqueue)
+ return -EIO;
Is this actually possible? That would mean a data corruption or a bug in
the driver, right? In either case it can be NULL or 1 or any other invalid
value, so checking for NULL doesn't seem to help a lot?
Yes it is possible. The virtio device may ask the driver to reset itself.
This can happen at any time, including when the device is actively used.
In such case, we disable the use of virtqueues by setting the .vqueue
values to NULL.
.msg_count is also accessed in virtsnd_pcm_msg_complete() which is why
presumably you use atomic access. But here you already increment the count
before you even begin adding the message to the virtqueue. So if
virtsnd_pcm_msg_complete() preempts you here the .msg_count will be
inconsistent? Possibly you need to protect both operations together:
incrementing the counter and adding messages to queues.
It is not necessary here. As virtqueue_add_sgs requires the virtqueue to
be protected by the caller using an external lock, so all calls to
virtsnd_pcm_msg_send are wrapped with spinlocks (with disabled interrupts
for the current core) for the tx/rx virtqueues.
quoted
+
+ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ rc = virtqueue_add_sgs(vqueue, psgs, 2, 1, msg,
+ GFP_ATOMIC);
+ else
+ rc = virtqueue_add_sgs(vqueue, psgs, 1, 2, msg,
+ GFP_ATOMIC);
+
+ if (rc) {
+ atomic_dec(&substream->msg_count);
+ return -EIO;
+ }
+
+ substream->msg_last_enqueued = i;
+ }
+
+ if (!(substream->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING)))
+ notify = virtqueue_kick_prepare(vqueue);
+
+ if (notify)
+ if (!virtqueue_notify(vqueue))
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * virtsnd_pcm_msg_complete() - Complete an I/O message.
+ * @msg: I/O message.
+ * @size: Number of bytes written.
+ *
+ * Completion of the message means the elapsed period.
+ *
+ * The interrupt handler modifies three fields of the substream
structure
+ * (hw_ptr, xfer_xrun, msg_count) that are used in operator
functions. These
+ * values are atomic to avoid frequent interlocks with the interrupt
handler.
+ * This becomes especially important in the case of multiple running
substreams
+ * that share both the virtqueue and interrupt handler.
+ *
+ * Context: Interrupt context.
+ */
+static void virtsnd_pcm_msg_complete(struct virtio_pcm_msg *msg,
size_t size)
+{
+ struct virtio_pcm_substream *substream = msg->substream;
+ snd_pcm_uframes_t hw_ptr;
+ unsigned int msg_count;
+
+ /*
+ * hw_ptr always indicates the buffer position of the first I/O
message
+ * in the virtqueue. Therefore, on each completion of an I/O
message,
+ * the hw_ptr value is unconditionally advanced.
+ */
+ hw_ptr = (snd_pcm_uframes_t)atomic_read(&substream->hw_ptr);
Also unclear why this has to be atomic, especially taking into account
that it's only accessed in "interrupt context."
The general situation looks like this:
.hw_ptr and .xfer_xrun
written in the virtsnd_pcm_msg_complete()
read in the pointer() substream operator
.xfer_enabled
written in the trigger() substream operator
read in the virtsnd_pcm_msg_complete()
ALSA takes some substream locks while calling for trigger/pointer().
Unfortunately, we cannot use the same substream locks here, as it opens
up many control paths leading to deadlock. And all that remains is either
to use atomic fields, or to introduce our own spinlock for each substream
(to protect these fields). Personally, I don't know which would be better.
But the code with atomic fields looks at least simpler.
quoted
+
+ /*
+ * If the capture substream returned an incorrect status, then just
+ * increase the hw_ptr by the period size.
+ */
+ if (substream->direction == SNDRV_PCM_STREAM_PLAYBACK ||
+ size <= sizeof(msg->status)) {
+ hw_ptr += substream->period_size;
+ } else {
+ size -= sizeof(msg->status);
+ hw_ptr += size / substream->frame_bytes;
+ }
+
+ atomic_set(&substream->hw_ptr, (u32)(hw_ptr %
substream->buffer_size));
+ atomic_set(&substream->xfer_xrun, 0);
+
+ msg_count = atomic_dec_return(&substream->msg_count);
+
+ if (atomic_read(&substream->xfer_enabled)) {
+ struct snd_pcm_runtime *runtime =
substream->substream->runtime;
+
+ runtime->delay =
+ bytes_to_frames(runtime,
+
le32_to_cpu(msg->status.latency_bytes));
+
+ snd_pcm_period_elapsed(substream->substream);
+
+ virtsnd_pcm_msg_send(substream);
+ } else if (!msg_count) {
+ wake_up_all(&substream->msg_empty);
+ }
+}
Thanks
Guennadi
--
Anton Yakovlev
Senior Software Engineer
OpenSynergy GmbH
Rotherstr. 20, 10245 Berlin
www.opensynergy.com
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-02-01 23:22:40
On 25.01.2021 17:59, Guennadi Liakhovetski wrote:
On Sun, 24 Jan 2021, Anton Yakovlev wrote:
[snip]
quoted
+/**
+ * virtsnd_pcm_release() - Release the PCM substream on the device side.
+ * @substream: VirtIO substream.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+static inline bool virtsnd_pcm_released(struct virtio_pcm_substream
*substream)
+{
+ /*
+ * The spec states that upon receipt of the RELEASE command "the
device
+ * MUST complete all pending I/O messages for the specified
stream ID".
+ * Thus, we consider the absence of I/O messages in the queue as an
+ * indication that the substream has been released.
+ */
+ return atomic_read(&substream->msg_count) == 0;
Also here having it atomic doesn't really seem to help. This just means,
that at some point of time it was == 0.
Technically, you're right. In practice, everything looks like this:
I/O messages are added to the virtqueue either at the start of the
substream or in the interrupt handler (and only as long as .xfer_enabled
is true). In general, this means that the .msg_count can only be
incremented in the interrupt handler. As soon as the substream stops,
the .xfer_enabled becomes false and the .msg_count no longer increases.
This means that the .msg_count was either already 0, or we need to wait
for it to become 0.
quoted
+}++static int virtsnd_pcm_release(struct virtio_pcm_substream *substream)
wait_event_interruptible_timeout() will return a positive number in
success cases, 0 means a timeout and condition still false. Whereas when
you call this function you interpret 0 as success and you expect any != 0
to be a negative error. Wondering how this worked during your tests?
Yeah, that's actually a bug. We haven't hit a timeout on that control path.
quoted
+}++/**+ * virtsnd_pcm_open() - Open the PCM substream.+ * @substream: Kernel ALSA substream.+ *+ * Context: Any context.+ * Return: 0 on success, -errno on failure.+ */+static int virtsnd_pcm_open(struct snd_pcm_substream *substream)+{+ struct virtio_pcm *pcm = snd_pcm_substream_chip(substream);+ struct virtio_pcm_substream *ss = NULL;++ if (pcm) {+ switch (substream->stream) {+ case SNDRV_PCM_STREAM_PLAYBACK:+ case SNDRV_PCM_STREAM_CAPTURE: {+ struct virtio_pcm_stream *stream =+ &pcm->streams[substream->stream];++ if (substream->number < stream->nsubstreams)
Can this condition ever be false?
Hard to tell. But there may be some bug. In general, I try to adhere to
the rule that if an array element is referenced by index, it is better
to check the index value first.
quoted
+ ss = stream->substreams[substream->number];
+ break;
+ }
+ }
+ }
+
+ if (!ss)
+ return -EBADFD;
+
+ substream->runtime->hw = ss->hw;
+ substream->private_data = ss;
+
+ return 0;
+}
+
+/**
+ * virtsnd_pcm_close() - Close the PCM substream.
+ * @substream: Kernel ALSA substream.
+ *
+ * Context: Any context.
+ * Return: 0.
+ */
+static int virtsnd_pcm_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+/**
+ * virtsnd_pcm_hw_params() - Set the parameters of the PCM substream.
+ * @substream: Kernel ALSA substream.
+ * @hw_params: Hardware parameters (can be NULL).
+ *
+ * The function can be called both from the upper level (in this case,
+ * @hw_params is not NULL) or from the driver itself (in this case,
@hw_params
+ * is NULL, and the parameter values are taken from the runtime
structure).
+ *
+ * In all cases, the function:
+ * 1. checks the state of the virtqueue and, if necessary, tries to
fix it,
+ * 2. sets the parameters on the device side,
+ * 3. allocates a hardware buffer and I/O messages.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct virtio_pcm_substream *ss =
snd_pcm_substream_chip(substream);
+ struct virtio_device *vdev = ss->snd->vdev;
+ struct virtio_snd_msg *msg;
+ struct virtio_snd_pcm_set_params *request;
+ snd_pcm_format_t format;
+ unsigned int channels;
+ unsigned int rate;
+ unsigned int buffer_bytes;
+ unsigned int period_bytes;
+ unsigned int periods;
+ unsigned int i;
+ int vformat = -1;
+ int vrate = -1;
+ int rc;
+
+ /*
+ * If we got here after ops->trigger() was called, the queue may
+ * still contain messages. In this case, we need to release the
+ * substream first.
+ */
+ if (atomic_read(&ss->msg_count)) {
+ rc = virtsnd_pcm_release(ss);
+ if (rc) {
+ dev_err(&vdev->dev,
+ "SID %u: invalid I/O queue state\n",
+ ss->sid);
+ return rc;
+ }
+ }
+
+ /* Set hardware parameters in device */
+ if (hw_params) {
+ format = params_format(hw_params);
+ channels = params_channels(hw_params);
+ rate = params_rate(hw_params);
+ buffer_bytes = params_buffer_bytes(hw_params);
+ period_bytes = params_period_bytes(hw_params);
+ periods = params_periods(hw_params);
+ } else {
+ format = runtime->format;
+ channels = runtime->channels;
+ rate = runtime->rate;
+ buffer_bytes = frames_to_bytes(runtime,
runtime->buffer_size);
+ period_bytes = frames_to_bytes(runtime,
runtime->period_size);
+ periods = runtime->periods;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(g_a2v_format_map); ++i)
+ if (g_a2v_format_map[i].alsa_bit == format) {
+ vformat = g_a2v_format_map[i].vio_bit;
+
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(g_a2v_rate_map); ++i)
+ if (g_a2v_rate_map[i].rate == rate) {
+ vrate = g_a2v_rate_map[i].vio_bit;
+
+ break;
+ }
+
+ if (vformat == -1 || vrate == -1)
+ return -EINVAL;
+
+ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_SET_PARAMS,
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ request = sg_virt(&msg->sg_request);
+
+ request->buffer_bytes = cpu_to_virtio32(vdev, buffer_bytes);
+ request->period_bytes = cpu_to_virtio32(vdev, period_bytes);
+ request->channels = channels;
+ request->format = vformat;
+ request->rate = vrate;
I presume the latter three fields don't have to be endienness-converted,
perhaps they're 8-bit wide only.
Wouldn't it be better to only try to send the message after below
allocations completed successfully?
I thought the reverse logic was better. This message asks the device to
set a specific set of parameters. And if the device returned an error
for some reason, then there is no point in allocating memory.
quoted
+ if (rc)
+ return rc;
+
+ /* If the buffer was already allocated earlier, do nothing. */
+ if (runtime->dma_area)
+ return 0;
+
+ /* Allocate hardware buffer */
+ rc = snd_pcm_lib_malloc_pages(substream, buffer_bytes);
+ if (rc < 0)
+ return rc;
+
+ /* Allocate and initialize I/O messages */
+ rc = virtsnd_pcm_msg_alloc(ss, periods, runtime->dma_area,
+ period_bytes);
+ if (rc)
+ snd_pcm_lib_free_pages(substream);
+
+ return rc;
+}
+
+/**
+ * virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream.
+ * @substream: Kernel ALSA substream.
+ *
+ * The function does the following:
+ * 1. tries to release the PCM substream on the device side,
+ * 2. frees the hardware buffer.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct virtio_pcm_substream *ss =
snd_pcm_substream_chip(substream);
+ int rc;
+
+ rc = virtsnd_pcm_release(ss);
+
+ /*
+ * Even if we failed to send the RELEASE message or wait for the
queue
+ * flush to complete, we can safely delete the buffer. Because
after
+ * receiving the STOP command, the device must stop all I/O message
+ * processing. If there are still pending messages in the queue,
the
+ * next ops->hw_params() call should deal with this.
+ */
+ snd_pcm_lib_free_pages(substream);
+
+ return rc;
+}
+
+/**
+ * virtsnd_pcm_hw_params() - Prepare the PCM substream.
copy-paste: this is virtsnd_pcm_prepare()
Oops... :)
quoted
+ * @substream: Kernel ALSA substream.
+ *
+ * The function can be called both from the upper level or from the
driver
+ * itself.
+ *
+ * In all cases, the function:
+ * 1. checks the state of the virtqueue and, if necessary, tries to
fix it,
+ * 2. prepares the substream on the device side.
+ *
+ * Context: Any context that permits to sleep. May take and release
the tx/rx
+ * queue spinlock.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct virtio_pcm_substream *ss =
snd_pcm_substream_chip(substream);
+ struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);
+ struct virtio_snd_msg *msg;
+ unsigned long flags;
+ int rc;
+
+ /*
+ * If we got here after ops->trigger() was called, the queue may
+ * still contain messages. In this case, we need to reset the
+ * substream first.
+ */
+ if (atomic_read(&ss->msg_count)) {
+ rc = virtsnd_pcm_hw_params(substream, NULL);
+ if (rc)
+ return rc;
+ }
+
+ spin_lock_irqsave(&queue->lock, flags);
+ ss->msg_last_enqueued = -1;
+ spin_unlock_irqrestore(&queue->lock, flags);
+
+ /*
+ * Since I/O messages are asynchronous, they can be completed
+ * when the runtime structure no longer exists. Since each
+ * completion implies incrementing the hw_ptr, we cache all the
+ * current values needed to compute the new hw_ptr value.
+ */
+ ss->frame_bytes = substream->runtime->frame_bits >> 3;
+ ss->period_size = substream->runtime->period_size;
+ ss->buffer_size = substream->runtime->buffer_size;
+
+ atomic_set(&ss->hw_ptr, 0);
+ atomic_set(&ss->xfer_xrun, 0);
+ atomic_set(&ss->msg_count, 0);
+
+ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_PREPARE,
+ GFP_KERNEL);
+ if (IS_ERR(msg))
+ return PTR_ERR(msg);
+
+ return virtsnd_ctl_msg_send_sync(ss->snd, msg);
+}
+
+/**
+ * virtsnd_pcm_trigger() - Process command for the PCM substream.
+ * @substream: Kernel ALSA substream.
+ * @command: Substream command (SNDRV_PCM_TRIGGER_XXX).
+ *
+ * Depending on the command, the function does the following:
+ * 1. enables/disables data transmission,
+ * 2. starts/stops the substream on the device side.
+ *
+ * Context: Atomic context. May take and release the tx/rx queue
spinlock.
Really? Cannot .trigger() sleep? E.g. I see mdelay(25) in
snd_es18xx_playback1_trigger()
Actually, you made a good point here. I didn't know, that it is possible
to disable atomic mode for that callback. But, apparently, it is possible.
And virtio pcm definetly is nonatomic. I need to redo this code, thanks!
quoted
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtsnd_pcm_trigger(struct snd_pcm_substream *substream,
int command)
+{
+ struct virtio_pcm_substream *ss =
snd_pcm_substream_chip(substream);
+ struct virtio_snd *snd = ss->snd;
+ struct virtio_snd_queue *queue = virtsnd_pcm_queue(ss);
+ struct virtio_snd_msg *msg;
+
+ switch (command) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: {
+ int rc;
+
+ spin_lock(&queue->lock);
+ rc = virtsnd_pcm_msg_send(ss);
+ spin_unlock(&queue->lock);
Maybe it would be good to explain why locking is required here and isn't
required in most other locations, where messages are sent?
There are two kinds of messages here: control messages and I/O. Functions
for sending control message acquire and release the control virtqueue
spinlock on their own. But we cannot do the same for I/O messages, since
virtsnd_pcm_msg_send is also called from the interrupt handler, which is
already grabbing the lock for the I/O virtqueue.
Thanks
Guennadi
quoted
+ if (rc)+ return rc;++ atomic_set(&ss->xfer_enabled, 1);++ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_START,+ GFP_ATOMIC);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ return virtsnd_ctl_msg_send(snd, msg);+ }+ case SNDRV_PCM_TRIGGER_STOP:+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH: {+ atomic_set(&ss->xfer_enabled, 0);++ msg = virtsnd_pcm_ctl_msg_alloc(ss, VIRTIO_SND_R_PCM_STOP,+ GFP_ATOMIC);+ if (IS_ERR(msg))+ return PTR_ERR(msg);++ return virtsnd_ctl_msg_send(snd, msg);+ }+ default: {+ return -EINVAL;+ }+ }+}
--
Anton Yakovlev
Senior Software Engineer
OpenSynergy GmbH
Rotherstr. 20, 10245 Berlin
www.opensynergy.com
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization
From: Anton Yakovlev <anton.yakovlev@opensynergy.com> Date: 2021-02-01 23:23:18
On 26.01.2021 10:22, Guennadi Liakhovetski wrote:
CAUTION: This email originated from outside of the organization.
Do not click links or open attachments unless you recognize the sender
and know the content is safe.
On Sun, 24 Jan 2021, Anton Yakovlev wrote:
+/**
+ * virtsnd_chmap_parse_cfg() - Parse the channel map configuration.
+ * @snd: VirtIO sound device.
+ *
+ * This function is called during initial device initialization.
+ *
+ * Context: Any context that permits to sleep.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtsnd_chmap_parse_cfg(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ unsigned int i;
+ int rc;
+
+ virtio_cread(vdev, struct virtio_snd_config, chmaps,
&snd->nchmaps);
+ if (!snd->nchmaps)
+ return 0;
+
+ snd->chmaps = devm_kcalloc(&vdev->dev, snd->nchmaps,
+ sizeof(*snd->chmaps), GFP_KERNEL);
+ if (!snd->chmaps)
+ return -ENOMEM;
+
+ rc = virtsnd_ctl_query_info(snd, VIRTIO_SND_R_CHMAP_INFO, 0,
+ snd->nchmaps, sizeof(*snd->chmaps),
+ snd->chmaps);
+ if (rc)
+ return rc;
+
+ /* Count the number of channel maps per each PCM device/stream. */
+ for (i = 0; i < snd->nchmaps; ++i) {
+ struct virtio_snd_chmap_info *info = &snd->chmaps[i];
+ unsigned int nid = le32_to_cpu(info->hdr.hda_fn_nid);
+ struct virtio_pcm *pcm;
+ struct virtio_pcm_stream *stream;
+
+ pcm = virtsnd_pcm_find_or_create(snd, nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+
+ switch (info->direction) {
+ case VIRTIO_SND_D_OUTPUT: {
+ stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ break;
+ }
+ case VIRTIO_SND_D_INPUT: {
+ stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
+ break;
+ }
+ default: {
+ dev_err(&vdev->dev,
+ "chmap #%u: unknown direction (%u)\n", i,
+ info->direction);
+ return -EINVAL;
+ }
+ }
+
+ stream->nchmaps++;
+ }
+
+ return 0;
+}
+
+/**
+ * virtsnd_chmap_add_ctls() - Create an ALSA control for channel maps.
+ * @pcm: ALSA PCM device.
+ * @direction: PCM stream direction (SNDRV_PCM_STREAM_XXX).
+ * @stream: VirtIO PCM stream.
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+static int virtsnd_chmap_add_ctls(struct snd_pcm *pcm, int direction,
+ struct virtio_pcm_stream *stream)
+{
+ unsigned int i;
+ int max_channels = 0;
+
+ for (i = 0; i < stream->nchmaps; i++)
+ if (max_channels < stream->chmaps[i].channels)
+ max_channels = stream->chmaps[i].channels;
+
+ return snd_pcm_add_chmap_ctls(pcm, direction, stream->chmaps,
+ max_channels, 0, NULL);
+}
+
+/**
+ * virtsnd_chmap_build_devs() - Build ALSA controls for channel maps.
+ * @snd: VirtIO sound device.
+ *
+ * Context: Any context.
+ * Return: 0 on success, -errno on failure.
+ */
+int virtsnd_chmap_build_devs(struct virtio_snd *snd)
+{
+ struct virtio_device *vdev = snd->vdev;
+ struct virtio_pcm *pcm;
+ struct virtio_pcm_stream *stream;
+ unsigned int i;
+ int rc;
+
+ /* Allocate channel map elements per each PCM device/stream. */
+ list_for_each_entry(pcm, &snd->pcm_list, list) {
+ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {
+ stream = &pcm->streams[i];
+
+ if (!stream->nchmaps)
+ continue;
+
+ stream->chmaps = devm_kcalloc(&vdev->dev,
+ stream->nchmaps + 1,
+
sizeof(*stream->chmaps),
+ GFP_KERNEL);
+ if (!stream->chmaps)
+ return -ENOMEM;
+
+ stream->nchmaps = 0;
+ }
+ }
+
+ /* Initialize channel maps per each PCM device/stream. */
+ for (i = 0; i < snd->nchmaps; ++i) {
+ struct virtio_snd_chmap_info *info = &snd->chmaps[i];
+ unsigned int nid = le32_to_cpu(info->hdr.hda_fn_nid);
+ unsigned int channels = info->channels;
+ unsigned int ch;
+ struct snd_pcm_chmap_elem *chmap;
+
+ pcm = virtsnd_pcm_find(snd, nid);
+ if (IS_ERR(pcm))
+ return PTR_ERR(pcm);
+
+ if (info->direction == VIRTIO_SND_D_OUTPUT)
+ stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
+ else
+ stream = &pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+ chmap = &stream->chmaps[stream->nchmaps++];
+
+ if (channels > ARRAY_SIZE(chmap->map))
+ channels = ARRAY_SIZE(chmap->map);
+
+ chmap->channels = channels;
+
+ for (ch = 0; ch < channels; ++ch) {
+ u8 position = info->positions[ch];
+
+ if (position >= ARRAY_SIZE(g_v2a_position_map))
+ return -EINVAL;
+
+ chmap->map[ch] = g_v2a_position_map[position];
+ }
+ }
You enter this function after virtsnd_chmap_parse_cfg() has run. And
virtsnd_chmap_parse_cfg() has already found or created all the PCMs and
counted channel maps - the same way as you do in the above loop. Wouldn't
it be enough to reuse the result of that counting and avoid re-counting
here?
If I understood your question right, then... it's not re-counting here. :)
It's just a referencing to each channel map for each stream in one by
one manner.
quoted
++ /* Create an ALSA control per each PCM device/stream. */+ list_for_each_entry(pcm, &snd->pcm_list, list) {+ if (!pcm->pcm)+ continue;++ for (i = 0; i < ARRAY_SIZE(pcm->streams); ++i) {+ stream = &pcm->streams[i];++ if (!stream->nchmaps)+ continue;++ rc = virtsnd_chmap_add_ctls(pcm->pcm, i, stream);+ if (rc)+ return rc;+ }+ }++ return 0;+}
--
Anton Yakovlev
Senior Software Engineer
OpenSynergy GmbH
Rotherstr. 20, 10245 Berlin
www.opensynergy.com
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization