diff options
author | James O'Shannessy <james.oshannessy@fiberdyne.com.au> | 2018-08-27 15:08:14 +1000 |
---|---|---|
committer | Mark Farrugia <mark.farrugia@fiberdyne.com.au> | 2018-10-26 17:27:24 +1100 |
commit | bc8c3a602bceaba0e6d34a1ba8b776b56b00d766 (patch) | |
tree | ae8cec69c910144611e06f272033cc8c2aee7032 | |
parent | 416c9b0f9b816a6b2eb5c544f21567ad286dd4be (diff) |
Public push of AVIRT.
Follow readme for building in/out of tree for Ubuntu/AGL/etc.
Signed-off-by: James O'Shannessy <james.oshannessy@fiberdyne.com.au>
-rw-r--r-- | .clang-format | 428 | ||||
-rw-r--r-- | .gitignore | 10 | ||||
-rw-r--r-- | .vscode/extensions.json | 8 | ||||
-rw-r--r-- | .vscode/settings.json | 8 | ||||
-rw-r--r-- | .vscode/tasks.json | 73 | ||||
-rw-r--r-- | Kconfig | 20 | ||||
-rw-r--r-- | LICENSE | 201 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | README.md | 47 | ||||
-rw-r--r-- | alsa-pcm.c | 381 | ||||
-rwxr-xr-x | alsa.c | 255 | ||||
-rwxr-xr-x | alsa.h | 131 | ||||
-rw-r--r-- | core.c | 495 | ||||
-rw-r--r-- | core.h | 64 | ||||
-rw-r--r-- | dummy/Kconfig | 12 | ||||
-rw-r--r-- | dummy/Makefile | 6 | ||||
-rw-r--r-- | dummy/dummy.c | 278 | ||||
-rwxr-xr-x | loadDrivers.sh | 18 | ||||
-rwxr-xr-x | make-agl.sh | 12 | ||||
-rwxr-xr-x | unload.sh | 9 | ||||
-rw-r--r-- | xds-project.conf | 4 |
21 files changed, 2472 insertions, 4 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4b283db --- /dev/null +++ b/.clang-format @@ -0,0 +1,428 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 4. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + #AfterExternBlock: false # Unknown to clang-format-5.0 + BeforeCatch: false + BeforeElse: false + IndentBraces: false + #SplitEmptyFunction: true # Unknown to clang-format-4.0 + #SplitEmptyRecord: true # Unknown to clang-format-4.0 + #SplitEmptyNamespace: true # Unknown to clang-format-4.0 +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +#CompactNamespaces: false # Unknown to clang-format-4.0 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +#FixNamespaceComments: false # Unknown to clang-format-4.0 + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +ForEachMacros: + - 'apei_estatus_for_each_section' + - 'ata_for_each_dev' + - 'ata_for_each_link' + - 'ax25_for_each' + - 'ax25_uid_for_each' + - 'bio_for_each_integrity_vec' + - '__bio_for_each_segment' + - 'bio_for_each_segment' + - 'bio_for_each_segment_all' + - 'bio_list_for_each' + - 'bip_for_each_vec' + - 'blkg_for_each_descendant_post' + - 'blkg_for_each_descendant_pre' + - 'blk_queue_for_each_rl' + - 'bond_for_each_slave' + - 'bond_for_each_slave_rcu' + - 'btree_for_each_safe128' + - 'btree_for_each_safe32' + - 'btree_for_each_safe64' + - 'btree_for_each_safel' + - 'card_for_each_dev' + - 'cgroup_taskset_for_each' + - 'cgroup_taskset_for_each_leader' + - 'cpufreq_for_each_entry' + - 'cpufreq_for_each_entry_idx' + - 'cpufreq_for_each_valid_entry' + - 'cpufreq_for_each_valid_entry_idx' + - 'css_for_each_child' + - 'css_for_each_descendant_post' + - 'css_for_each_descendant_pre' + - 'device_for_each_child_node' + - 'drm_atomic_crtc_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane_state' + - 'drm_for_each_connector_iter' + - 'drm_for_each_crtc' + - 'drm_for_each_encoder' + - 'drm_for_each_encoder_mask' + - 'drm_for_each_fb' + - 'drm_for_each_legacy_plane' + - 'drm_for_each_plane' + - 'drm_for_each_plane_mask' + - 'drm_mm_for_each_hole' + - 'drm_mm_for_each_node' + - 'drm_mm_for_each_node_in_range' + - 'drm_mm_for_each_node_safe' + - 'for_each_active_drhd_unit' + - 'for_each_active_iommu' + - 'for_each_available_child_of_node' + - 'for_each_bio' + - 'for_each_board_func_rsrc' + - 'for_each_bvec' + - 'for_each_child_of_node' + - 'for_each_clear_bit' + - 'for_each_clear_bit_from' + - 'for_each_cmsghdr' + - 'for_each_compatible_node' + - 'for_each_console' + - 'for_each_cpu' + - 'for_each_cpu_and' + - 'for_each_cpu_not' + - 'for_each_cpu_wrap' + - 'for_each_dev_addr' + - 'for_each_dma_cap_mask' + - 'for_each_drhd_unit' + - 'for_each_dss_dev' + - 'for_each_efi_memory_desc' + - 'for_each_efi_memory_desc_in_map' + - 'for_each_endpoint_of_node' + - 'for_each_evictable_lru' + - 'for_each_fib6_node_rt_rcu' + - 'for_each_fib6_walker_rt' + - 'for_each_free_mem_range' + - 'for_each_free_mem_range_reverse' + - 'for_each_func_rsrc' + - 'for_each_hstate' + - 'for_each_if' + - 'for_each_iommu' + - 'for_each_ip_tunnel_rcu' + - 'for_each_irq_nr' + - 'for_each_lru' + - 'for_each_matching_node' + - 'for_each_matching_node_and_match' + - 'for_each_memblock' + - 'for_each_memblock_type' + - 'for_each_memcg_cache_index' + - 'for_each_mem_pfn_range' + - 'for_each_mem_range' + - 'for_each_mem_range_rev' + - 'for_each_migratetype_order' + - 'for_each_msi_entry' + - 'for_each_net' + - 'for_each_netdev' + - 'for_each_netdev_continue' + - 'for_each_netdev_continue_rcu' + - 'for_each_netdev_feature' + - 'for_each_netdev_in_bond_rcu' + - 'for_each_netdev_rcu' + - 'for_each_netdev_reverse' + - 'for_each_netdev_safe' + - 'for_each_net_rcu' + - 'for_each_new_connector_in_state' + - 'for_each_new_crtc_in_state' + - 'for_each_new_plane_in_state' + - 'for_each_new_private_obj_in_state' + - 'for_each_node' + - 'for_each_node_by_name' + - 'for_each_node_by_type' + - 'for_each_node_mask' + - 'for_each_node_state' + - 'for_each_node_with_cpus' + - 'for_each_node_with_property' + - 'for_each_of_allnodes' + - 'for_each_of_allnodes_from' + - 'for_each_of_pci_range' + - 'for_each_old_connector_in_state' + - 'for_each_old_crtc_in_state' + - 'for_each_oldnew_connector_in_state' + - 'for_each_oldnew_crtc_in_state' + - 'for_each_oldnew_plane_in_state' + - 'for_each_oldnew_private_obj_in_state' + - 'for_each_old_plane_in_state' + - 'for_each_old_private_obj_in_state' + - 'for_each_online_cpu' + - 'for_each_online_node' + - 'for_each_online_pgdat' + - 'for_each_pci_bridge' + - 'for_each_pci_dev' + - 'for_each_pci_msi_entry' + - 'for_each_populated_zone' + - 'for_each_possible_cpu' + - 'for_each_present_cpu' + - 'for_each_prime_number' + - 'for_each_prime_number_from' + - 'for_each_process' + - 'for_each_process_thread' + - 'for_each_property_of_node' + - 'for_each_reserved_mem_region' + - 'for_each_resv_unavail_range' + - 'for_each_rtdcom' + - 'for_each_rtdcom_safe' + - 'for_each_set_bit' + - 'for_each_set_bit_from' + - 'for_each_sg' + - 'for_each_sg_page' + - '__for_each_thread' + - 'for_each_thread' + - 'for_each_zone' + - 'for_each_zone_zonelist' + - 'for_each_zone_zonelist_nodemask' + - 'fwnode_for_each_available_child_node' + - 'fwnode_for_each_child_node' + - 'fwnode_graph_for_each_endpoint' + - 'gadget_for_each_ep' + - 'hash_for_each' + - 'hash_for_each_possible' + - 'hash_for_each_possible_rcu' + - 'hash_for_each_possible_rcu_notrace' + - 'hash_for_each_possible_safe' + - 'hash_for_each_rcu' + - 'hash_for_each_safe' + - 'hctx_for_each_ctx' + - 'hlist_bl_for_each_entry' + - 'hlist_bl_for_each_entry_rcu' + - 'hlist_bl_for_each_entry_safe' + - 'hlist_for_each' + - 'hlist_for_each_entry' + - 'hlist_for_each_entry_continue' + - 'hlist_for_each_entry_continue_rcu' + - 'hlist_for_each_entry_continue_rcu_bh' + - 'hlist_for_each_entry_from' + - 'hlist_for_each_entry_from_rcu' + - 'hlist_for_each_entry_rcu' + - 'hlist_for_each_entry_rcu_bh' + - 'hlist_for_each_entry_rcu_notrace' + - 'hlist_for_each_entry_safe' + - '__hlist_for_each_rcu' + - 'hlist_for_each_safe' + - 'hlist_nulls_for_each_entry' + - 'hlist_nulls_for_each_entry_from' + - 'hlist_nulls_for_each_entry_rcu' + - 'hlist_nulls_for_each_entry_safe' + - 'ide_host_for_each_port' + - 'ide_port_for_each_dev' + - 'ide_port_for_each_present_dev' + - 'idr_for_each_entry' + - 'idr_for_each_entry_continue' + - 'idr_for_each_entry_ul' + - 'inet_bind_bucket_for_each' + - 'inet_lhash2_for_each_icsk_rcu' + - 'iov_for_each' + - 'key_for_each' + - 'key_for_each_safe' + - 'klp_for_each_func' + - 'klp_for_each_object' + - 'kvm_for_each_memslot' + - 'kvm_for_each_vcpu' + - 'list_for_each' + - 'list_for_each_entry' + - 'list_for_each_entry_continue' + - 'list_for_each_entry_continue_rcu' + - 'list_for_each_entry_continue_reverse' + - 'list_for_each_entry_from' + - 'list_for_each_entry_from_reverse' + - 'list_for_each_entry_lockless' + - 'list_for_each_entry_rcu' + - 'list_for_each_entry_reverse' + - 'list_for_each_entry_safe' + - 'list_for_each_entry_safe_continue' + - 'list_for_each_entry_safe_from' + - 'list_for_each_entry_safe_reverse' + - 'list_for_each_prev' + - 'list_for_each_prev_safe' + - 'list_for_each_safe' + - 'llist_for_each' + - 'llist_for_each_entry' + - 'llist_for_each_entry_safe' + - 'llist_for_each_safe' + - 'media_device_for_each_entity' + - 'media_device_for_each_intf' + - 'media_device_for_each_link' + - 'media_device_for_each_pad' + - 'netdev_for_each_lower_dev' + - 'netdev_for_each_lower_private' + - 'netdev_for_each_lower_private_rcu' + - 'netdev_for_each_mc_addr' + - 'netdev_for_each_uc_addr' + - 'netdev_for_each_upper_dev_rcu' + - 'netdev_hw_addr_list_for_each' + - 'nft_rule_for_each_expr' + - 'nla_for_each_attr' + - 'nla_for_each_nested' + - 'nlmsg_for_each_attr' + - 'nlmsg_for_each_msg' + - 'nr_neigh_for_each' + - 'nr_neigh_for_each_safe' + - 'nr_node_for_each' + - 'nr_node_for_each_safe' + - 'of_for_each_phandle' + - 'of_property_for_each_string' + - 'of_property_for_each_u32' + - 'pci_bus_for_each_resource' + - 'ping_portaddr_for_each_entry' + - 'plist_for_each' + - 'plist_for_each_continue' + - 'plist_for_each_entry' + - 'plist_for_each_entry_continue' + - 'plist_for_each_entry_safe' + - 'plist_for_each_safe' + - 'pnp_for_each_card' + - 'pnp_for_each_dev' + - 'protocol_for_each_card' + - 'protocol_for_each_dev' + - 'queue_for_each_hw_ctx' + - 'radix_tree_for_each_contig' + - 'radix_tree_for_each_slot' + - 'radix_tree_for_each_tagged' + - 'rbtree_postorder_for_each_entry_safe' + - 'resource_list_for_each_entry' + - 'resource_list_for_each_entry_safe' + - 'rhl_for_each_entry_rcu' + - 'rhl_for_each_rcu' + - 'rht_for_each' + - 'rht_for_each_continue' + - 'rht_for_each_entry' + - 'rht_for_each_entry_continue' + - 'rht_for_each_entry_rcu' + - 'rht_for_each_entry_rcu_continue' + - 'rht_for_each_entry_safe' + - 'rht_for_each_rcu' + - 'rht_for_each_rcu_continue' + - '__rq_for_each_bio' + - 'rq_for_each_segment' + - 'scsi_for_each_prot_sg' + - 'scsi_for_each_sg' + - 'sctp_for_each_hentry' + - 'sctp_skb_for_each' + - 'shdma_for_each_chan' + - '__shost_for_each_device' + - 'shost_for_each_device' + - 'sk_for_each' + - 'sk_for_each_bound' + - 'sk_for_each_entry_offset_rcu' + - 'sk_for_each_from' + - 'sk_for_each_rcu' + - 'sk_for_each_safe' + - 'sk_nulls_for_each' + - 'sk_nulls_for_each_from' + - 'sk_nulls_for_each_rcu' + - 'snd_pcm_group_for_each_entry' + - 'snd_soc_dapm_widget_for_each_path' + - 'snd_soc_dapm_widget_for_each_path_safe' + - 'snd_soc_dapm_widget_for_each_sink_path' + - 'snd_soc_dapm_widget_for_each_source_path' + - 'tb_property_for_each' + - 'udp_portaddr_for_each_entry' + - 'udp_portaddr_for_each_entry_rcu' + - 'usb_hub_for_each_child' + - 'v4l2_device_for_each_subdev' + - 'v4l2_m2m_for_each_dst_buf' + - 'v4l2_m2m_for_each_dst_buf_safe' + - 'v4l2_m2m_for_each_src_buf' + - 'v4l2_m2m_for_each_src_buf_safe' + - 'zorro_for_each_dev' + +#IncludeBlocks: Preserve # Unknown to clang-format-5.0 +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +#IndentPPDirectives: None # Unknown to clang-format-5.0 +IndentWidth: 8 +IndentWrappedFunctionNames: true +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: Inner +#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +#SortUsingDeclarations: false # Unknown to clang-format-4.0 +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 +#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 +SpaceBeforeParens: ControlStatements +#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... @@ -1,4 +1,6 @@ -build/ -CMakeCache.txt -Makefile -*.so +*.o* +*.ko* +*.symvers +*.mod* +*.mk* +build*/**
\ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4c7180e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "ms-vscode.cpptools", + "xaver.clang-format", + ] +}
\ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4dc08b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.tabSize": 8, + "editor.insertSpaces": false, + "editor.rulers": [ + 80 + ], + "editor.formatOnSave": true, +}
\ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..c901648 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,73 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "type": "shell", + "presentation": { + "reveal": "always" + }, + "tasks": [ + { + "label": "Make Driver", + "command": "CONFIG_AVIRT=m CONFIG_AVIRT_BUILDLOCAL=y CONFIG_AVIRT_DUMMYAP=m make -C /lib/modules/$(uname -r)/build/ M=$(pwd)", + "group": "build", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "Clean Driver", + "command": "CONFIG_AVIRT=m CONFIG_AVIRT_BUILDLOCAL=y CONFIG_AVIRT_DUMMYAP=m make -C /lib/modules/$(uname -r)/build/ M=$(pwd) clean", + "group": "build", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "Deploy Drivers", + "command": "sudo sh -c './unload.sh && ./loadDrivers.sh'", + "problemMatcher": [] + }, + { + "label": "Make and Deploy Drivers", + "command": "make && sudo sh -c './unload.sh && ./loadDrivers.sh'", + "problemMatcher": [] + }, + { + "label": "Unload new drivers", + "command": "sudo sh unload.sh", + "problemMatcher": [] + }, + { + "label": "Load new drivers", + "command": "sudo sh ./loadDrivers.sh", + "problemMatcher": [] + }, + { + "label": "Make Driver AGL", + "type": "shell", + "command": "./make-agl.sh bf869951", + "group": "build", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "Clean Driver AGL", + "type": "shell", + "command": "./make-agl.sh bf869951 clean", + "group": "build", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "Deploy Driver AGL", + "type": "shell", + "command": "rsync -av avirt_core.ko dummy/avirt_dummyap.ko root@192.168.1.198:~/", + "problemMatcher": [ + "$gcc" + ] + }, + ] +}
\ No newline at end of file @@ -0,0 +1,20 @@ +menuconfig AVIRT + tristate "AVIRT support" + default n + ---help--- + Say Y here if you want to enable AVIRT support. + This driver needs at least one additional audio path in + order to establish audio routing. + + To compile this driver as a module, choose M here: the + module will be called avirt_core. + + If in doubt, say N here. + + + +if AVIRT + +source "drivers/staging/avirt/dummy/Kconfig" + +endif @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 JOSHANNE + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88eebbd --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_AVIRT) += avirt_core.o +avirt_core-y := core.o +avirt_core-y += alsa.o +avirt_core-y += alsa-pcm.o + +ifeq ($(CONFIG_AVIRT_BUILDLOCAL),) + CCFLAGS_AVIRT := "drivers/staging/" +else + CCFLAGS_AVIRT := "$(PWD)/../" +endif + +ccflags-y += -I${CCFLAGS_AVIRT} + +$(info $(KERNELRELEASE)) +obj-$(CONFIG_AVIRT_DUMMYAP) += dummy/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..59bcd08 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +Alsa Virtual Sound Driver +-------------------------- +[![pipeline status](https://git.fiberdyne.com.au/JOSHANNE/kmodule_development/badges/master/pipeline.svg)](https://git.fiberdyne.com.au/JOSHANNE/kmodule_development/commits/master) + +The ALSA Virtual Sound Driver (AVIRT) aims to provide a Linux kernel solution to the issue of audio routing in kernel-space, as well as security per-stream, and dynamic configuration of streams at the kernel level. + +A top-level abstract dynamic audio driver is presented to the user-space via an ALSA middle-layer card. From there, respective low-level "real" audio drivers can subscribe to it as an "Audio Path". + +The top-level driver is configured (currently) using module parameters, as is the norm for sound drivers in the Linux tree, however this will utilise a configfs configuration implementation in future. + +A sample dummy Audio Path is provided as an example to show how a low-level audio driver would subscribe to AVIRT, and accept audio routing for playback. + +## Building +### Out Of Tree +The kernel modules can be built either in-tree, or out-of-tree. +To build both AVIRT and the dummy Audio Path out-of-tree, use the following command: + +```sh +$ CONFIG_AVIRT=m CONFIG_AVIRT_BUILDLOCAL=y CONFIG_AVIRT_DUMMYAP=m make -C /lib/modules/$(uname -r)/build/ M=$(pwd) +``` + +To build both AVIRT and the dummy Audio Path out-of-tree for [AGL](http://docs.automotivelinux.org/) (`aarch64` currently supported), use the [XDS](http://docs.automotivelinux.org/docs/devguides/en/dev/reference/xds/part-1/0_Abstract.html) build system together with the `make_agl.sh` script: + +```sh +$ ./make_agl.sh ${XDS_SDK_ID} +``` +### In tree +``` +$ TODO +``` + +## Running +To run, we must load the kernel modules using the `loadDrivers.sh` script, which contains sample module parameters to AVIRT: +```sh +$ ./loadDrivers.sh +``` +To unload the drivers use: +```sh +$ ./unload.sh +``` + +## TODO + - Currently, playback only - implementing capture is WIP. + - Rework module parameters into configfs configuration. + - Create a loopback Audio Path for use with AVIRT, to demonstrate standard AGL soft-mixing capabilities. + - Modify Fiberdyne DSP driver for use with AVRIT, to demonstrate DSP off-loading and hard-mixing capabilities via Xtensa HiFi2. + diff --git a/alsa-pcm.c b/alsa-pcm.c new file mode 100644 index 0000000..bd705f4 --- /dev/null +++ b/alsa-pcm.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ALSA Virtual Soundcard + * + * alsa-pcm.c - ALSA PCM implementation + * + * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd + */ + +#include "core.h" +#include "alsa.h" + +#define DO_AUDIOPATH_CB(callback, substream, ...) \ + do { \ + struct avirt_audiopath *ap; \ + ap = avirt_get_current_audiopath(); \ + CHK_NULL_V(ap, "Cannot find Audio Path!"); \ + if (ap->pcm_ops->callback) { \ + return ap->pcm_ops->callback(substream, \ + ##__VA_ARGS__); \ + } \ + } while (0) + +/** + * configure_pcm - set up substream properties from user configuration + * @substream: pointer to ALSA PCM substream + * @return 0 on success or error code otherwise + */ +static int configure_pcm(struct snd_pcm_substream *substream) +{ + struct avirt_alsa_dev_config *config; + struct avirt_audiopath *audiopath; + struct avirt_alsa_dev_group *group; + struct snd_pcm_hardware *hw; + unsigned bytes_per_sample = 0, blocksize = 0; + + audiopath = avirt_get_current_audiopath(); + CHK_NULL_V(audiopath, "Cannot find Audio Path!"); + + blocksize = audiopath->blocksize; + + // Copy the hw params from the audiopath to the pcm + hw = &substream->runtime->hw; + memcpy(hw, audiopath->hw, sizeof(struct snd_pcm_hardware)); + pr_info("%s %d %d", __func__, blocksize, hw->periods_max); + + if (hw->formats == SNDRV_PCM_FMTBIT_S16_LE) + bytes_per_sample = 2; + else { + pr_err("[%s] PCM only supports SNDRV_PCM_FMTBIT_S16_LE", + __func__); + return -EINVAL; + } + + // Get device group (playback/capture) + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + // Check if substream id is valid + if (substream->pcm->device >= group->devices) + return -1; + + // Setup remaining hw properties + config = &group->config[substream->pcm->device]; + hw->channels_min = config->channels; + hw->channels_max = config->channels; + hw->buffer_bytes_max = blocksize * hw->periods_max * bytes_per_sample * + config->channels; + hw->period_bytes_min = blocksize * bytes_per_sample * config->channels; + hw->period_bytes_max = blocksize * bytes_per_sample * config->channels; + + return 0; +} + +/******************************************************************************* + * ALSA PCM Callbacks + ******************************************************************************/ +/** + * pcm_open - Implements 'open' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * + * This is called when an ALSA PCM substream is opened. The substream device is + * configured here. + * + * Returns 0 on success or error code otherwise. + */ +static int pcm_open(struct snd_pcm_substream *substream) +{ + // Setup the pcm device based on the configuration assigned + CHK_ERR_V(configure_pcm(substream), "Failed to setup pcm device"); + + // Do additional Audio Path 'open' callback + DO_AUDIOPATH_CB(open, substream); + + return 0; +} + +/** + * pcm_close - Implements 'close' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * + * This is called when a PCM substream is closed. + * + * Returns 0 on success or error code otherwise. + */ +static int pcm_close(struct snd_pcm_substream *substream) +{ + // Do additional Audio Path 'close' callback + DO_AUDIOPATH_CB(close, substream); + + return 0; +} + +/** + * pcm_hw_params - Implements 'hw_params' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * @hw_params: contains the hardware parameters for the PCM + * + * This is called when the hardware parameters are set, including buffer size, + * the period size, the format, etc. The buffer allocation should be done here. + * + * Returns 0 on success or error code otherwise. + */ +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int channels, err; + size_t bufsz; + struct avirt_audiopath *audiopath; + struct avirt_alsa_dev_group *group; + + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + channels = group->config[substream->pcm->device].channels; + + if ((params_channels(hw_params) > channels) || + (params_channels(hw_params) < channels)) { + pr_err("Requested number of channels not supported.\n"); + return -EINVAL; + } + + audiopath = avirt_get_current_audiopath(); + CHK_NULL_V(audiopath, "Cannot find Audio Path!"); + + bufsz = params_buffer_bytes(hw_params) * audiopath->hw->periods_max; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, bufsz); + if (err <= 0) { + pr_err("pcm: buffer allocation failed (%d)\n", err); + return err; + } + + // Do additional Audio Path 'hw_params' callback + // DO_AUDIOPATH_CB(hw_params, substream, hw_params); + + return 0; +} + +/** + * pcm_hw_free - Implements 'hw_free' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * + * Release the resources allocated via 'hw_params'. + * This function is always called before the 'close' callback . + * + * Returns 0 on success or error code otherwise. + */ +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + CHK_ERR(snd_pcm_lib_free_vmalloc_buffer(substream)); + + // Do additional Audio Path 'hw_free' callback + // DO_AUDIOPATH_CB(hw_free, substream); + + return 0; +} + +/** + * pcm_prepare - Implements 'prepare' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * + * The format rate, sample rate, etc., can be set here. This callback can be + * called many times at each setup. This function is also used to handle overrun + * and underrun conditions when we try and resync the DMA (if we're using DMA). + * + * Returns 0 on success or error code otherwise. + */ +static int pcm_prepare(struct snd_pcm_substream *substream) +{ + struct avirt_alsa_dev_group *group; + struct snd_pcm_runtime *runtime = substream->runtime; + + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + // Reset HW buffer index for the device + group->streams[substream->pcm->device].hw_frame_idx = 0; + + group->buffersize = frames_to_bytes(runtime, runtime->buffer_size); + + // Do additional Audio Path 'prepare' callback + DO_AUDIOPATH_CB(prepare, substream); + + return 0; +} + +/** + * pcm_trigger - Implements 'trigger' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * @cmd: action to be performed (start or stop) + * + * This is called when the PCM is started, stopped or paused. The action + * indicated action is specified in the second argument, SNDRV_PCM_TRIGGER_XXX + * + * Returns 0 on success or error code otherwise. + */ +static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct avirt_alsa_dev_group *group; + + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + group->streams[substream->pcm->device].substream = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + default: + pr_err("trigger must be START or STOP"); + return -EINVAL; + } + + // Do additional Audio Path 'trigger' callback + DO_AUDIOPATH_CB(trigger, substream, cmd); + + return 0; +} + +/** + * pcm_pointer - Implements 'pointer' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * + * This gets called when the user space needs a DMA buffer index. IO errors will + * be generated if the index does not increment, or drives beyond the frame + * threshold of the buffer itself. + * + * Returns the current hardware buffer frame index. + */ +static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream) +{ + struct avirt_alsa_dev_group *group; + + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + return group->streams[substream->pcm->device].hw_frame_idx; +} + +/** + * pcm_pointer - Implements 'get_time_info' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * @system_ts + * @audio_ts + * @audio_tstamp_config + * @audio_tstamp_report + * + * Generic way to get system timestamp and audio timestamp info + * + * Returns 0 on success or error code otherwise + */ +static int pcm_get_time_info( + struct snd_pcm_substream *substream, struct timespec *system_ts, + struct timespec *audio_ts, + struct snd_pcm_audio_tstamp_config *audio_tstamp_config, + struct snd_pcm_audio_tstamp_report *audio_tstamp_report) +{ + struct avirt_alsa_dev_group *group; + + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + DO_AUDIOPATH_CB(get_time_info, substream, system_ts, audio_ts, + audio_tstamp_config, audio_tstamp_report); + + return 0; +} + +/** + * pcm_copy_user - Implements 'copy_user' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * @channel: + * @pos: The offset in the DMA + * @src: Audio PCM data from the user space + * @count: + * + * This is where we need to copy user audio PCM data into the sound driver + * + * Returns 0 on success or error code otherwise. + * + */ +static int pcm_copy_user(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, void __user *src, + snd_pcm_uframes_t count) +{ + //struct snd_pcm_runtime *runtime; + //int offset; + + //runtime = substream->runtime; + //offset = frames_to_bytes(runtime, pos); + + // Do additional Audio Path 'copy_user' callback + DO_AUDIOPATH_CB(copy_user, substream, channel, pos, src, count); + + return 0; +} + +/** + * pcm_copy_kernel - Implements 'copy_kernel' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * @channel: + * @pos: The offset in the DMA + * @src: Audio PCM data from the user space + * @count: + * + * This is where we need to copy kernel audio PCM data into the sound driver + * + * Returns 0 on success or error code otherwise. + * + */ +static int pcm_copy_kernel(struct snd_pcm_substream *substream, int channel, + unsigned long pos, void *buf, unsigned long count) +{ + DO_AUDIOPATH_CB(copy_kernel, substream, channel, pos, buf, count); + return 0; +} + +/** + * pcm_ack - Implements 'ack' callback for PCM middle layer + * @substream: pointer to ALSA PCM substream + * + * This is where we need to ack + * + * Returns 0 on success or error code otherwise. + * + */ +int pcm_ack(struct snd_pcm_substream *substream) +{ + DO_AUDIOPATH_CB(ack, substream); + return 0; +} + +static int pcm_silence(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, snd_pcm_uframes_t count) +{ + DO_AUDIOPATH_CB(fill_silence, substream, channel, pos, count); + return 0; +} + +struct snd_pcm_ops pcm_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .get_time_info = pcm_get_time_info, + .fill_silence = pcm_silence, + .copy_user = pcm_copy_user, + .copy_kernel = pcm_copy_kernel, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + .ack = pcm_ack, + +}; @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ALSA Virtual Soundcard + * + * alsa.c - ALSA PCM driver for virtual ALSA card + * + * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd + */ + +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/initval.h> + +#include "alsa.h" +#include "core.h" + +static struct avirt_alsa_driver *_driver = NULL; + +extern struct snd_pcm_ops pcm_ops; + +/** + * pcm_constructor - Constructs the ALSA PCM middle devices for this driver + * @card: The snd_card struct to construct the devices for + * @return 0 on success or error code otherwise + */ +static int pcm_constructor(struct snd_card *card) +{ + struct snd_pcm *pcm; + int i; + + // Allocate Playback PCM instances + for (i = 0; i < _driver->playback.devices; i++) { + CHK_ERR(snd_pcm_new(card, + _driver->playback.config[i].devicename, i, + 1, 0, &pcm)); + + /** Register driver callbacks */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops); + + pcm->info_flags = 0; + strcpy(pcm->name, _driver->playback.config[i].devicename); + } + + // Allocate Capture PCM instances + for (i = 0; i < _driver->capture.devices; i++) { + CHK_ERR(snd_pcm_new(card, _driver->capture.config[i].devicename, + i, 0, 1, &pcm)); + + /** Register driver callbacks */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops); + + pcm->info_flags = 0; + strcpy(pcm->name, _driver->capture.config[i].devicename); + } + + return 0; +} + +/** + * alloc_dev_config - Allocates memory for ALSA device configuration + * @return: 0 on success or error code otherwise + */ +static int alloc_dev_config(struct avirt_alsa_dev_config **devconfig, + struct avirt_alsa_dev_config *userconfig, + unsigned numdevices) +{ + if (numdevices == 0) + return 0; + + *devconfig = kzalloc(sizeof(struct avirt_alsa_dev_config) * numdevices, + GFP_KERNEL); + if (!(*devconfig)) + return -ENOMEM; + + memcpy(*devconfig, userconfig, + sizeof(struct avirt_alsa_dev_config) * numdevices); + + return 0; +} + +/** + * alloc_dev_streams - Initializes ALSA device substream buffers + * @return: 0 on success or error code otherwise + */ +static int alloc_dev_streams(struct avirt_alsa_dev_config *config, + struct avirt_alsa_stream **streams, + unsigned numdevices) +{ + unsigned i; + + if (numdevices == 0) + return 0; + + *streams = kzalloc(sizeof(struct avirt_alsa_stream) * numdevices, + GFP_KERNEL); + + if (!(*streams)) + return -EFAULT; + + for (i = 0; i < numdevices; i++) + (*streams)[i].hw_frame_idx = 0; + + return 0; +} + +struct avirt_alsa_dev_group *avirt_alsa_get_dev_group(int direction) +{ + if (!_driver) { + pr_err("[%s] _driver is NULL", __func__); + return NULL; + } + + switch (direction) { + case SNDRV_PCM_STREAM_PLAYBACK: + return &_driver->playback; + case SNDRV_PCM_STREAM_CAPTURE: + return &_driver->capture; + default: + pr_err("[%s] Direction must be SNDRV_PCM_STREAM_XXX!", + __func__); + return NULL; + } +} + +/** + * avirt_alsa_init - Initializes the ALSA driver + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_init() +{ + // Allocate memory for the driver + _driver = kzalloc(sizeof(struct avirt_alsa_driver), GFP_KERNEL); + if (!_driver) + return -ENOMEM; + + return 0; +} + +/** + * avirt_alsa_configure_pcm- Configure the PCM device + * @config: Device configuration structure array + * @direction: Direction of PCM (SNDRV_PCM_STREAM_XXX) + * @numdevices: Number of devices (array length) + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_configure_pcm(struct avirt_alsa_dev_config *config, + int direction, unsigned numdevices) +{ + struct avirt_alsa_dev_group *group; + + group = avirt_alsa_get_dev_group(direction); + CHK_NULL(group); + + CHK_ERR(alloc_dev_config(&group->config, config, numdevices)); + + group->devices = numdevices; + + CHK_ERR(alloc_dev_streams(group->config, &group->streams, + group->devices)); + return 0; +} + +/** + * avirt_alsa_register - Registers the ALSA driver + * @devptr: Platform driver device + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_register(struct platform_device *devptr) +{ + struct snd_card *card; + static struct snd_device_ops device_ops; + + if (!_driver) + return -EFAULT; + + // Create the card instance + CHK_ERR_V(snd_card_new(&devptr->dev, SNDRV_DEFAULT_IDX1, "avirt", + THIS_MODULE, 0, &card), + "Failed to create sound card"); + + strcpy(card->driver, "avirt-alsa-device"); + strcpy(card->shortname, "avirt"); + strcpy(card->longname, "A virtual sound card driver for ALSA"); + _driver->card = card; + + // Create new sound device + CHK_ERR_V((snd_device_new(card, SNDRV_DEV_LOWLEVEL, _driver, + &device_ops)), + "Failed to create sound device"); + + CHK_ERR((pcm_constructor(card))); + + /** Register with the ALSA framework */ + CHK_ERR_V(snd_card_register(card), "Device registration failed"); + + return 0; +} + +/** + * avirt_alsa_deregister - Deregisters the ALSA driver + * @devptr: Platform driver device + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_deregister(void) +{ + snd_card_free(_driver->card); + + if (_driver->playback.config) + kfree(_driver->playback.config); + if (_driver->playback.streams) + kfree(_driver->playback.streams); + if (_driver->capture.config) + kfree(_driver->capture.config); + if (_driver->capture.streams) + kfree(_driver->capture.streams); + + kfree(_driver); + + return 0; +} + +/** + * pcm_buff_complete_cb - PCM buffer complete callback + * @substreamid: pointer to ALSA PCM substream + * @return 0 on success or error code otherwise + * + * This should be called from a child Audio Path once it has finished processing + * the pcm buffer + */ +int pcm_buff_complete_cb(struct snd_pcm_substream *substream) +{ + int maxframe, deviceid; + struct avirt_audiopath *audiopath; + struct avirt_alsa_dev_group *group; + + deviceid = substream->pcm->device; + + group = avirt_alsa_get_dev_group(substream->stream); + CHK_NULL(group); + + audiopath = avirt_get_current_audiopath(); + CHK_NULL_V(audiopath, "Cannot find Audio Path!"); + + group->streams[deviceid].hw_frame_idx += audiopath->blocksize; + maxframe = audiopath->blocksize * audiopath->hw->periods_max; + + // Once the index reaches the DMA buffer boundary, reset it to 0 + if (group->streams[deviceid].hw_frame_idx >= maxframe) + group->streams[deviceid].hw_frame_idx = 0; + + // Notify ALSA middle layer of the elapsed period boundary + snd_pcm_period_elapsed(group->streams[deviceid].substream); + + return 0; +} @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ALSA Virtual Soundcard + * + * alsa.h - Top level ALSA interface + * + * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd + */ + +#ifndef __AVIRT_ALSA_H__ +#define __AVIRT_ALSA_H__ + +#include <linux/platform_device.h> + +#define MAX_NAME_LEN 32 + +#define PRINT_ERR(errno, errmsg, ...) \ + pr_err("[%s()] %s [ERRNO:%d]", __func__, errmsg, ##__VA_ARGS__, errno); + +#define CHK_ERR(errno) \ + do { \ + if (errno < 0) \ + return errno; \ + } while (0) + +#define CHK_ERR_V(errno, errmsg, ...) \ + do { \ + if (errno < 0) { \ + PRINT_ERR(errno, errmsg, ##__VA_ARGS__) \ + return errno; \ + } \ + } while (0) + +#define CHK_NULL(x) \ + do { \ + if (!x) \ + return -EFAULT; \ + } while (0) + +#define CHK_NULL_V(x, errmsg, ...) \ + do { \ + if (!x) { \ + PRINT_ERR(EFAULT, errmsg, ##__VA_ARGS__) \ + return -EFAULT; \ + } \ + } while (0) + +/* + * Substream device configuration + */ +struct avirt_alsa_dev_config { + const char devicename[MAX_NAME_LEN]; + int channels; +}; + +/** + * Stream maintainance + */ +struct avirt_alsa_stream { + int hw_frame_idx; + struct snd_pcm_substream *substream; +}; + +/** + * Collection of devices + */ +struct avirt_alsa_dev_group { + struct avirt_alsa_dev_config *config; + struct avirt_alsa_stream *streams; + int devices; + int buffersize; +}; + +/** + * ALSA driver data + */ +struct avirt_alsa_driver { + struct snd_card *card; + struct avirt_alsa_dev_group playback; + struct avirt_alsa_dev_group capture; +}; + +/** + * avirt_alsa_init - Initializes the ALSA driver + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_init(void); + +/** + * avirt_alsa_configure_pcm- Configure the PCM device + * @config: Device configuration structure array + * @direction: Direction of PCM (SNDRV_PCM_STREAM_XXX) + * @numdevices: Number of devices (array length) + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_configure_pcm(struct avirt_alsa_dev_config *config, + int direction, unsigned numdevices); + +/** + * avirt_alsa_register - Registers the ALSA driver + * @devptr: Platform driver device + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_register(struct platform_device *devptr); + +/** + * avirt_alsa_deregister - Deregisters the ALSA driver + * @devptr: Platform driver device + * @return: 0 on success or error code otherwise + */ +int avirt_alsa_deregister(void); + +/** + * avirt_alsa_get_dev_group - Gets the device group for the specified direction + * @direction: SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE + * @return: Either the playback or capture device group on success, + * or NULL otherwise + */ +struct avirt_alsa_dev_group *avirt_alsa_get_dev_group(int direction); + +/** + * pcm_buff_complete_cb - PCM buffer complete callback + * @substream: pointer to ALSA PCM substream + * @return 0 on success or error code otherwise + * + * This should be called from a child Audio Path once it has finished processing + * the PCM buffer + */ +int pcm_buff_complete_cb(struct snd_pcm_substream *substream); + +#endif // __AVIRT_ALSA_H__ @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ALSA Virtual Soundcard + * + * core.c - Implementation of core module for virtual ALSA card + * + * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> + +#include "avirt/core.h" +#include "alsa.h" + +MODULE_AUTHOR("JOSHANNE <james.oshannessy@fiberdyne.com.au>"); +MODULE_AUTHOR("MFARRUGI <mark.farrugia@fiberdyne.com.au>"); +MODULE_DESCRIPTION("A configurable virtual soundcard"); +MODULE_LICENSE("GPL v2"); + +#define SND_AVIRTUAL_DRIVER "snd_avirt" +#define MAX_PCM_DEVS 8 +#define MAX_AUDIOPATHS 4 +#define DEFAULT_FILE_PERMS 0644 + +/* Number of playback devices to create (max = MAX_PCM_DEVS) */ +static unsigned playback_num = 0; +/* Number of capture devices to create (max = MAX_PCM_DEVS) */ +static unsigned capture_num = 0; +/* Names per playback device */ +static char *playback_names[MAX_PCM_DEVS]; +/* Names per capture device */ +static char *capture_names[MAX_PCM_DEVS]; +/* Channels per playback device */ +static unsigned playback_chans[MAX_PCM_DEVS]; +/* Channels per capture device */ +static unsigned capture_chans[MAX_PCM_DEVS]; + +module_param(playback_num, int, DEFAULT_FILE_PERMS); +MODULE_PARM_DESC(playback_num, + "Number of playback devices to create (max = MAX_PCM_DEVS)"); +module_param(capture_num, int, DEFAULT_FILE_PERMS); +MODULE_PARM_DESC(capture_num, + "Number of capture devices to create (max = MAX_PCM_DEVS)"); +module_param_array(playback_names, charp, NULL, DEFAULT_FILE_PERMS); +MODULE_PARM_DESC(playback_names, "Names per playback device"); +module_param_array(capture_names, charp, NULL, DEFAULT_FILE_PERMS); +MODULE_PARM_DESC(capture_names, "Names per capture device"); +module_param_array(playback_chans, int, NULL, DEFAULT_FILE_PERMS); +MODULE_PARM_DESC(playback_chans, "Channels per playback device"); +module_param_array(capture_chans, int, NULL, DEFAULT_FILE_PERMS); +MODULE_PARM_DESC(capture_chans, "Channels per capture device"); + +static struct avirt_core { + struct device *dev; + + struct class *avirt_class; + struct platform_device *platform_dev; +} core; + +static struct avirt_coreinfo coreinfo = { + .version = { 0, 0, 1 }, + .pcm_buff_complete = pcm_buff_complete_cb, +}; + +static struct list_head audiopath_list; + +/** + * avirt_probe - Register ALSA soundcard + * @devptr: Platform driver device + * @return: 0 on success or error code otherwise + */ +static int avirt_probe(struct platform_device *devptr) +{ + // struct avirt_alsa_dev_config capture_config[MAX_PCM_DEVS]; + struct avirt_alsa_dev_config playback_config[MAX_PCM_DEVS]; + int err = 0, i = 0; + + if (playback_num == 0 && capture_num == 0) { + pr_err("playback_num or capture_num must be greater than 0!\n"); + return -EINVAL; + } + + coreinfo.playback_num = playback_num; + coreinfo.capture_num = capture_num; + + err = avirt_alsa_init(); + if (err < 0) + return err; + + // Set up playback + for (i = 0; i < playback_num; i++) { + if (!playback_names[i]) { + pr_err("Playback config devicename is NULL for idx=%d\n", + i); + return -EINVAL; + } + memcpy((char *)playback_config[i].devicename, playback_names[i], + MAX_NAME_LEN); + if (playback_chans[i] == 0) { + pr_err("Playback config channels is 0 for idx=%d\n", i); + return -EINVAL; + } + playback_config[i].channels = playback_chans[i]; + } + err = avirt_alsa_configure_pcm(playback_config, + SNDRV_PCM_STREAM_PLAYBACK, playback_num); + if (err < 0) + return err; + +// Does not work yet! +#if 0 + // Set up capture + for (i = 0; i < capture_num; i++) { + if (!capture_names[i]) { + pr_err("Capture config devicename is NULL for idx=%d", + i); + return -EINVAL; + } + memcpy((char *)capture_config[i].devicename, capture_names[i], + 255); + + if (capture_chans[i] == 0) { + pr_err("Capture config channels is 0 for idx=%d"); + return -EINVAL; + } + capture_config[i].channels = capture_chans[i]; + } + err = avirt_alsa_configure_pcm(capture_config, SNDRV_PCM_STREAM_CAPTURE, capture_num); + if (err < 0) + return err; +#endif + + // Register for ALSA + CHK_ERR(avirt_alsa_register(devptr)); + + return err; +} + +/** + * avirt_remove - Deregister ALSA soundcard + * @devptr: Platform driver device + * @return: 0 on success or error code otherwise + */ +static int avirt_remove(struct platform_device *devptr) +{ + avirt_alsa_deregister(); + + return 0; +} + +static struct platform_driver avirt_driver = { + .probe = avirt_probe, + .remove = avirt_remove, + .driver = + { + .name = SND_AVIRTUAL_DRIVER, + }, +}; + +struct avirt_audiopath_obj { + struct kobject kobj; + struct list_head list; + struct avirt_audiopath *path; +}; + +static struct kset *avirt_audiopath_kset; +static struct kobject *kobj; + +#define to_audiopath_obj(d) container_of(d, struct avirt_audiopath_obj, kobj) +#define to_audiopath_attr(a) \ + container_of(a, struct avirt_audiopath_attribute, attr) + +/** + * struct avirt_audiopath_attribute - access the attributes of Audio Path + * @attr: attributes of an Audio Path + * @show: pointer to the show function + * @store: pointer to the store function + */ +struct avirt_audiopath_attribute { + struct attribute attr; + ssize_t (*show)(struct avirt_audiopath_obj *d, + struct avirt_audiopath_attribute *attr, char *buf); + ssize_t (*store)(struct avirt_audiopath_obj *d, + struct avirt_audiopath_attribute *attr, + const char *buf, size_t count); +}; + +/** + * audiopath_attr_show - show function of an Audio Path + * @kobj: pointer to kobject + * @attr: pointer to attribute struct + * @buf: buffer + */ +static ssize_t audiopath_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct avirt_audiopath_attribute *audiopath_attr; + struct avirt_audiopath_obj *audiopath_obj; + + audiopath_attr = to_audiopath_attr(attr); + audiopath_obj = to_audiopath_obj(kobj); + + if (!audiopath_attr->show) + return -EIO; + + return audiopath_attr->show(audiopath_obj, audiopath_attr, buf); +} + +/** + * audiopath_attr_store - attribute store function of Audio Path object + * @kobj: pointer to kobject + * @attr: pointer to attribute struct + * @buf: buffer + * @len: length of buffer + */ +static ssize_t audiopath_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, + size_t len) +{ + struct avirt_audiopath_attribute *audiopath_attr; + struct avirt_audiopath_obj *audiopath_obj; + + audiopath_attr = to_audiopath_attr(attr); + audiopath_obj = to_audiopath_obj(kobj); + + if (!audiopath_attr->store) + return -EIO; + return audiopath_attr->store(audiopath_obj, audiopath_attr, buf, len); +} + +static const struct sysfs_ops avirt_audiopath_sysfs_ops = { + .show = audiopath_attr_show, + .store = audiopath_attr_store, +}; + +/** + * avirt_audiopath_release - Audio Path release function + * @kobj: pointer to Audio Paths's kobject + */ +static void avirt_audiopath_release(struct kobject *kobj) +{ + struct avirt_audiopath_obj *audiopath_obj = to_audiopath_obj(kobj); + + kfree(audiopath_obj); +} + +static ssize_t audiopath_name_show(struct avirt_audiopath_obj *audiopath_obj, + struct avirt_audiopath_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", audiopath_obj->path->name); +} + +static ssize_t audiopath_version_show(struct avirt_audiopath_obj *audiopath_obj, + struct avirt_audiopath_attribute *attr, + char *buf) +{ + struct avirt_audiopath *audiopath = audiopath_obj->path; + return sprintf(buf, "%d.%d.%d\n", audiopath->version[0], + audiopath->version[1], audiopath->version[2]); +} + +static struct avirt_audiopath_attribute avirt_audiopath_attrs[] = { + __ATTR_RO(audiopath_name), + __ATTR_RO(audiopath_version), +}; + +static struct attribute *avirt_audiopath_def_attrs[] = { + &avirt_audiopath_attrs[0].attr, + &avirt_audiopath_attrs[1].attr, + NULL, +}; + +static struct kobj_type avirt_audiopath_ktype = { + .sysfs_ops = &avirt_audiopath_sysfs_ops, + .release = avirt_audiopath_release, + .default_attrs = avirt_audiopath_def_attrs, +}; + +/** + * create_avirt_audiopath_obj - creates an Audio Path object + * @name: name of the Audio Path + * + * This creates an Audio Path object and assigns the kset and registers + * it with sysfs. + * @return: Pointer to the Audio Path object or NULL if it failed. + */ +static struct avirt_audiopath_obj *create_avirt_audiopath_obj(const char *name) +{ + struct avirt_audiopath_obj *avirt_audiopath; + int retval; + + avirt_audiopath = kzalloc(sizeof(*avirt_audiopath), GFP_KERNEL); + if (!avirt_audiopath) + return NULL; + avirt_audiopath->kobj.kset = avirt_audiopath_kset; + retval = kobject_init_and_add(&avirt_audiopath->kobj, + &avirt_audiopath_ktype, kobj, "%s", name); + if (retval) { + kobject_put(&avirt_audiopath->kobj); + return NULL; + } + kobject_uevent(&avirt_audiopath->kobj, KOBJ_ADD); + return avirt_audiopath; +} + +/** + * destroy_avirt_audiopath_obj - destroys an Audio Path object + * @name: the Audio Path object + */ +static void destroy_avirt_audiopath_obj(struct avirt_audiopath_obj *p) +{ + kobject_put(&p->kobj); +} + +/** + * avirt_get_current_audiopath - retrieves the current Audio Path + * @return: Current Audio Path + */ +struct avirt_audiopath *avirt_get_current_audiopath() +{ + struct avirt_audiopath_obj *ap_obj = list_entry( + audiopath_list.next, struct avirt_audiopath_obj, list); + return ap_obj->path; +} + +/** + * avirt_register_audiopath - register Audio Path with ALSA virtual driver + * @audiopath: Audio Path to be registered + * @core: ALSA virtual driver core info + * @return: 0 on success or error code otherwise + */ +int avirt_register_audiopath(struct avirt_audiopath *audiopath, + struct avirt_coreinfo **info) +{ + struct avirt_audiopath_obj *audiopath_obj; + + if (!audiopath) { + pr_err("Bad audio path\n"); + return -EINVAL; + } + + audiopath_obj = create_avirt_audiopath_obj(audiopath->name); + if (!audiopath_obj) { + pr_info("failed to alloc driver object\n"); + return -ENOMEM; + } + audiopath_obj->path = audiopath; + + audiopath->context = audiopath_obj; + pr_info("Registered new Audio Path: %s\n", audiopath->name); + pr_info("\tBlocksize: %d, Periods: %d\n", audiopath->blocksize, + audiopath->hw->periods_max); + list_add_tail(&audiopath_obj->list, &audiopath_list); + + *info = &coreinfo; + + return 0; +} +EXPORT_SYMBOL_GPL(avirt_register_audiopath); + +/** + * avirt_deregister_audiopath - deregister Audio Path with ALSA virtual driver + * @audiopath: Audio Path to be deregistered + * @return: 0 on success or error code otherwise + */ +int avirt_deregister_audiopath(struct avirt_audiopath *audiopath) +{ + struct avirt_audiopath_obj *audiopath_obj; + + // Check if audio path is registered + if (!audiopath) { + pr_err("Bad Audio Path Driver\n"); + return -EINVAL; + } + + audiopath_obj = audiopath->context; + if (!audiopath_obj) { + pr_info("driver not registered.\n"); + return -EINVAL; + } + + list_del(&audiopath_obj->list); + destroy_avirt_audiopath_obj(audiopath_obj); + pr_info("Deregistered Audio Path %s\n", audiopath->name); + + return 0; +} +EXPORT_SYMBOL_GPL(avirt_deregister_audiopath); + +/** + * avirt_unregister_all - Unregister the platform device driver + */ +static void avirt_unregister_all(void) +{ + platform_device_unregister(core.platform_dev); + platform_driver_unregister(&avirt_driver); +} + +/** + * core_init - Initialize the kernel module + */ +static int __init core_init(void) +{ + int err; + + pr_info("Alsa Virtual Sound Driver avirt-%d.%d.%d\n", + coreinfo.version[0], coreinfo.version[1], coreinfo.version[2]); + + // Initialize audiopaths linked list + INIT_LIST_HEAD(&audiopath_list); + + err = platform_driver_register(&avirt_driver); + if (err < 0) + return err; + + core.platform_dev = platform_device_register_simple(SND_AVIRTUAL_DRIVER, + 0, NULL, 0); + if (IS_ERR(core.platform_dev)) { + err = PTR_ERR(core.platform_dev); + pr_err("platform_dev error [%d]!\n", err); + goto exit_platform_device; + } + + core.avirt_class = class_create(THIS_MODULE, SND_AVIRTUAL_DRIVER); + if (IS_ERR(core.avirt_class)) { + pr_err("No udev support\n"); + err = PTR_ERR(core.avirt_class); + goto exit_bus; + } + + core.dev = device_create(core.avirt_class, NULL, 0, NULL, "avirtcore"); + if (IS_ERR(core.dev)) { + err = PTR_ERR(core.dev); + goto exit_class; + } + + avirt_audiopath_kset = + kset_create_and_add("audiopaths", NULL, &core.dev->kobj); + if (!avirt_audiopath_kset) { + err = -ENOMEM; + goto exit_class_container; + } + + return 0; + +exit_class_container: + device_destroy(core.avirt_class, 0); +exit_class: + class_destroy(core.avirt_class); +exit_bus: +exit_platform_device: + avirt_unregister_all(); + return err; +} + +/** + * core_exit - Destroy the kernel module + */ +static void __exit core_exit(void) +{ + kset_unregister(avirt_audiopath_kset); + device_destroy(core.avirt_class, 0); + class_destroy(core.avirt_class); + + avirt_unregister_all(); + + pr_info("playback_num: %d, capture_num: %d\n", playback_num, + capture_num); + + pr_info("playback_chans: %d %d %d %d %d %d %d %d\n", playback_chans[0], + playback_chans[1], playback_chans[2], playback_chans[3], + playback_chans[4], playback_chans[5], playback_chans[6], + playback_chans[7]); + + pr_info("capture_chans: %d %d %d %d %d %d %d %d\n", capture_chans[0], + capture_chans[1], capture_chans[2], capture_chans[3], + capture_chans[4], capture_chans[5], capture_chans[6], + capture_chans[7]); + + pr_info("playback_names: %s %s %s %s %s %s %s %s\n", playback_names[0], + playback_names[1], playback_names[2], playback_names[3], + playback_names[4], playback_names[5], playback_names[6], + playback_names[7]); + + pr_info("capture_names: %s %s %s %s %s %s %s %s\n", capture_names[0], + capture_names[1], capture_names[2], capture_names[3], + capture_names[4], capture_names[5], capture_names[6], + capture_names[7]); +} + +module_init(core_init); +module_exit(core_exit); @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ALSA Virtual Soundcard + * + * core.h - System-level header for virtual ALSA card + * + * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd + */ + +#ifndef __AVIRT_CORE_H__ +#define __AVIRT_CORE_H__ + +#include <sound/pcm.h> + +/** + * PCM buffer complete callback + * + * These are called from the audiopath when a PCM buffer has completed, and + * new data can be submitted/retrieved + */ +typedef int (*avirt_buff_complete)(struct snd_pcm_substream *substream); + +struct avirt_audiopath { + const char *name; + unsigned version[3]; + int value; + struct snd_pcm_hardware *hw; + struct snd_pcm_ops *pcm_ops; + unsigned blocksize; + + void *context; +}; + +struct avirt_coreinfo { + unsigned version[3]; + unsigned playback_num; + unsigned capture_num; + + avirt_buff_complete pcm_buff_complete; +}; + +/** + * avirt_register_audiopath - register Audio Path with ALSA virtual driver + * @audiopath: Audio Path to be registered + * @core: ALSA virtual driver core info + * @return: 0 on success or error code otherwise + */ +int avirt_register_audiopath(struct avirt_audiopath *audiopath, + struct avirt_coreinfo **coreinfo); + +/** + * avirt_deregister_audiopath - deregister Audio Path with ALSA virtual driver + * @audiopath: Audio Path to be deregistered + * @return: 0 on success or error code otherwise + */ +int avirt_deregister_audiopath(struct avirt_audiopath *audiopath); + +/** + * avirt_get_current_audiopath - retrieves the current Audio Path + * @return: Current Audio Path + */ +struct avirt_audiopath *avirt_get_current_audiopath(void); + +#endif // __AVIRT_CORE_H__ diff --git a/dummy/Kconfig b/dummy/Kconfig new file mode 100644 index 0000000..6a706c0 --- /dev/null +++ b/dummy/Kconfig @@ -0,0 +1,12 @@ +# +# AVIRT Dummy Audio Path +# + +config AVIRT_DUMMYAP + tristate "DummyAP" + select SND_PCM + ---help--- + Say Y here if you want to add dummy audio path. + + To compile this driver as a module, choose M here: the + module will be called avirt_dummyap. diff --git a/dummy/Makefile b/dummy/Makefile new file mode 100644 index 0000000..965bd1a --- /dev/null +++ b/dummy/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_AVIRT_DUMMYAP) += avirt_dummyap.o + +$(info $(src)) +avirt_dummyap-objs := dummy.o +ccflags-y += -Idrivers/staging/ +ccflags-y += -I$(src)/../../ diff --git a/dummy/dummy.c b/dummy/dummy.c new file mode 100644 index 0000000..7522345 --- /dev/null +++ b/dummy/dummy.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ALSA Virtual Soundcard + * + * dummy_audiopath.c - AVIRT sample Audio Path definition + * + * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <avirt/core.h> + +MODULE_AUTHOR("JOSHANNE <james.oshannessy@fiberdyne.com.au>"); +MODULE_AUTHOR("MFARRUGI <mark.farrugia@fiberdyne.com.au>"); +MODULE_DESCRIPTION("Sample Audio Path Module Interface"); +MODULE_LICENSE("GPL"); + +#define DUMMY_SAMPLE_RATE 48000 +#define DUMMY_BLOCKSIZE 512 +#define DUMMY_PERIODS_MIN 1 +#define DUMMY_PERIODS_MAX 8 + +#define get_dummy_ops(substream) \ + (*(const struct dummy_timer_ops **)(substream)->runtime->private_data) + +static struct avirt_coreinfo *coreinfo; + +/******************************************************************************* + * System Timer Interface + * + * This is used to call the ALSA PCM callbacks required to notify AVIRT of the + * 'periods elapsed', so that buffers can keep getting transmitted here + * + * (Borrowed from the default ALSA 'dummy' driver (sound/driver/dummy.c)) + ******************************************************************************/ +struct dummy_timer_ops { + int (*create)(struct snd_pcm_substream *); + void (*free)(struct snd_pcm_substream *); + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); +}; + +struct dummy_systimer_pcm { + /* ops must be the first item */ + const struct dummy_timer_ops *timer_ops; + spinlock_t lock; + struct timer_list timer; + unsigned long base_time; + unsigned int frac_pos; /* fractional sample position (based HZ) */ + unsigned int frac_period_rest; + unsigned int frac_buffer_size; /* buffer_size * HZ */ + unsigned int frac_period_size; /* period_size * HZ */ + unsigned int rate; + int elapsed; + struct snd_pcm_substream *substream; +}; + +static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm) +{ + mod_timer(&dpcm->timer, jiffies + (dpcm->frac_period_rest + dpcm->rate - + 1) / dpcm->rate); +} + +static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm) +{ + unsigned long delta; + + delta = jiffies - dpcm->base_time; + if (!delta) + return; + dpcm->base_time += delta; + delta *= dpcm->rate; + dpcm->frac_pos += delta; + while (dpcm->frac_pos >= dpcm->frac_buffer_size) + dpcm->frac_pos -= dpcm->frac_buffer_size; + while (dpcm->frac_period_rest <= delta) { + dpcm->elapsed++; + dpcm->frac_period_rest += dpcm->frac_period_size; + } + dpcm->frac_period_rest -= delta; +} + +static int dummy_systimer_start(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + dpcm->base_time = jiffies; + dummy_systimer_rearm(dpcm); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + del_timer(&dpcm->timer); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_systimer_pcm *dpcm = runtime->private_data; + + dpcm->frac_pos = 0; + dpcm->rate = runtime->rate; + dpcm->frac_buffer_size = runtime->buffer_size * HZ; + dpcm->frac_period_size = runtime->period_size * HZ; + dpcm->frac_period_rest = dpcm->frac_period_size; + dpcm->elapsed = 0; + + return 0; +} + +static void dummy_systimer_callback(struct timer_list *t) +{ + struct dummy_systimer_pcm *dpcm = from_timer(dpcm, t, timer); + unsigned long flags; + int elapsed = 0; + + spin_lock_irqsave(&dpcm->lock, flags); + dummy_systimer_update(dpcm); + dummy_systimer_rearm(dpcm); + elapsed = dpcm->elapsed; + dpcm->elapsed = 0; + spin_unlock_irqrestore(&dpcm->lock, flags); + if (elapsed) + coreinfo->pcm_buff_complete(dpcm->substream); +} + +static snd_pcm_uframes_t + dummy_systimer_pointer(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + snd_pcm_uframes_t pos; + + spin_lock(&dpcm->lock); + dummy_systimer_update(dpcm); + pos = dpcm->frac_pos / HZ; + spin_unlock(&dpcm->lock); + return pos; +} + +static int dummy_systimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + timer_setup(&dpcm->timer, dummy_systimer_callback, 0); + spin_lock_init(&dpcm->lock); + dpcm->substream = substream; + return 0; +} + +static void dummy_systimer_free(struct snd_pcm_substream *substream) +{ + kfree(substream->runtime->private_data); +} + +static const struct dummy_timer_ops dummy_systimer_ops = { + .create = dummy_systimer_create, + .free = dummy_systimer_free, + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, + .pointer = dummy_systimer_pointer, +}; + +/******************************************************************************* + * Audio Path ALSA PCM Callbacks + ******************************************************************************/ +static int dummy_pcm_open(struct snd_pcm_substream *substream) +{ + const struct dummy_timer_ops *ops; + int err; + + ops = &dummy_systimer_ops; + err = ops->create(substream); + if (err < 0) + return err; + get_dummy_ops(substream) = ops; + + return 0; +} + +static int dummy_pcm_close(struct snd_pcm_substream *substream) +{ + get_dummy_ops(substream)->free(substream); + return 0; +} + +static snd_pcm_uframes_t dummy_pcm_pointer(struct snd_pcm_substream *substream) +{ + return get_dummy_ops(substream)->pointer(substream); +} + +static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return get_dummy_ops(substream)->start(substream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + return get_dummy_ops(substream)->stop(substream); + } + return -EINVAL; +} + +static int dummy_pcm_prepare(struct snd_pcm_substream *substream) +{ + return get_dummy_ops(substream)->prepare(substream); +} + +static struct snd_pcm_ops dummyap_pcm_ops = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .prepare = dummy_pcm_prepare, + .pointer = dummy_pcm_pointer, + .trigger = dummy_pcm_trigger, +}; + +/******************************************************************************* + * Dummy Audio Path AVIRT registration + ******************************************************************************/ +static struct snd_pcm_hardware dummyap_hw = { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .info = (SNDRV_PCM_INFO_INTERLEAVED // Channel interleaved audio + | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID), + .rates = SNDRV_PCM_RATE_48000, + .rate_min = DUMMY_SAMPLE_RATE, + .rate_max = DUMMY_SAMPLE_RATE, + .periods_min = DUMMY_PERIODS_MIN, + .periods_max = DUMMY_PERIODS_MAX, +}; + +static struct avirt_audiopath dummyap_module = { + .name = "Dummy Audio Path", + .version = { 0, 0, 1 }, + .value = 10, + .hw = &dummyap_hw, + .pcm_ops = &dummyap_pcm_ops, + .blocksize = DUMMY_BLOCKSIZE, +}; + +static int __init dummy_init(void) +{ + int err = 0; + + pr_info("init()\n"); + + err = avirt_register_audiopath(&dummyap_module, &coreinfo); + if ((err < 0) || (!coreinfo)) { + pr_err("%s: coreinfo is NULL!\n", __func__); + return err; + } + + return err; +} + +static void __exit dummy_exit(void) +{ + pr_info("exit()\n"); + + avirt_deregister_audiopath(&dummyap_module); +} + +module_init(dummy_init); +module_exit(dummy_exit); diff --git a/loadDrivers.sh b/loadDrivers.sh new file mode 100755 index 0000000..fca3203 --- /dev/null +++ b/loadDrivers.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Sample module parameters +params="\ +playback_num=4 \ +playback_chans=2,4,1,1 \ +playback_names=radio,media,nav,phone \ +capture_num=1 \ +capture_chans=2 \ +capture_names=voice" + +# Load the virtual driver +insmod avirt_core.ko "$params" + +# Load the additional audio path +insmod dummy/avirt_dummyap.ko + +echo "Drivers Loaded!"
\ No newline at end of file diff --git a/make-agl.sh b/make-agl.sh new file mode 100755 index 0000000..4421af8 --- /dev/null +++ b/make-agl.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Get SDK path +sdk_id=$1 # first arg must be XDS_SDK_ID +shift 1 +long_sdkpath=$(xds-cli sdks get $sdk_id | grep Path) +sdkpath=${long_sdkpath:4} + +# Build +/opt/AGL/bin/xds-cli exec --config xds-project.conf -- \ + LDFLAGS= CONFIG_AVIRT=m CONFIG_AVIRT_BUILDLOCAL=y CONFIG_AVIRT_DUMMYAP=m \ + make -C $sdkpath/sysroots/aarch64-agl-linux/usr/src/kernel M=$(pwd) $@ diff --git a/unload.sh b/unload.sh new file mode 100755 index 0000000..02ed767 --- /dev/null +++ b/unload.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +rm_module() { + lsmod |grep "^$1\>" && rmmod $1 || true +} + +rm_module avirt_dummyap +rm_module avirt_core +echo "Drivers Removed!"
\ No newline at end of file diff --git a/xds-project.conf b/xds-project.conf new file mode 100644 index 0000000..59a82f8 --- /dev/null +++ b/xds-project.conf @@ -0,0 +1,4 @@ +# XDS project settings +export XDS_AGENT_URL=localhost:8800 +export XDS_PROJECT_ID=4ad79245-a1c3-11e8-82d3-0242ac110002 +export XDS_SDK_ID=bf869951 |