summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames O'Shannessy <james.oshannessy@fiberdyne.com.au>2018-08-27 15:08:14 +1000
committerMark Farrugia <mark.farrugia@fiberdyne.com.au>2018-10-26 17:27:24 +1100
commitbc8c3a602bceaba0e6d34a1ba8b776b56b00d766 (patch)
treeae8cec69c910144611e06f272033cc8c2aee7032
parent416c9b0f9b816a6b2eb5c544f21567ad286dd4be (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-format428
-rw-r--r--.gitignore10
-rw-r--r--.vscode/extensions.json8
-rw-r--r--.vscode/settings.json8
-rw-r--r--.vscode/tasks.json73
-rw-r--r--Kconfig20
-rw-r--r--LICENSE201
-rw-r--r--Makefile16
-rw-r--r--README.md47
-rw-r--r--alsa-pcm.c381
-rwxr-xr-xalsa.c255
-rwxr-xr-xalsa.h131
-rw-r--r--core.c495
-rw-r--r--core.h64
-rw-r--r--dummy/Kconfig12
-rw-r--r--dummy/Makefile6
-rw-r--r--dummy/dummy.c278
-rwxr-xr-xloadDrivers.sh18
-rwxr-xr-xmake-agl.sh12
-rwxr-xr-xunload.sh9
-rw-r--r--xds-project.conf4
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
+...
diff --git a/.gitignore b/.gitignore
index fcd6a60..7032bb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..0e2a52a
--- /dev/null
+++ b/Kconfig
@@ -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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e147c80
--- /dev/null
+++ b/LICENSE
@@ -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,
+
+};
diff --git a/alsa.c b/alsa.c
new file mode 100755
index 0000000..0b08744
--- /dev/null
+++ b/alsa.c
@@ -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;
+}
diff --git a/alsa.h b/alsa.h
new file mode 100755
index 0000000..29fc64d
--- /dev/null
+++ b/alsa.h
@@ -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__
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..d0a24ec
--- /dev/null
+++ b/core.c
@@ -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);
diff --git a/core.h b/core.h
new file mode 100644
index 0000000..5eb6026
--- /dev/null
+++ b/core.h
@@ -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